]> git.proxmox.com Git - mirror_corosync-qdevice.git/commitdiff
Initial import from corosync codebase
authorJan Friesse <jfriesse@redhat.com>
Mon, 22 Jan 2018 14:59:23 +0000 (15:59 +0100)
committerJan Friesse <jfriesse@redhat.com>
Tue, 23 Jan 2018 13:24:36 +0000 (14:24 +0100)
Used the code from corosync master
(31ddba64a2726bcedf81eb84df2e2da4846832f7)

Signed-off-by: Jan Friesse <jfriesse@redhat.com>
201 files changed:
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
README [new file with mode: 0644]
autogen.sh [new file with mode: 0755]
build-aux/git-version-gen [new file with mode: 0755]
build-aux/gitlog-to-changelog [new file with mode: 0755]
build-aux/release.mk [new file with mode: 0644]
configure.ac [new file with mode: 0644]
corosync-qdevice.spec.in [new file with mode: 0644]
init/.gitignore [new file with mode: 0644]
init/Makefile.am [new file with mode: 0644]
init/corosync-qdevice.in [new file with mode: 0755]
init/corosync-qdevice.service.in [new file with mode: 0644]
init/corosync-qdevice.sysconfig.example [new file with mode: 0644]
init/corosync-qnetd.in [new file with mode: 0755]
init/corosync-qnetd.service.in [new file with mode: 0644]
init/corosync-qnetd.sysconfig.example [new file with mode: 0644]
man/.gitignore [new file with mode: 0644]
man/Makefile.am [new file with mode: 0644]
man/corosync-qdevice-net-certutil.8 [new file with mode: 0644]
man/corosync-qdevice-tool.8 [new file with mode: 0644]
man/corosync-qdevice.8 [new file with mode: 0644]
man/corosync-qnetd-certutil.8 [new file with mode: 0644]
man/corosync-qnetd-tool.8 [new file with mode: 0644]
man/corosync-qnetd.8 [new file with mode: 0644]
qdevices/.gitignore [new file with mode: 0644]
qdevices/Makefile.am [new file with mode: 0644]
qdevices/corosync-qdevice-net-certutil.sh [new file with mode: 0644]
qdevices/corosync-qdevice-tool.c [new file with mode: 0644]
qdevices/corosync-qdevice.c [new file with mode: 0644]
qdevices/corosync-qnetd-certutil.sh [new file with mode: 0644]
qdevices/corosync-qnetd-tool.c [new file with mode: 0644]
qdevices/corosync-qnetd.c [new file with mode: 0644]
qdevices/dynar-getopt-lex.c [new file with mode: 0644]
qdevices/dynar-getopt-lex.h [new file with mode: 0644]
qdevices/dynar-simple-lex.c [new file with mode: 0644]
qdevices/dynar-simple-lex.h [new file with mode: 0644]
qdevices/dynar-str.c [new file with mode: 0644]
qdevices/dynar-str.h [new file with mode: 0644]
qdevices/dynar.c [new file with mode: 0644]
qdevices/dynar.h [new file with mode: 0644]
qdevices/msg.c [new file with mode: 0644]
qdevices/msg.h [new file with mode: 0644]
qdevices/msgio.c [new file with mode: 0644]
qdevices/msgio.h [new file with mode: 0644]
qdevices/node-list.c [new file with mode: 0644]
qdevices/node-list.h [new file with mode: 0644]
qdevices/nss-sock.c [new file with mode: 0644]
qdevices/nss-sock.h [new file with mode: 0644]
qdevices/pr-poll-array.c [new file with mode: 0644]
qdevices/pr-poll-array.h [new file with mode: 0644]
qdevices/process-list.c [new file with mode: 0644]
qdevices/process-list.h [new file with mode: 0644]
qdevices/qdevice-advanced-settings.c [new file with mode: 0644]
qdevices/qdevice-advanced-settings.h [new file with mode: 0644]
qdevices/qdevice-cmap.c [new file with mode: 0644]
qdevices/qdevice-cmap.h [new file with mode: 0644]
qdevices/qdevice-config.h [new file with mode: 0644]
qdevices/qdevice-heuristics-cmd-str.h [new file with mode: 0644]
qdevices/qdevice-heuristics-cmd.c [new file with mode: 0644]
qdevices/qdevice-heuristics-cmd.h [new file with mode: 0644]
qdevices/qdevice-heuristics-exec-list.c [new file with mode: 0644]
qdevices/qdevice-heuristics-exec-list.h [new file with mode: 0644]
qdevices/qdevice-heuristics-exec-result.c [new file with mode: 0644]
qdevices/qdevice-heuristics-exec-result.h [new file with mode: 0644]
qdevices/qdevice-heuristics-instance.c [new file with mode: 0644]
qdevices/qdevice-heuristics-instance.h [new file with mode: 0644]
qdevices/qdevice-heuristics-io.c [new file with mode: 0644]
qdevices/qdevice-heuristics-io.h [new file with mode: 0644]
qdevices/qdevice-heuristics-log.c [new file with mode: 0644]
qdevices/qdevice-heuristics-log.h [new file with mode: 0644]
qdevices/qdevice-heuristics-mode.c [new file with mode: 0644]
qdevices/qdevice-heuristics-mode.h [new file with mode: 0644]
qdevices/qdevice-heuristics-result-notifier.c [new file with mode: 0644]
qdevices/qdevice-heuristics-result-notifier.h [new file with mode: 0644]
qdevices/qdevice-heuristics-worker-cmd.c [new file with mode: 0644]
qdevices/qdevice-heuristics-worker-cmd.h [new file with mode: 0644]
qdevices/qdevice-heuristics-worker-instance.h [new file with mode: 0644]
qdevices/qdevice-heuristics-worker-log.c [new file with mode: 0644]
qdevices/qdevice-heuristics-worker-log.h [new file with mode: 0644]
qdevices/qdevice-heuristics-worker.c [new file with mode: 0644]
qdevices/qdevice-heuristics-worker.h [new file with mode: 0644]
qdevices/qdevice-heuristics.c [new file with mode: 0644]
qdevices/qdevice-heuristics.h [new file with mode: 0644]
qdevices/qdevice-instance.c [new file with mode: 0644]
qdevices/qdevice-instance.h [new file with mode: 0644]
qdevices/qdevice-ipc-cmd.c [new file with mode: 0644]
qdevices/qdevice-ipc-cmd.h [new file with mode: 0644]
qdevices/qdevice-ipc.c [new file with mode: 0644]
qdevices/qdevice-ipc.h [new file with mode: 0644]
qdevices/qdevice-log-debug.c [new file with mode: 0644]
qdevices/qdevice-log-debug.h [new file with mode: 0644]
qdevices/qdevice-log.c [new file with mode: 0644]
qdevices/qdevice-log.h [new file with mode: 0644]
qdevices/qdevice-model-net.c [new file with mode: 0644]
qdevices/qdevice-model-net.h [new file with mode: 0644]
qdevices/qdevice-model-type.h [new file with mode: 0644]
qdevices/qdevice-model.c [new file with mode: 0644]
qdevices/qdevice-model.h [new file with mode: 0644]
qdevices/qdevice-net-algo-2nodelms.c [new file with mode: 0644]
qdevices/qdevice-net-algo-2nodelms.h [new file with mode: 0644]
qdevices/qdevice-net-algo-ffsplit.c [new file with mode: 0644]
qdevices/qdevice-net-algo-ffsplit.h [new file with mode: 0644]
qdevices/qdevice-net-algo-lms.c [new file with mode: 0644]
qdevices/qdevice-net-algo-lms.h [new file with mode: 0644]
qdevices/qdevice-net-algo-test.c [new file with mode: 0644]
qdevices/qdevice-net-algo-test.h [new file with mode: 0644]
qdevices/qdevice-net-algorithm.c [new file with mode: 0644]
qdevices/qdevice-net-algorithm.h [new file with mode: 0644]
qdevices/qdevice-net-cast-vote-timer.c [new file with mode: 0644]
qdevices/qdevice-net-cast-vote-timer.h [new file with mode: 0644]
qdevices/qdevice-net-disconnect-reason.h [new file with mode: 0644]
qdevices/qdevice-net-echo-request-timer.c [new file with mode: 0644]
qdevices/qdevice-net-echo-request-timer.h [new file with mode: 0644]
qdevices/qdevice-net-heuristics.c [new file with mode: 0644]
qdevices/qdevice-net-heuristics.h [new file with mode: 0644]
qdevices/qdevice-net-instance.c [new file with mode: 0644]
qdevices/qdevice-net-instance.h [new file with mode: 0644]
qdevices/qdevice-net-ipc-cmd.c [new file with mode: 0644]
qdevices/qdevice-net-ipc-cmd.h [new file with mode: 0644]
qdevices/qdevice-net-msg-received.c [new file with mode: 0644]
qdevices/qdevice-net-msg-received.h [new file with mode: 0644]
qdevices/qdevice-net-nss.c [new file with mode: 0644]
qdevices/qdevice-net-nss.h [new file with mode: 0644]
qdevices/qdevice-net-poll-array-user-data.h [new file with mode: 0644]
qdevices/qdevice-net-poll.c [new file with mode: 0644]
qdevices/qdevice-net-poll.h [new file with mode: 0644]
qdevices/qdevice-net-send.c [new file with mode: 0644]
qdevices/qdevice-net-send.h [new file with mode: 0644]
qdevices/qdevice-net-socket.c [new file with mode: 0644]
qdevices/qdevice-net-socket.h [new file with mode: 0644]
qdevices/qdevice-net-votequorum.c [new file with mode: 0644]
qdevices/qdevice-net-votequorum.h [new file with mode: 0644]
qdevices/qdevice-votequorum.c [new file with mode: 0644]
qdevices/qdevice-votequorum.h [new file with mode: 0644]
qdevices/qnet-config.h [new file with mode: 0644]
qdevices/qnetd-advanced-settings.c [new file with mode: 0644]
qdevices/qnetd-advanced-settings.h [new file with mode: 0644]
qdevices/qnetd-algo-2nodelms.c [new file with mode: 0644]
qdevices/qnetd-algo-2nodelms.h [new file with mode: 0644]
qdevices/qnetd-algo-ffsplit.c [new file with mode: 0644]
qdevices/qnetd-algo-ffsplit.h [new file with mode: 0644]
qdevices/qnetd-algo-lms.c [new file with mode: 0644]
qdevices/qnetd-algo-lms.h [new file with mode: 0644]
qdevices/qnetd-algo-test.c [new file with mode: 0644]
qdevices/qnetd-algo-test.h [new file with mode: 0644]
qdevices/qnetd-algo-utils.c [new file with mode: 0644]
qdevices/qnetd-algo-utils.h [new file with mode: 0644]
qdevices/qnetd-algorithm.c [new file with mode: 0644]
qdevices/qnetd-algorithm.h [new file with mode: 0644]
qdevices/qnetd-client-algo-timer.c [new file with mode: 0644]
qdevices/qnetd-client-algo-timer.h [new file with mode: 0644]
qdevices/qnetd-client-list.c [new file with mode: 0644]
qdevices/qnetd-client-list.h [new file with mode: 0644]
qdevices/qnetd-client-msg-received.c [new file with mode: 0644]
qdevices/qnetd-client-msg-received.h [new file with mode: 0644]
qdevices/qnetd-client-net.c [new file with mode: 0644]
qdevices/qnetd-client-net.h [new file with mode: 0644]
qdevices/qnetd-client-send.c [new file with mode: 0644]
qdevices/qnetd-client-send.h [new file with mode: 0644]
qdevices/qnetd-client.c [new file with mode: 0644]
qdevices/qnetd-client.h [new file with mode: 0644]
qdevices/qnetd-cluster-list.c [new file with mode: 0644]
qdevices/qnetd-cluster-list.h [new file with mode: 0644]
qdevices/qnetd-cluster.c [new file with mode: 0644]
qdevices/qnetd-cluster.h [new file with mode: 0644]
qdevices/qnetd-dpd-timer.c [new file with mode: 0644]
qdevices/qnetd-dpd-timer.h [new file with mode: 0644]
qdevices/qnetd-instance.c [new file with mode: 0644]
qdevices/qnetd-instance.h [new file with mode: 0644]
qdevices/qnetd-ipc-cmd.c [new file with mode: 0644]
qdevices/qnetd-ipc-cmd.h [new file with mode: 0644]
qdevices/qnetd-ipc.c [new file with mode: 0644]
qdevices/qnetd-ipc.h [new file with mode: 0644]
qdevices/qnetd-log-debug.c [new file with mode: 0644]
qdevices/qnetd-log-debug.h [new file with mode: 0644]
qdevices/qnetd-log.c [new file with mode: 0644]
qdevices/qnetd-log.h [new file with mode: 0644]
qdevices/qnetd-poll-array-user-data.h [new file with mode: 0644]
qdevices/send-buffer-list.c [new file with mode: 0644]
qdevices/send-buffer-list.h [new file with mode: 0644]
qdevices/test-dynar-getopt-lex.c [new file with mode: 0644]
qdevices/test-dynar-simple-lex.c [new file with mode: 0644]
qdevices/test-dynar.c [new file with mode: 0644]
qdevices/test-process-list.c [new file with mode: 0644]
qdevices/test-qnetd-cluster-list.c [new file with mode: 0644]
qdevices/timer-list.c [new file with mode: 0644]
qdevices/timer-list.h [new file with mode: 0644]
qdevices/tlv.c [new file with mode: 0644]
qdevices/tlv.h [new file with mode: 0644]
qdevices/unix-socket-client-list.c [new file with mode: 0644]
qdevices/unix-socket-client-list.h [new file with mode: 0644]
qdevices/unix-socket-client.c [new file with mode: 0644]
qdevices/unix-socket-client.h [new file with mode: 0644]
qdevices/unix-socket-ipc.c [new file with mode: 0644]
qdevices/unix-socket-ipc.h [new file with mode: 0644]
qdevices/unix-socket.c [new file with mode: 0644]
qdevices/unix-socket.h [new file with mode: 0644]
qdevices/utils.c [new file with mode: 0644]
qdevices/utils.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..118c4f5
--- /dev/null
@@ -0,0 +1,33 @@
+*.o
+*.a
+*.so*
+*.lo
+*.la
+.libs
+.deps
+.version
+doc
+Makefile
+Makefile.in
+corosync-qdevice.spec
+aclocal.m4
+autom4te.cache/
+compile
+config.guess
+config.log
+config.status
+config.sub
+configure
+corosync-*.tar*
+depcomp
+install-sh
+libtool
+ltmain.sh
+m4
+missing
+tags
+ID
+Doxyfile
+config.h*
+stamp-*
+test-driver
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..031f06a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,58 @@
+-----------------------------------------------------------------------------
+The following license applies to every file in this source distribution except
+for the files git-version-gen, and gitlog-to-changelog.
+
+The git* files, which are available under GPLv3 or later,  are only used by
+our release process to generate text file content and are not part of any
+generated binary.
+-----------------------------------------------------------------------------
+
+Copyright (c) 2015-2018 Red Hat, Inc.
+
+All rights reserved.
+
+This software licensed under BSD license, the text of which follows:
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+- Neither the name of the Red Hat, Inc. nor the names of its
+  contributors may be used to endorse or promote products derived from this
+  software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
+
+-----------------------------------------------------------------------------
+The corosync-qdevice project uses software for release processing which generates
+changelogs and version information for the software.  These programs are not
+used by the generated binaries or libraries These files are git-version-gen
+and gitlog-to-changelog.
+-----------------------------------------------------------------------------
+The license for these files is as follows:
+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 3 of the License, or
+(at your option) any later version.
+
+This program 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, see <http://www.gnu.org/licenses/>.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..a926e3f
--- /dev/null
@@ -0,0 +1,156 @@
+# Copyright (c) 2009 Red Hat, Inc.
+#
+# Authors: Andrew Beekhof
+#         Steven Dake (sdake@redhat.com)
+#
+# This software licensed under BSD license, the text of which follows:
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+SPEC                   = $(PACKAGE_NAME).spec
+
+TARFILE                        = $(PACKAGE_NAME)-$(VERSION).tar.gz
+
+EXTRA_DIST             = autogen.sh $(SPEC).in \
+                         build-aux/git-version-gen \
+                         build-aux/gitlog-to-changelog \
+                         build-aux/release.mk \
+                         .version
+
+ACLOCAL_AMFLAGS                = -I m4
+
+MAINTAINERCLEANFILES   = Makefile.in aclocal.m4 configure depcomp \
+                         config.guess config.sub missing install-sh \
+                         autoheader automake autoconf \
+                         autoscan.log configure.scan ltmain.sh test-driver
+
+dist_doc_DATA          = LICENSE
+
+SUBDIRS                        = qdevices man init
+
+install-exec-local:
+if BUILD_QNETD
+       $(INSTALL) -m 770 -d $(DESTDIR)/${localstatedir}/run/corosync-qnetd
+       $(INSTALL) -m 770 -d $(DESTDIR)/${COROSYSCONFDIR}/qnetd
+endif
+if BUILD_QDEVICES
+       $(INSTALL) -m 770 -d $(DESTDIR)/${localstatedir}/run/corosync-qdevice
+       $(INSTALL) -d $(DESTDIR)/${COROSYSCONFDIR}/qdevice/
+       $(INSTALL) -m 770 -d $(DESTDIR)/${COROSYSCONFDIR}/qdevice/net
+endif
+
+uninstall-local:
+if BUILD_QNETD
+       rmdir $(DESTDIR)/${localstatedir}/run/corosync-qnetd || :;
+       rmdir $(DESTDIR)/${COROSYSCONFDIR}/qnetd || :;
+endif
+if BUILD_QDEVICES
+       rmdir $(DESTDIR)/${localstatedir}/run/corosync-qdevice || :;
+       rmdir $(DESTDIR)/${COROSYSCONFDIR}/qdevice/net || :;
+       rmdir $(DESTDIR)/${COROSYSCONFDIR}/qdevice/ || :;
+endif
+
+dist-clean-local:
+       rm -f autoconf automake autoheader
+
+clean-generic:
+       rm -rf doc/api $(SPEC) $(TARFILE)
+
+## make rpm/srpm section.
+
+$(SPEC): $(SPEC).in
+       rm -f $@-t $@
+       date="$(shell LC_ALL=C date "+%a %b %d %Y")" && \
+       if [ -f .tarball-version ]; then \
+               gitver="$(shell cat .tarball-version)" && \
+               rpmver=$$gitver && \
+               alphatag="" && \
+               dirty="" && \
+               numcomm=""; \
+       else \
+               gitver="$(shell git describe --abbrev=4 --match='v*' HEAD 2>/dev/null)" && \
+               rpmver=`echo $$gitver | sed -e "s/^v//" -e "s/-.*//g"` && \
+               alphatag=`echo $$gitver | sed -e "s/.*-//" -e "s/^g//"` && \
+               vtag=`echo $$gitver | sed -e "s/-.*//g"` && \
+               numcomm=`git rev-list $$vtag..HEAD | wc -l` && \
+               git update-index --refresh > /dev/null 2>&1 || true && \
+               dirty=`git diff-index --name-only HEAD 2>/dev/null`; \
+       fi && \
+       if [ "$$numcomm" = "0" ]; then numcomm=""; fi && \
+       if [ -n "$$numcomm" ]; then numcomm="%global numcomm $$numcomm"; fi && \
+       if [ "$$alphatag" = "$$gitver" ]; then alphatag=""; fi && \
+       if [ -n "$$alphatag" ]; then alphatag="%global alphatag $$alphatag"; fi && \
+       if [ -n "$$dirty" ]; then dirty="%global dirty dirty"; fi && \
+       sed \
+               -e "s#@version@#$$rpmver#g" \
+               -e "s#@ALPHATAG@#$$alphatag#g" \
+               -e "s#@NUMCOMM@#$$numcomm#g" \
+               -e "s#@DIRTY@#$$dirty#g" \
+               -e "s#@date@#$$date#g" \
+       $< > $@-t; \
+       chmod a-w $@-t
+       mv $@-t $@
+
+$(TARFILE):
+       $(MAKE) dist
+
+RPMBUILDOPTS   = --define "_sourcedir $(abs_builddir)" \
+                 --define "_specdir $(abs_builddir)" \
+                 --define "_builddir $(abs_builddir)" \
+                 --define "_srcrpmdir $(abs_builddir)" \
+                 --define "_rpmdir $(abs_builddir)"
+
+srpm: clean
+       $(MAKE) $(SPEC) $(TARFILE)
+       rpmbuild $(WITH_LIST) $(RPMBUILDOPTS) --nodeps -bs $(SPEC)
+
+rpm: clean _version
+       $(MAKE) $(SPEC) $(TARFILE)
+       rpmbuild $(WITH_LIST) $(RPMBUILDOPTS) -ba $(SPEC)
+
+# release/versioning
+BUILT_SOURCES  = .version
+.version:
+       echo $(VERSION) > $@-t && mv $@-t $@
+
+dist-hook: gen-ChangeLog
+       echo $(VERSION) > $(distdir)/.tarball-version
+
+gen_start_date = 2000-01-01
+.PHONY: gen-ChangeLog _version
+gen-ChangeLog:
+       if test -d .git; then                                           \
+               LC_ALL=C $(top_srcdir)/build-aux/gitlog-to-changelog            \
+                       --since=$(gen_start_date) > $(distdir)/cl-t;    \
+               rm -f $(distdir)/ChangeLog;                             \
+               mv $(distdir)/cl-t $(distdir)/ChangeLog;                \
+       fi
+
+_version:
+       cd $(srcdir) && rm -rf autom4te.cache .version && autoreconf -i
+       $(MAKE) $(AM_MAKEFLAGS) Makefile
+
+maintainer-clean-local:
+       rm -rf m4
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e479343
--- /dev/null
+++ b/README
@@ -0,0 +1,33 @@
+Corosync-qdevice
+----------------
+corosync-qdevice is a daemon running on each node of a cluster. It provides
+a configured number of votes to the quorum subsystem based on a third-party
+arbitrator's decision. Its primary use is to allow a cluster to sustain more
+node failures than standard quorum rules allow. It is recommended for clusters
+with an even number of nodes and highly recommended for 2 node clusters.
+
+corosync-qnetd is a daemon running outside of the cluster with the purpose
+of providing a vote to the corosync-qdevice model net. It's designed to
+support multiple clusters and be almost configuration and state free.
+New clusters are handled dynamically and no configuration file exists.
+It's also able to run as non-root user - which is recommended.
+Connection between the corosync-qdevice model net client can be optionally
+configured with TLS client certificate checking. The communication protocol
+between server and client is designed to be very simple and allow
+backwards compatibility.
+
+Originally both qdevice and qnetd were part of the Corosync codebase
+(https://github.com/corosync/corosync) but because it's got quite big we
+decided to split it into it's own sub project.
+
+Dependencies
+------------
+* Corosync >= 2.0
+* NSS
+
+Installation
+------------
+$ ./autogen.sh
+$ ./configure
+$ make
+$ sudo make install
diff --git a/autogen.sh b/autogen.sh
new file mode 100755 (executable)
index 0000000..35f44b3
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+mkdir -p m4
+echo Building configuration system...
+autoreconf -i && echo Now run ./configure and make
diff --git a/build-aux/git-version-gen b/build-aux/git-version-gen
new file mode 100755 (executable)
index 0000000..795a98b
--- /dev/null
@@ -0,0 +1,161 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2010-10-13.20; # UTC
+
+# Copyright (C) 2007-2010 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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, see <http://www.gnu.org/licenses/>.
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+#   produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+#   presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+#   a checked-out repository.  Created with contents that were learned at
+#   the last time autoconf was run, and used by git-version-gen.  Must not
+#   be present in either $(srcdir) or $(builddir) for git-version-gen to
+#   give accurate answers during normal development with a checked out tree,
+#   but must be present in a tarball when there is no version control system.
+#   Therefore, it cannot be used in any dependencies.  GNUmakefile has
+#   hooks to force a reconfigure at distribution time to get the value
+#   correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+#   tarball.  Usable in dependencies, particularly for files that don't
+#   want to depend on config.h but do want to track version changes.
+#   Delete this file prior to any autoconf run where you want to rebuild
+#   files to pick up a version string change; and leave it stale to
+#   minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+#         m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+#         [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+#      echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+#      echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+    1|2) ;;
+    *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version" \
+         '[TAG-NORMALIZATION-SED-SCRIPT]'
+       exit 1;;
+esac
+
+tarball_version_file=$1
+tag_sed_script="${2:-s/x/x/}"
+nl='
+'
+
+# Avoid meddling by environment variable of the same name.
+v=
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+    v=`cat $tarball_version_file` || exit 1
+    case $v in
+       *$nl*) v= ;; # reject multi-line output
+       [0-9]*) ;;
+       *) v= ;;
+    esac
+    test -z "$v" \
+       && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+    : # use $v
+# Otherwise, if there is at least one git commit involving the working
+# directory, and "git describe" output looks sensible, use that to
+# derive a version string.
+elif test "`git log -1 --pretty=format:x . 2>&1`" = x \
+    && v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+         || git describe --abbrev=4 HEAD 2>/dev/null` \
+    && v=`printf '%s\n' "$v" | sed "$tag_sed_script"` \
+    && case $v in
+        v[0-9]*) ;;
+        *) (exit 1) ;;
+       esac
+then
+    # Is this a new git that lists number of commits since the last
+    # tag or the previous older version that did not?
+    #   Newer: v6.10-77-g0f8faeb
+    #   Older: v6.10-g0f8faeb
+    case $v in
+       *-*-*) : git describe is okay three part flavor ;;
+       *-*)
+           : git describe is older two part flavor
+           # Recreate the number of commits and rewrite such that the
+           # result is the same as if we were using the newer version
+           # of git describe.
+           vtag=`echo "$v" | sed 's/-.*//'`
+           numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+           v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+           ;;
+    esac
+
+    # Change the first '-' to a '.', so version-comparing tools work properly.
+    # Remove the "g" in git describe's output string, to save a byte.
+    v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+else
+    v=UNKNOWN
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git update-index --refresh > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+    '') ;;
+    *) # Append the suffix only if there isn't one already.
+       case $v in
+         *-dirty) ;;
+         *) v="$v-dirty" ;;
+       esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d "$nl"
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/build-aux/gitlog-to-changelog b/build-aux/gitlog-to-changelog
new file mode 100755 (executable)
index 0000000..7660af5
--- /dev/null
@@ -0,0 +1,191 @@
+eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}'
+  & eval 'exec perl -wS "$0" $argv:q'
+    if 0;
+# Convert git log output to ChangeLog format.
+
+my $VERSION = '2009-10-30 13:46'; # UTC
+# The definition above must lie within the first 8 lines in order
+# for the Emacs time-stamp write hook (at end) to update it.
+# If you change this file with Emacs, please let the write hook
+# do its job.  Otherwise, update this string manually.
+
+# Copyright (C) 2008-2010 Free Software Foundation, Inc.
+
+# 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 3 of the License, or
+# (at your option) any later version.
+
+# This program 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, see <http://www.gnu.org/licenses/>.
+
+# Written by Jim Meyering
+
+use strict;
+use warnings;
+use Getopt::Long;
+use POSIX qw(strftime);
+
+(my $ME = $0) =~ s|.*/||;
+
+# use File::Coda; # http://meyering.net/code/Coda/
+END {
+  defined fileno STDOUT or return;
+  close STDOUT and return;
+  warn "$ME: failed to close standard output: $!\n";
+  $? ||= 1;
+}
+
+sub usage ($)
+{
+  my ($exit_code) = @_;
+  my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
+  if ($exit_code != 0)
+    {
+      print $STREAM "Try `$ME --help' for more information.\n";
+    }
+  else
+    {
+      print $STREAM <<EOF;
+Usage: $ME [OPTIONS] [ARGS]
+
+Convert git log output to ChangeLog format.  If present, any ARGS
+are passed to "git log".  To avoid ARGS being parsed as options to
+$ME, they may be preceded by '--'.
+
+OPTIONS:
+
+   --since=DATE convert only the logs since DATE;
+                  the default is to convert all log entries.
+   --format=FMT set format string for commit subject and body;
+                  see 'man git-log' for the list of format metacharacters;
+                  the default is '%s%n%b%n'
+
+   --help       display this help and exit
+   --version    output version information and exit
+
+EXAMPLE:
+
+  $ME --since=2008-01-01 > ChangeLog
+  $ME -- -n 5 foo > last-5-commits-to-branch-foo
+
+EOF
+    }
+  exit $exit_code;
+}
+
+# If the string $S is a well-behaved file name, simply return it.
+# If it contains white space, quotes, etc., quote it, and return the new string.
+sub shell_quote($)
+{
+  my ($s) = @_;
+  if ($s =~ m![^\w+/.,-]!)
+    {
+      # Convert each single quote to '\''
+      $s =~ s/\'/\'\\\'\'/g;
+      # Then single quote the string.
+      $s = "'$s'";
+    }
+  return $s;
+}
+
+sub quoted_cmd(@)
+{
+  return join (' ', map {shell_quote $_} @_);
+}
+
+{
+  my $since_date = '1970-01-01 UTC';
+  my $format_string = '%s%n%b%n';
+  GetOptions
+    (
+     help => sub { usage 0 },
+     version => sub { print "$ME version $VERSION\n"; exit },
+     'since=s' => \$since_date,
+     'format=s' => \$format_string,
+    ) or usage 1;
+
+  my @cmd = (qw (git log --log-size), "--since=$since_date",
+             '--pretty=format:%ct  %an  <%ae>%n%n'.$format_string, @ARGV);
+  open PIPE, '-|', @cmd
+    or die ("$ME: failed to run `". quoted_cmd (@cmd) ."': $!\n"
+            . "(Is your Git too old?  Version 1.5.1 or later is required.)\n");
+
+  my $prev_date_line = '';
+  while (1)
+    {
+      defined (my $in = <PIPE>)
+        or last;
+      $in =~ /^log size (\d+)$/
+        or die "$ME:$.: Invalid line (expected log size):\n$in";
+      my $log_nbytes = $1;
+
+      my $log;
+      my $n_read = read PIPE, $log, $log_nbytes;
+      $n_read == $log_nbytes
+        or die "$ME:$.: unexpected EOF\n";
+
+      my @line = split "\n", $log;
+      my $author_line = shift @line;
+      defined $author_line
+        or die "$ME:$.: unexpected EOF\n";
+      $author_line =~ /^(\d+)  (.*>)$/
+        or die "$ME:$.: Invalid line "
+          . "(expected date/author/email):\n$author_line\n";
+
+      my $date_line = sprintf "%s  $2\n", strftime ("%F", localtime ($1));
+      # If this line would be the same as the previous date/name/email
+      # line, then arrange not to print it.
+      if ($date_line ne $prev_date_line)
+        {
+          $prev_date_line eq ''
+            or print "\n";
+          print $date_line;
+        }
+      $prev_date_line = $date_line;
+
+      # Omit "Signed-off-by..." lines.
+      @line = grep !/^Signed-off-by: .*>$/, @line;
+
+      # If there were any lines
+      if (@line == 0)
+        {
+          warn "$ME: warning: empty commit message:\n  $date_line\n";
+        }
+      else
+        {
+          # Remove leading and trailing blank lines.
+          while ($line[0] =~ /^\s*$/) { shift @line; }
+          while ($line[$#line] =~ /^\s*$/) { pop @line; }
+
+          # Prefix each non-empty line with a TAB.
+          @line = map { length $_ ? "\t$_" : '' } @line;
+
+          print "\n", join ("\n", @line), "\n";
+        }
+
+      defined ($in = <PIPE>)
+        or last;
+      $in ne "\n"
+        and die "$ME:$.: unexpected line:\n$in";
+    }
+
+  close PIPE
+    or die "$ME: error closing pipe from " . quoted_cmd (@cmd) . "\n";
+  # FIXME-someday: include $PROCESS_STATUS in the diagnostic
+}
+
+# Local Variables:
+# mode: perl
+# indent-tabs-mode: nil
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "my $VERSION = '"
+# time-stamp-format: "%:y-%02m-%02d %02H:%02M"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "'; # UTC"
+# End:
diff --git a/build-aux/release.mk b/build-aux/release.mk
new file mode 100644 (file)
index 0000000..739821a
--- /dev/null
@@ -0,0 +1,75 @@
+# to build official release tarballs, handle tagging and publish.
+
+# signing key
+gpgsignkey=
+
+project=corosync-qdevice
+
+all: checks setup tag tarballs sha256 sign
+
+checks:
+ifeq (,$(version))
+       @echo ERROR: need to define version=
+       @exit 1
+endif
+       @if [ ! -d .git ]; then \
+               echo This script needs to be executed from top level cluster git tree; \
+               exit 1; \
+       fi
+
+setup: checks
+       ./autogen.sh
+       ./configure
+       make maintainer-clean
+
+tag: setup ./tag-$(version)
+
+tag-$(version):
+ifeq (,$(release))
+       @echo Building test release $(version), no tagging
+else
+       git tag -a -m "v$(version) release" v$(version) HEAD
+       @touch $@
+endif
+
+tarballs: tag
+       ./autogen.sh
+       ./configure
+       make distcheck
+
+sha256: tarballs $(project)-$(version).sha256
+
+$(project)-$(version).sha256:
+ifeq (,$(release))
+       @echo Building test release $(version), no sha256
+else
+       sha256sum $(project)-$(version)*tar* | sort -k2 > $@
+endif
+
+sign: sha256 $(project)-$(version).sha256.asc
+
+$(project)-$(version).sha256.asc: $(project)-$(version).sha256
+ifeq (,$(gpgsignkey))
+       @echo No GPG signing key defined
+else
+ifeq (,$(release))
+       @echo Building test release $(version), no sign
+else
+       gpg --default-key $(gpgsignkey) \
+               --detach-sign \
+               --armor \
+               $<
+endif
+endif
+
+publish:
+ifeq (,$(release))
+       @echo Building test release $(version), no publishing!
+else
+       @echo CHANGEME git push --tags origin
+       @echo CHANGEME scp $(project)-$(version).* \
+               fedorahosted.org:$(project)
+endif
+
+clean:
+       rm -rf $(project)-* tag-*
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..0b1ff0d
--- /dev/null
@@ -0,0 +1,368 @@
+#                                               -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+# bootstrap / init
+AC_PREREQ([2.61])
+
+AC_INIT([corosync-qdevice],
+       m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+       [users@clusterlabs.org])
+
+AC_USE_SYSTEM_EXTENSIONS
+
+AM_INIT_AUTOMAKE([foreign 1.11])
+
+LT_PREREQ([2.2.6])
+LT_INIT
+
+AM_SILENT_RULES([yes])
+
+AC_CONFIG_SRCDIR([qdevices/corosync-qdevice.c])
+AC_CONFIG_HEADER([config.h])
+AC_CONFIG_MACRO_DIR([m4])
+
+AC_CANONICAL_HOST
+
+AC_LANG([C])
+
+AC_SUBST(WITH_LIST, [""])
+
+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
+
+if test "$srcdir" = "."; then
+       AC_MSG_NOTICE([building in place srcdir:$srcdir])
+       AC_DEFINE([BUILDING_IN_PLACE], 1, [building in place])
+else
+       AC_MSG_NOTICE([building out of tree srcdir:$srcdir])
+fi
+
+# 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_AWK
+AC_PROG_GREP
+AC_PROG_SED
+AC_PROG_CPP
+AC_PROG_CC
+AC_PROG_CC_C99
+if test "x$ac_cv_prog_cc_c99" = "xno"; then
+       AC_MSG_ERROR(["C99 support is required"])
+fi
+AC_PROG_LN_S
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+PKG_PROG_PKG_CONFIG
+AC_PATH_PROG([BASHPATH], [bash])
+AC_CHECK_PROGS([GROFF], [groff])
+
+# Checks for typedefs.
+AC_TYPE_UID_T
+AC_TYPE_INT16_T
+AC_TYPE_INT32_T
+AC_TYPE_INT64_T
+AC_TYPE_INT8_T
+AC_TYPE_UINT16_T
+AC_TYPE_UINT32_T
+AC_TYPE_UINT64_T
+AC_TYPE_UINT8_T
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+
+# Checks for libraries.
+PKG_CHECK_MODULES([nss],[nss])
+PKG_CHECK_MODULES([qb], [libqb])
+PKG_CHECK_MODULES([corosync_common], [libcorosync_common])
+PKG_CHECK_MODULES([cmap], [libcmap])
+PKG_CHECK_MODULES([votequorum], [libvotequorum])
+
+AC_CONFIG_FILES([Makefile
+                qdevices/Makefile
+                man/Makefile
+                init/Makefile
+                ])
+
+# ===============================================
+# Helpers
+# ===============================================
+
+## helper for CC stuff
+cc_supports_flag() {
+       BACKUP="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $@ $unknown_warnings_as_errors"
+       AC_MSG_CHECKING([whether $CC supports "$@"])
+       AC_PREPROC_IFELSE([AC_LANG_PROGRAM([])],
+                         [RC=0; AC_MSG_RESULT([yes])],
+                         [RC=1; AC_MSG_RESULT([no])])
+       CPPFLAGS="$BACKUP"
+       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([secure-build],
+       [  --enable-secure-build           : enable PIE/RELRO build. ],
+       [],
+       [enable_secure_build="yes"])
+
+AC_ARG_ENABLE([systemd],
+             [  --enable-systemd                : Install systemd service files],,
+       [ enable_systemd="no" ])
+AM_CONDITIONAL(INSTALL_SYSTEMD, test x$enable_systemd = xyes)
+
+AC_ARG_WITH([initconfigdir],
+       [AS_HELP_STRING([--with-initconfigdir=DIR],
+               [configuration directory @<:@SYSCONFDIR/sysconfig@:>@])],
+       [INITCONFIGDIR="$withval"],
+       [INITCONFIGDIR='${sysconfdir}/sysconfig'])
+AC_SUBST([INITCONFIGDIR])
+
+AC_ARG_WITH([initddir],
+       [  --with-initddir=DIR     : path to init script directory. ],
+       [ INITDDIR="$withval" ],
+       [ INITDDIR="$sysconfdir/init.d" ])
+
+AC_ARG_WITH([systemddir],
+       [  --with-systemddir=DIR   : path to systemd unit files directory. ],
+       [ SYSTEMDDIR="$withval" ],
+       [ SYSTEMDDIR="/lib/systemd/system" ])
+
+AC_ARG_ENABLE([qdevices],
+       [  --disable-qdevices               : Quorum devices support ],,
+       [ enable_qdevices="yes" ])
+AM_CONDITIONAL(BUILD_QDEVICES, test x$enable_qdevices = xyes)
+AC_ARG_ENABLE([qnetd],
+       [  --disable-qnetd                  : Quorum Net Daemon support ],,
+       [ enable_qnetd="yes" ])
+AM_CONDITIONAL(BUILD_QNETD, test x$enable_qnetd = xyes)
+
+# *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"
+       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
+
+if test "x${enable_systemd}" = xyes; then
+       PKG_CHECK_MODULES([libsystemd], [libsystemd])
+       AC_DEFINE([HAVE_LIBSYSTEMD], [1], [have systemd interface library])
+       PACKAGE_FEATURES="$PACKAGE_FEATURES systemd"
+        WITH_LIST="$WITH_LIST --with systemd"
+fi
+if test "x${enable_qdevices}" = xyes; then
+       PACKAGE_FEATURES="$PACKAGE_FEATURES qdevices"
+fi
+if test "x${enable_qnetd}" = xyes; then
+       PACKAGE_FEATURES="$PACKAGE_FEATURES qnetd"
+fi
+
+# extra warnings
+EXTRA_WARNINGS=""
+
+WARNLIST="
+       all
+       shadow
+       missing-prototypes
+       missing-declarations
+       strict-prototypes
+       declaration-after-statement
+       pointer-arith
+       write-strings
+       cast-align
+       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_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
+
+if test "x${enable_secure_build}" = xyes; then
+  # stolen from apache configure snippet
+  AC_CACHE_CHECK([whether $CC accepts PIE flags], [ap_cv_cc_pie], [
+    save_CFLAGS=$CFLAGS
+    save_LDFLAGS=$LDFLAGS
+    CFLAGS="$CFLAGS -fPIE"
+    LDFLAGS="$LDFLAGS -pie"
+    AC_TRY_RUN([static int foo[30000]; int main () { return 0; }],
+      [ap_cv_cc_pie=yes], [ap_cv_cc_pie=no], [ap_cv_cc_pie=yes])
+    CFLAGS=$save_CFLAGS
+    LDFLAGS=$save_LDFLAGS
+  ])
+  if test "$ap_cv_cc_pie" = "yes"; then
+    SEC_FLAGS="$SEC_FLAGS -fPIE"
+    SEC_LDFLAGS="$SEC_LDFLAGS -pie"
+    PACKAGE_FEATURES="$PACKAGE_FEATURES pie"
+  fi
+
+  # similar to above
+  AC_CACHE_CHECK([whether $CC accepts RELRO flags], [ap_cv_cc_relro], [
+    save_LDFLAGS=$LDFLAGS
+    LDFLAGS="$LDFLAGS -Wl,-z,relro"
+    AC_TRY_RUN([static int foo[30000]; int main () { return 0; }],
+      [ap_cv_cc_relro=yes], [ap_cv_cc_relro=no], [ap_cv_cc_relro=yes])
+    LDFLAGS=$save_LDFLAGS
+  ])
+  if test "$ap_cv_cc_relro" = "yes"; then
+    SEC_LDFLAGS="$SEC_LDFLAGS -Wl,-z,relro"
+    PACKAGE_FEATURES="$PACKAGE_FEATURES relro"
+  fi
+
+  AC_CACHE_CHECK([whether $CC accepts BINDNOW flags], [ap_cv_cc_bindnow], [
+    save_LDFLAGS=$LDFLAGS
+    LDFLAGS="$LDFLAGS -Wl,-z,now"
+    AC_TRY_RUN([static int foo[30000]; int main () { return 0; }],
+      [ap_cv_cc_bindnow=yes], [ap_cv_cc_bindnow=no], [ap_cv_cc_bindnow=yes])
+    LDFLAGS=$save_LDFLAGS
+  ])
+  if test "$ap_cv_cc_bindnow" = "yes"; then
+    SEC_LDFLAGS="$SEC_LDFLAGS -Wl,-z,now"
+    PACKAGE_FEATURES="$PACKAGE_FEATURES bindnow"
+  fi
+fi
+
+AC_CACHE_CHECK([whether $CC accepts "--as-needed"], [ap_cv_cc_as_needed], [
+  save_LDFLAGS=$LDFLAGS
+  LDFLAGS="$LDFLAGS -Wl,--as-needed"
+  AC_TRY_RUN([static int foo[30000]; int main () { return 0; }],
+    [ap_cv_cc_as_needed=yes], [ap_cv_cc_as_needed=no], [ap_cv_cc_as_needed=yes])
+  LDFLAGS=$save_LDFLAGS
+])
+
+# define global include dirs
+INCLUDE_DIRS="$INCLUDE_DIRS -I\$(top_builddir)/include -I\$(top_srcdir)/include"
+
+# final build of *FLAGS
+CFLAGS="$ENV_CFLAGS $lt_prog_compiler_pic $SEC_FLAGS $OPT_CFLAGS $GDB_FLAGS \
+       $EXTRA_WARNINGS \
+       $WERROR_CFLAGS"
+CPPFLAGS="$ENV_CPPFLAGS $INCLUDE_DIRS"
+LDFLAGS="$ENV_LDFLAGS $lt_prog_compiler_pic $SEC_LDFLAGS"
+
+if test "$ap_cv_cc_as_needed" = "yes"; then
+  LDFLAGS="$LDFLAGS -Wl,--as-needed"
+fi
+
+# substitute what we need:
+AC_SUBST([BASHPATH])
+AC_SUBST([INITDDIR])
+AC_SUBST([SYSTEMDDIR])
+AC_SUBST([LOGDIR])
+AC_SUBST([LOGROTATEDIR])
+
+AC_SUBST([SOMAJOR])
+AC_SUBST([SOMINOR])
+AC_SUBST([SOMICRO])
+AC_SUBST([SONAME])
+
+AC_SUBST([NSS_LDFLAGS])
+
+AM_CONDITIONAL(BUILD_HTML_DOCS, test -n "${GROFF}")
+
+AC_DEFINE_UNQUOTED([LOCALSTATEDIR], "$(eval echo ${localstatedir})", [localstate directory])
+
+COROSYSCONFDIR=${sysconfdir}/corosync
+AC_SUBST([COROSYSCONFDIR])
+AC_DEFINE_UNQUOTED([COROSYSCONFDIR], "$(eval echo ${COROSYSCONFDIR})", [corosync-qdevice config directory])
+
+AC_DEFINE_UNQUOTED([PACKAGE_FEATURES], "${PACKAGE_FEATURES}", [corosync-qdevice 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([  System systemd directory = ${SYSTEMDDIR}])
+AC_MSG_RESULT([  Log directory            = ${LOGDIR}])
+AC_MSG_RESULT([  Log rotate directory     = ${LOGROTATEDIR}])
+AC_MSG_RESULT([  corosync config dir      = ${COROSYSCONFDIR}])
+AC_MSG_RESULT([  init config directory    = ${INITCONFIGDIR}])
+AC_MSG_RESULT([  Features                 = ${PACKAGE_FEATURES}])
+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([  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/corosync-qdevice.spec.in b/corosync-qdevice.spec.in
new file mode 100644 (file)
index 0000000..eace00d
--- /dev/null
@@ -0,0 +1,212 @@
+@ALPHATAG@
+@NUMCOMM@
+@DIRTY@
+
+# Conditionals
+# Invoke "rpmbuild --without <feature>" or "rpmbuild --with <feature>"
+# to disable or enable specific features
+%bcond_with runautogen
+%bcond_with systemd
+
+%global gitver %{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}
+%global gittarver %{?numcomm:.%{numcomm}}%{?alphatag:-%{alphatag}}%{?dirty:-%{dirty}}
+
+Name: corosync-qdevice
+Summary: The Corosync Cluster Engine Qdevice
+Version: @version@
+Release: 1%{?gitver}%{?dist}
+License: BSD
+Group: System Environment/Base
+URL: https://github.com/corosync/corosync-qdevice
+Source0: https://github.com/corosync/corosync-qdevice/releases/download/v%{version}%{?gittarver}/%{name}-%{version}%{?gittarver}.tar.gz
+
+# Runtime bits
+Requires: corosync >= 2.4.0
+Requires: corosynclib >= 2.4.0
+Requires: nss-tools
+
+%if %{with systemd}
+Requires(post): systemd
+Requires(preun): systemd
+Requires(postun): systemd
+%else
+Requires(post): /sbin/chkconfig
+Requires(preun): /sbin/chkconfig
+%endif
+
+# Build bits
+BuildRequires: corosynclib-devel
+BuildRequires: groff
+BuildRequires: libqb-devel
+BuildRequires: nss-devel
+BuildRequires: sed
+
+%if %{with runautogen}
+BuildRequires: autoconf automake libtool
+%endif
+%if %{with systemd}
+BuildRequires: systemd-units
+BuildRequires: systemd-devel
+%endif
+
+BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
+
+%prep
+%setup -q -n %{name}-%{version}%{?gittarver}
+
+%build
+%if %{with runautogen}
+./autogen.sh
+%endif
+
+%{configure} \
+%if %{with systemd}
+       --enable-systemd \
+%endif
+       --enable-qdevices \
+       --enable-qnetd \
+       --with-initddir=%{_initrddir} \
+       --with-systemddir=%{_unitdir}
+
+make %{_smp_mflags}
+
+%install
+rm -rf %{buildroot}
+
+make install DESTDIR=%{buildroot}
+
+## tree fixup
+# drop docs and html docs for now
+rm -rf %{buildroot}%{_docdir}/*
+mkdir -p %{buildroot}%{_sysconfdir}/sysconfig
+# /etc/sysconfig/corosync-qdevice
+install -m 644 init/corosync-qdevice.sysconfig.example \
+   %{buildroot}%{_sysconfdir}/sysconfig/corosync-qdevice
+# /etc/sysconfig/corosync-qnetd
+install -m 644 init/corosync-qnetd.sysconfig.example \
+   %{buildroot}%{_sysconfdir}/sysconfig/corosync-qnetd
+
+%if %{with systemd}
+sed -i -e 's/^#User=/User=/' \
+   %{buildroot}%{_unitdir}/corosync-qnetd.service
+%else
+sed -i -e 's/^COROSYNC_QNETD_RUNAS=""$/COROSYNC_QNETD_RUNAS="coroqnetd"/' \
+   %{buildroot}%{_sysconfdir}/sysconfig/corosync-qnetd
+%endif
+
+%clean
+rm -rf %{buildroot}
+
+%description
+This package contains the Corosync Cluster Engine Qdevice, script for creating
+NSS certificates and an init script.
+
+%post
+%if %{with systemd} && 0%{?systemd_post:1}
+%systemd_post corosync-qdevice.service
+%else
+if [ $1 -eq 1 ]; then
+       /sbin/chkconfig --add corosync-qdevice || :
+fi
+%endif
+
+%preun
+%if %{with systemd} && 0%{?systemd_preun:1}
+%systemd_preun corosync-qdevice.service
+%else
+if [ $1 -eq 0 ]; then
+       /sbin/service corosync-qdevice stop &>/dev/null || :
+       /sbin/chkconfig --del corosync-qdevice || :
+fi
+%endif
+
+%postun
+%if %{with systemd} && 0%{?systemd_postun:1}
+%systemd_postun
+%endif
+
+%files
+%defattr(-,root,root,-)
+%dir %{_sysconfdir}/corosync/qdevice
+%dir %config(noreplace) %{_sysconfdir}/corosync/qdevice/net
+%dir %{_localstatedir}/run/corosync-qdevice
+%{_sbindir}/corosync-qdevice
+%{_sbindir}/corosync-qdevice-net-certutil
+%{_sbindir}/corosync-qdevice-tool
+%config(noreplace) %{_sysconfdir}/sysconfig/corosync-qdevice
+%if %{with systemd}
+%{_unitdir}/corosync-qdevice.service
+%else
+%{_initrddir}/corosync-qdevice
+%endif
+%{_mandir}/man8/corosync-qdevice-tool.8*
+%{_mandir}/man8/corosync-qdevice-net-certutil.8*
+%{_mandir}/man8/corosync-qdevice.8*
+
+%package -n corosync-qnetd
+Summary: The Corosync Cluster Engine Qdevice Network Daemon
+Group: System Environment/Base
+Requires: nss-tools
+Requires(pre): shadow-utils
+Requires(pre): /usr/sbin/useradd
+
+%if %{with systemd}
+Requires(post): systemd
+Requires(preun): systemd
+Requires(postun): systemd
+%endif
+
+%description -n corosync-qnetd
+This package contains the Corosync Cluster Engine Qdevice Network Daemon,
+script for creating NSS certificates and an init script.
+
+%pre -n corosync-qnetd
+getent group coroqnetd >/dev/null || groupadd -r coroqnetd
+getent passwd coroqnetd >/dev/null || \
+    useradd -r -g coroqnetd -d / -s /sbin/nologin -c "User for corosync-qnetd" coroqnetd
+exit 0
+
+%post -n corosync-qnetd
+%if %{with systemd} && 0%{?systemd_post:1}
+%systemd_post corosync-qnetd.service
+%else
+if [ $1 -eq 1 ]; then
+       /sbin/chkconfig --add corosync-qnetd || :
+fi
+%endif
+
+%preun -n corosync-qnetd
+%if %{with systemd} && 0%{?systemd_preun:1}
+%systemd_preun corosync-qnetd.service
+%else
+if [ $1 -eq 0 ]; then
+       /sbin/service corosync-qnetd stop &>/dev/null || :
+       /sbin/chkconfig --del corosync-qnetd || :
+fi
+%endif
+
+%postun -n corosync-qnetd
+%if %{with systemd} && 0%{?systemd_postun:1}
+%systemd_postun
+%endif
+
+%files -n corosync-qnetd
+%defattr(-,root,root,-)
+%dir %config(noreplace) %attr(770, coroqnetd, coroqnetd) %{_sysconfdir}/corosync/qnetd
+%dir %attr(770, coroqnetd, coroqnetd) %{_localstatedir}/run/corosync-qnetd
+%{_bindir}/corosync-qnetd
+%{_bindir}/corosync-qnetd-certutil
+%{_bindir}/corosync-qnetd-tool
+%config(noreplace) %{_sysconfdir}/sysconfig/corosync-qnetd
+%if %{with systemd}
+%{_unitdir}/corosync-qnetd.service
+%else
+%{_initrddir}/corosync-qnetd
+%endif
+%{_mandir}/man8/corosync-qnetd-tool.8*
+%{_mandir}/man8/corosync-qnetd-certutil.8*
+%{_mandir}/man8/corosync-qnetd.8*
+
+%changelog
+* @date@ Autotools generated version <nobody@nowhere.org> - @version@-1-@numcomm@.@alphatag@.@dirty@
+- Autotools generated version
diff --git a/init/.gitignore b/init/.gitignore
new file mode 100644 (file)
index 0000000..4bac5c3
--- /dev/null
@@ -0,0 +1,8 @@
+corosync
+corosync-notifyd
+corosync.service
+corosync-notifyd.service
+corosync-qnetd
+corosync-qnetd.service
+corosync-qdevice
+corosync-qdevice.service
diff --git a/init/Makefile.am b/init/Makefile.am
new file mode 100644 (file)
index 0000000..453ade9
--- /dev/null
@@ -0,0 +1,83 @@
+# Copyright (c) 2004 MontaVista Software, Inc.
+# Copyright (c) 2009 - 2018 Red Hat, Inc.
+#
+# Authors: Jan Friesse (jfriesse@redhat.com)
+#          Steven Dake (sdake@redhat.com)
+#          Fabio M. Di Nitto (fdinitto@redhat.com)
+#
+# All rights reserved.
+#
+# This software licensed under BSD license, the text of which follows:
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+MAINTAINERCLEANFILES   = Makefile.in
+
+EXTRA_DIST             = corosync-qdevice.sysconfig.example corosync-qdevice.in \
+                          corosync-qdevice.service.in \
+                          corosync-qnetd.sysconfig.example corosync-qnetd.in \
+                          corosync-qnetd.service.in
+
+if INSTALL_SYSTEMD
+systemdconfdir     = $(SYSTEMDDIR)
+systemdconf_DATA       =
+else
+initscriptdir      = $(INITDDIR)
+initscript_SCRIPTS     =
+endif
+
+if BUILD_QDEVICES
+if INSTALL_SYSTEMD
+systemdconf_DATA   += corosync-qdevice.service
+else
+initscript_SCRIPTS  += corosync-qdevice
+endif
+endif
+
+if BUILD_QNETD
+if INSTALL_SYSTEMD
+systemdconf_DATA   += corosync-qnetd.service
+else
+initscript_SCRIPTS  += corosync-qnetd
+endif
+endif
+
+%: %.in Makefile
+       rm -f $@-t $@
+       cat $< | sed \
+               -e 's#@''SBINDIR@#$(sbindir)#g' \
+               -e 's#@''BINDIR@#$(bindir)#g' \
+               -e 's#@''SYSCONFDIR@#$(sysconfdir)#g' \
+               -e 's#@''INITCONFIGDIR@#$(INITCONFIGDIR)#g' \
+               -e 's#@''INITDDIR@#$(INITDDIR)#g' \
+               -e 's#@''LOCALSTATEDIR@#$(localstatedir)#g' \
+               -e 's#@''BASHPATH@#${BASHPATH}#g' \
+           > $@-t
+       mv $@-t $@
+
+all-local: $(initscript_SCRIPTS) $(systemdconf_DATA) $(upstartconf_DATA)
+
+clean-local:
+       rm -rf $(initscript_SCRIPTS) $(systemdconf_DATA) $(upstartconf_DATA)
diff --git a/init/corosync-qdevice.in b/init/corosync-qdevice.in
new file mode 100755 (executable)
index 0000000..581b6e6
--- /dev/null
@@ -0,0 +1,164 @@
+#!@BASHPATH@
+
+# Authors:
+#  Jan Friesse <jfriesse@redhat.com>
+#
+# License: Revised BSD
+
+# chkconfig: - 20 80
+# description: Corosync Qdevice daemon
+# processname: corosync-qdevice
+#
+### BEGIN INIT INFO
+# Provides:            corosync-qdevice
+# Required-Start:      corosync
+# Required-Stop:       corosync
+# Default-Start:
+# Default-Stop:
+# Short-Description:   Starts and stops Corosync Qdevice daemon.
+# Description:         Starts and stops Corosync Qdevice daemon.
+### END INIT INFO
+
+desc="Corosync Qdevice daemon"
+prog="corosync-qdevice"
+
+# set secure PATH
+PATH="/sbin:/bin:/usr/sbin:/usr/bin:@SBINDIR@"
+
+success()
+{
+       echo -ne "[  OK  ]\r"
+}
+
+failure()
+{
+       echo -ne "[FAILED]\r"
+}
+
+status()
+{
+       pid=$(pidof $1 2>/dev/null)
+       res=$?
+       if [ $res -ne 0 ]; then
+               echo "$1 is stopped"
+       else
+               echo "$1 (pid $pid) is running..."
+       fi
+       return $res
+}
+
+[ -f @INITCONFIGDIR@/$prog ] && . @INITCONFIGDIR@/$prog
+
+case '@INITCONFIGDIR@' in
+    */sysconfig) # rpm based distros
+       [ -f @INITDDIR@/functions ] && . @INITDDIR@/functions
+       [ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/subsys/$prog";;
+    */default) # deb based distros
+       [ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/$prog";;
+esac
+
+# The version of __pids_pidof in /etc/init.d/functions calls pidof with -x
+# This means it matches scripts, including this one.
+# Redefine it here so that status (from the same file) works.
+# Otherwise simultaneous calls to stop() will loop forever
+__pids_pidof() {
+        pidof -c -o $$ -o $PPID -o %PPID "$1" || \
+                pidof -c -o $$ -o $PPID -o %PPID "${1##*/}"
+}
+
+cluster_disabled_at_boot()
+{
+       if grep -q nocluster /proc/cmdline && \
+          [ "$(tty)" = "/dev/console" ]; then
+               echo -e "not configured to run at boot"
+               failure
+               return 1
+       fi
+       return 0
+}
+
+start()
+{
+       echo -n "Starting $desc ($prog): "
+
+       ! cluster_disabled_at_boot && return
+
+       # most recent distributions use tmpfs for @LOCALSTATEDIR@/run
+       # to avoid to clean it up on every boot.
+       # they also assume that init scripts will create
+       # required subdirectories for proper operations
+       if [ ! -d "@LOCALSTATEDIR@/run/corosync-qdevice" ];then
+               mkdir -p "@LOCALSTATEDIR@/run/corosync-qdevice"
+               chmod 0770 "@LOCALSTATEDIR@/run/corosync-qdevice"
+       fi
+
+       if status $prog > /dev/null 2>&1; then
+               success
+       else
+               $prog $COROSYNC_QDEVICE_OPTIONS > /dev/null 2>&1
+
+               if [ "$?" != 0 ]; then
+                       failure
+                       rtrn=1
+               else
+                       touch $LOCK_FILE
+                       success
+               fi
+       fi
+       echo
+}
+
+stop()
+{
+       ! status $prog > /dev/null 2>&1 && return
+
+       echo -n "Signaling $desc ($prog) to terminate: "
+       kill -TERM $(pidof $prog) > /dev/null 2>&1
+       success
+       echo
+
+       echo -n "Waiting for $prog services to unload:"
+       while status $prog > /dev/null 2>&1; do
+               sleep 1
+               echo -n "."
+       done
+
+       rm -f $LOCK_FILE
+       success
+       echo
+}
+
+restart()
+{
+       stop
+       start
+}
+
+rtrn=0
+
+case "$1" in
+start)
+       start
+;;
+restart|reload|force-reload)
+       restart
+;;
+condrestart|try-restart)
+       if status $prog > /dev/null 2>&1; then
+               restart
+       fi
+;;
+status)
+       status $prog
+       rtrn=$?
+;;
+stop)
+       stop
+;;
+*)
+       echo "usage: $0 {start|stop|restart|reload|force-reload|condrestart|try-restart|status}"
+       rtrn=2
+;;
+esac
+
+exit $rtrn
diff --git a/init/corosync-qdevice.service.in b/init/corosync-qdevice.service.in
new file mode 100644 (file)
index 0000000..18bc60b
--- /dev/null
@@ -0,0 +1,17 @@
+[Unit]
+Description=Corosync Qdevice daemon
+Documentation=man:corosync-qdevice
+ConditionKernelCommandLine=!nocluster
+Requires=corosync.service
+After=corosync.service
+
+[Service]
+EnvironmentFile=-@INITCONFIGDIR@/corosync-qdevice
+ExecStart=@SBINDIR@/corosync-qdevice -f $COROSYNC_QDEVICE_OPTIONS
+Type=notify
+Restart=on-abnormal
+RuntimeDirectory=corosync-qdevice
+RuntimeDirectoryMode=0770
+
+[Install]
+WantedBy=multi-user.target
diff --git a/init/corosync-qdevice.sysconfig.example b/init/corosync-qdevice.sysconfig.example
new file mode 100644 (file)
index 0000000..8cac818
--- /dev/null
@@ -0,0 +1,6 @@
+# Corosync Qdevice daemon init script configuration file
+
+# COROSYNC_QDEVICE_OPTIONS specifies options passed to corosync-qdevice command
+# (default is no options).
+# See "man corosync-qdevice" for detailed descriptions of the options.
+COROSYNC_QDEVICE_OPTIONS=""
diff --git a/init/corosync-qnetd.in b/init/corosync-qnetd.in
new file mode 100755 (executable)
index 0000000..681ea08
--- /dev/null
@@ -0,0 +1,171 @@
+#!@BASHPATH@
+
+# Authors:
+#  Jan Friesse <jfriesse@redhat.com>
+#
+# License: Revised BSD
+
+# chkconfig: - 20 80
+# description: Corosync Qdevice Network daemon
+# processname: corosync-qnetd
+#
+### BEGIN INIT INFO
+# Provides:            corosync-qnetd
+# Required-Start:      $network $syslog
+# Required-Stop:       $network $syslog
+# Default-Start:
+# Default-Stop:
+# Short-Description:   Starts and stops Corosync Qdevice Network daemon.
+# Description:         Starts and stops Corosync Qdevice Network daemon.
+### END INIT INFO
+
+desc="Corosync Qdevice Network daemon"
+prog="corosync-qnetd"
+
+# set secure PATH
+PATH="/sbin:/bin:/usr/sbin:/usr/bin:@SBINDIR@"
+
+success()
+{
+       echo -ne "[  OK  ]\r"
+}
+
+failure()
+{
+       echo -ne "[FAILED]\r"
+}
+
+status()
+{
+       pid=$(pidof $1 2>/dev/null)
+       res=$?
+       if [ $res -ne 0 ]; then
+               echo "$1 is stopped"
+       else
+               echo "$1 (pid $pid) is running..."
+       fi
+       return $res
+}
+
+[ -f @INITCONFIGDIR@/$prog ] && . @INITCONFIGDIR@/$prog
+
+case '@INITCONFIGDIR@' in
+    */sysconfig) # rpm based distros
+       [ -f @INITDDIR@/functions ] && . @INITDDIR@/functions
+       [ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/subsys/$prog";;
+    */default) # deb based distros
+       [ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/$prog";;
+esac
+
+# The version of __pids_pidof in /etc/init.d/functions calls pidof with -x
+# This means it matches scripts, including this one.
+# Redefine it here so that status (from the same file) works.
+# Otherwise simultaneous calls to stop() will loop forever
+__pids_pidof() {
+        pidof -c -o $$ -o $PPID -o %PPID "$1" || \
+                pidof -c -o $$ -o $PPID -o %PPID "${1##*/}"
+}
+
+cluster_disabled_at_boot()
+{
+       if grep -q nocluster /proc/cmdline && \
+          [ "$(tty)" = "/dev/console" ]; then
+               echo -e "not configured to run at boot"
+               failure
+               return 1
+       fi
+       return 0
+}
+
+start()
+{
+       echo -n "Starting $desc ($prog): "
+
+       ! cluster_disabled_at_boot && return
+
+       # most recent distributions use tmpfs for @LOCALSTATEDIR@/run
+       # to avoid to clean it up on every boot.
+       # they also assume that init scripts will create
+       # required subdirectories for proper operations
+       if [ ! -d "@LOCALSTATEDIR@/run/corosync-qnetd" ];then
+               mkdir -p "@LOCALSTATEDIR@/run/corosync-qnetd"
+               chmod 0770 "@LOCALSTATEDIR@/run/corosync-qnetd"
+               if [ ! -z "$COROSYNC_QNETD_RUNAS" ];then
+                       chown "$COROSYNC_QNETD_RUNAS:$COROSYNC_QNETD_RUNAS" "@LOCALSTATEDIR@/run/corosync-qnetd"
+               fi
+       fi
+
+       if status $prog > /dev/null 2>&1; then
+               success
+       else
+               if [ -z "$COROSYNC_QNETD_RUNAS" ];then
+                       $prog $COROSYNC_QNETD_OPTIONS > /dev/null 2>&1
+               else
+                       runuser -s @BASHPATH@ $COROSYNC_QNETD_RUNAS -c "$prog $COROSYNC_QNETD_OPTIONS > /dev/null 2>&1"
+               fi
+
+               if [ "$?" != 0 ]; then
+                       failure
+                       rtrn=1
+               else
+                       touch $LOCK_FILE
+                       success
+               fi
+       fi
+       echo
+}
+
+stop()
+{
+       ! status $prog > /dev/null 2>&1 && return
+
+       echo -n "Signaling $desc ($prog) to terminate: "
+       kill -TERM $(pidof $prog) > /dev/null 2>&1
+       success
+       echo
+
+       echo -n "Waiting for $prog services to unload:"
+       while status $prog > /dev/null 2>&1; do
+               sleep 1
+               echo -n "."
+       done
+
+       rm -f $LOCK_FILE
+       success
+       echo
+}
+
+restart()
+{
+       stop
+       start
+}
+
+rtrn=0
+
+case "$1" in
+start)
+       start
+;;
+restart|reload|force-reload)
+       restart
+;;
+condrestart|try-restart)
+       if status $prog > /dev/null 2>&1; then
+               restart
+       fi
+;;
+status)
+       status $prog
+       rtrn=$?
+;;
+stop)
+       stop
+;;
+*)
+       echo "usage: $0 {start|stop|restart|reload|force-reload|condrestart|try-restart|status}"
+       rtrn=2
+;;
+esac
+
+exit $rtrn
diff --git a/init/corosync-qnetd.service.in b/init/corosync-qnetd.service.in
new file mode 100644 (file)
index 0000000..283edff
--- /dev/null
@@ -0,0 +1,19 @@
+[Unit]
+Description=Corosync Qdevice Network daemon
+Documentation=man:corosync-qnetd
+ConditionKernelCommandLine=!nocluster
+Requires=network-online.target
+After=network-online.target
+
+[Service]
+EnvironmentFile=-@INITCONFIGDIR@/corosync-qnetd
+ExecStart=@BINDIR@/corosync-qnetd -f $COROSYNC_QNETD_OPTIONS
+Type=notify
+Restart=on-abnormal
+# Uncomment and set user who should be used for executing qnetd
+#User=coroqnetd
+RuntimeDirectory=corosync-qnetd
+RuntimeDirectoryMode=0770
+
+[Install]
+WantedBy=multi-user.target
diff --git a/init/corosync-qnetd.sysconfig.example b/init/corosync-qnetd.sysconfig.example
new file mode 100644 (file)
index 0000000..8d37535
--- /dev/null
@@ -0,0 +1,13 @@
+# Corosync Qdevice Network daemon init script configuration file
+
+# COROSYNC_QNETD_OPTIONS specifies options passed to corosync-qnetd command
+# (default is no options).
+# See "man corosync-qnetd" for detailed descriptions of the options.
+COROSYNC_QNETD_OPTIONS=""
+
+# COROSYNC_QNETD_RUNAS specifies user under which qnetd daemon should be running
+# (not set or empty is default and means "user who executes init script")
+# Make sure to set correct owner of directories /etc/corosync/qnetd and
+# /var/run/corosync-qnetd
+# This has no effect if systemd unit is used (you have to change unit file)
+COROSYNC_QNETD_RUNAS=""
diff --git a/man/.gitignore b/man/.gitignore
new file mode 100644 (file)
index 0000000..0315fcd
--- /dev/null
@@ -0,0 +1,2 @@
+*.html
+*.3
diff --git a/man/Makefile.am b/man/Makefile.am
new file mode 100644 (file)
index 0000000..6b959e1
--- /dev/null
@@ -0,0 +1,92 @@
+# Copyright (c) 2004 MontaVista Software, Inc.
+# Copyright (c) 2009 - 2018 Red Hat, Inc.
+#
+# Authors: Jan Friesse (jfriesse@redhat.com)
+#          Steven Dake (sdake@redhat.com)
+#          Fabio M. Di Nitto (fdinitto@redhat.com)
+#
+# All rights reserved.
+#
+# This software licensed under BSD license, the text of which follows:
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+MAINTAINERCLEANFILES   = Makefile.in
+
+qnetd_man              = corosync-qnetd-tool.8 \
+                          corosync-qnetd-certutil.8 \
+                          corosync-qnetd.8
+
+qdevices_man           = corosync-qdevice-tool.8 \
+                          corosync-qdevice-net-certutil.8 \
+                          corosync-qdevice.8
+
+EXTRA_DIST             = $(qnetd_man) \
+                         $(qdevices_man)
+
+dist_man_MANS          =
+
+if BUILD_QNETD
+dist_man_MANS          += $(qnetd_man)
+endif
+
+if BUILD_QDEVICES
+dist_man_MANS          += $(qdevices_man)
+endif
+
+HTML_DOCS              = $(dist_man_MANS:%=%.html) $(man_MANS:%=%.html)
+
+# developer man page generation
+%.3: %.3.in $(autogen_common)
+       @echo Generating $@ man page && \
+       rm -f $@-t-t $@-t $@ && \
+       date="$$(LC_ALL=C date "+%F" $${SOURCE_DATE_EPOCH+-d @$$SOURCE_DATE_EPOCH})" && \
+       awk "{print}(\$$1 ~ /@COMMONIPCERRORS@/){exit 0}" ${top_srcdir}/man/$@.in > $@-t-t && \
+       cat ${top_srcdir}/man/$(autogen_common) >> $@-t-t && \
+       awk -v p=0 "(\$$1 ~ /@COMMONIPCERRORS@/){p = 1} {if(p==1)print}" ${top_srcdir}/man/$@.in >> $@-t-t && \
+       cat $@-t-t | \
+               sed -e 's#@BUILDDATE@#'$$date'#g' \
+                   -e 's#@COMMONIPCERRORS@##g' \
+                   > $@-t && \
+       rm -f $@-t-t && \
+       mv $@-t $@
+
+clean-local:
+       rm -rf $(HTML_DOCS) $(autogen_man)
+
+if BUILD_HTML_DOCS
+%.html: %
+       $(GROFF) -mandoc -Thtml $^ > $@
+
+install-data-local:
+       $(INSTALL) -d $(DESTDIR)/${docdir}/html
+       $(INSTALL) -m 644 $(HTML_DOCS) $(DESTDIR)/${docdir}/html/
+
+uninstall-local:
+       cd $(DESTDIR)/${docdir}/html && rm -f $(HTML_DOCS)
+       rmdir $(DESTDIR)/${docdir}/html 2> /dev/null || :
+
+all-local: $(HTML_DOCS)
+endif
diff --git a/man/corosync-qdevice-net-certutil.8 b/man/corosync-qdevice-net-certutil.8
new file mode 100644 (file)
index 0000000..e689945
--- /dev/null
@@ -0,0 +1,85 @@
+.\"/*
+.\" * Copyright (C) 2016 Red Hat, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Jan Friesse <jfriesse@redhat.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of Red Hat, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH COROSYNC-QDEVICE-NET-CERTUTIL 8 2016-06-28
+.SH NAME
+corosync-qdevice-net-certutil - tool to generate qdevice model net TLS certificates
+.SH SYNOPSIS
+.B "corosync-qdevice-net-certutil [-i|-m|-M|-r|-s|-Q] [-c certificate] [-n cluster_name]"
+.SH DESCRIPTION
+.B corosync-qdevice-net-certutil
+is a frontend for NSS certutil used for generating client certificate for the net model of
+qdevice.
+.SH OPTIONS
+.TP
+.B -i
+Initialize the QDevice Net NSS certificate database.
+The default directory for the database is /etc/corosync/qdevice/net/. This directory
+has to be writable by the current user. It needs the QNetd CA certificate passed as the
+.B -c
+parameter. This certificate can be found on the server running QNetd in the file
+/etc/corosync/qnetd/nssdb/qnetd-cacert.crt.
+.TP
+.B -m
+Import the cluster certificate and key from a pk12 file.
+.TP
+.B -r
+Generate a certificate request. The certificate request is exported into
+/etc/corosync/qdevice/net/qdevice-net-node.crq. It is necessary to
+pass the cluster name using the
+.B -n
+parameter. The cluster name has to match the one defined in /etc/corosync/corosync.conf.
+.TP
+.B -M
+Import a signed certificate and export a certificate with private key into
+pk12 file.
+.TP
+.B -Q
+Use ssh/scp to properly set both
+.B corosync-qnetd
+and
+.B corosync-qdevice
+certificates on all nodes. It's highly recommended that you use an ssh agent,
+or ssh/scp will keep asking for a password - roughly 8 times the number of nodes.
+.TP
+.B -c
+File with certificate to load.
+.TP
+.B -n
+Name of the cluster.
+.SH SEE ALSO
+.BR corosync-qnetd (8)
+.BR corosync-qdevice (8)
+.SH AUTHOR
+Jan Friesse
+.PP
diff --git a/man/corosync-qdevice-tool.8 b/man/corosync-qdevice-tool.8
new file mode 100644 (file)
index 0000000..ac71e59
--- /dev/null
@@ -0,0 +1,130 @@
+.\"/*
+.\" * Copyright (C) 2016-2017 Red Hat, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Jan Friesse <jfriesse@redhat.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of Red Hat, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH COROSYNC-QDEVICE-TOOL 8 2017-10-17
+.SH NAME
+corosync-qdevice-tool \- corosync-qdevice control interface.
+.SH SYNOPSIS
+.B "corosync-qdevice-tool [-Hhsv] [-p qdevice_ipc_socket_path]"
+.SH DESCRIPTION
+.B corosync-qdevice-tool
+is a frontend to the internal corosync-qdevice IPC. Its main purpose is to show important
+information about the current internal state of
+.B corosync-qdevice.
+.SH OPTIONS
+.TP
+.B -H
+Properly shutdown the
+.B corosync-qdevice
+process
+.TP
+.B -h
+Display a short usage text
+.TP
+.B -s
+Display the status of the
+.B corosync-qdevice
+process. The output is described in its own section below.
+.TP
+.B -v
+Display more verbose output for the
+.B -s
+option.
+.TP
+.B -p
+Path to the
+.B corosync-qdevice
+communication socket.
+
+.SH STATUS COMMAND OUTPUT
+.nf
+Qdevice information
+-------------------
+Model:                  Net
+Node ID:                1
+HB interval:            10000ms
+Sync HB interval:       30000ms
+Configured node list:
+    0   Node ID = 1
+Heuristics:             Enabled
+Ring ID:                1.a00000000021b48
+Membership node list:   1
+Quorate:                Yes
+Quorum node list:
+    0   Node ID = 1, State = member
+Expected votes:         2
+Last poll call:         2016-06-24T17:05:20 (cast vote)
+
+Qdevice-net information
+----------------------
+Cluster name:           Cluster
+QNetd host:             localhost:5403
+Connect timeout:        8000ms
+HB interval:            8000ms
+VQ vote timer interval: 5000ms
+TLS:                    Supported
+Algorithm:              Fifty-Fifty split
+Tie-breaker:            Node with lowest node ID
+Poll timer running:     Yes (cast vote)
+State:                  Connected
+Heuristics result:      Pass (regular: Pass, membership: Fail, connect: Fail)
+TLS active:             Yes (client certificate sent)
+Connected since:        2016-06-24T17:02:35
+Echo reply received:    2016-06-24T17:05:15
+.fi
+
+The output is split into a generic qdevice section and a model specific section.
+Most of the items are just taken from corosync.conf file. It's helpful to note that the
+.I Membership node list
+is the membership list of the current node and should match the quorum node list.
+.I Last poll call
+is the timestamp (in iso format) of the last call to the votequorum_qdevice_poll
+function.
+
+For model net, it's good to check the
+.I Poll timer running
+state. Internally, model net supports 3 states. Not voting (when
+.I Poll timer running
+is No, which means
+.B corosync-qdevice
+is waiting for
+.B corosync-qnetd
+to reply), voting (without cast vote, it means that the
+.B corosync-qnetd
+algorithm decides that the current node shouldn't get a vote) and voting (with cast vote).
+.SH SEE ALSO
+.BR corosync-qnetd (8)
+.BR corosync-qdevice (8)
+.SH AUTHOR
+Jan Friesse
+.PP
diff --git a/man/corosync-qdevice.8 b/man/corosync-qdevice.8
new file mode 100644 (file)
index 0000000..1d86da9
--- /dev/null
@@ -0,0 +1,461 @@
+.\"/*
+.\" * Copyright (C) 2016-2017 Red Hat, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Jan Friesse <jfriesse@redhat.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of Red Hat, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH COROSYNC-QDEVICE 8 2017-10-17
+.SH NAME
+corosync-qdevice \- QDevice daemon
+.SH SYNOPSIS
+.B "corosync-qdevice [-dfh] [-S option=value[,option2=value2,...]]"
+
+.SH DESCRIPTION
+.B corosync-qdevice
+is a daemon running on each node of a cluster. It provides a configured
+number of votes to the
+quorum subsystem based on a third-party arbitrator's decision. Its primary use
+is to allow a cluster to sustain more node failures than standard quorum rules allow. 
+It is recommended for clusters with an even number of nodes and highly recommended 
+for 2 node clusters.
+.SH OPTIONS
+.TP
+.B -d
+Forcefully turn on debug information without the need to change corosync.conf.
+.TP
+.B -f
+Do not daemonize, run in the foreground.
+.TP
+.B -h
+Show short help text
+.TP
+.B -S
+Set advanced settings described in its own section below. This option
+shouldn't be generally used because most of the options are
+not safe to change.
+.SH CONFIGURATION
+.B corosync-qdevice
+reads its configuration from corosync.conf file.
+
+The main configuration is within
+.B quorum.device
+sub-key. Each model also has its own configuration within a
+similarly named sub-key.
+.TP
+.B model
+Specifies the model to be used. This parameter is required.
+.B corosync-qdevice
+is modular and is able to support multiple different models. The model basically
+defines what type of arbitrator is used. Currently only
+.I net
+is supported.
+.TP
+.B timeout
+Specifies how often
+.B corosync-qdevice
+should call the votequorum_poll function. It is also used by the
+.I net
+model to adjust
+its hearbeat timeout. It is recommended that you don't change this value.
+Default is
+.IR 10000 .
+.TP
+.B sync_timeout
+Specifies how often
+.B corosync-qdevice
+should call the votequorum_poll function during a sync phase. It is recommended that you don't change this value.
+Default is
+.IR 30000 .
+.TP
+.B votes
+The number of votes provided to the cluster by qdevice. Default is (number_of_nodes - 1) or generally
+sum(votes_per_node) - 1.
+
+.PP
+.B quorum.device.heuristics
+subkey holds the configuration of the heuristics. Heuristics are set of commands executed locally on
+startup, cluster membership change, successful connect to
+.B corosync-qnetd
+and optionally also at regular times. Commands are executed in parallel.
+When all commands finish successfully
+(their return error code is zero) on time,
+heuristics have passed, otherwise they have failed. The heuristics result is sent to
+.B corosync-qnetd
+and there it's used in calculations to determine which partition should be quorate.
+.TP
+.B timeout
+Specifies maximum time in milliseconds how long
+.B corosync-qdevice
+waits till the heuristics commands finish. If some command doesn't finish before the timeout, it's
+killed and heuristics fail. This timeout is used for heuristics executed at regular times.
+Default value is half of the
+.BR quorum.device.timeout ", so"
+.IR 5000 .
+.TP
+.B sync_timeout
+Similar to quorum.device.heuristics.timeout but used during membership changes. Default
+value is half of the
+.BR quorum.device.sync_timeout ", so"
+.IR 15000 .
+.TP
+.B interval
+Specifies interval between two regular heuristics execution. Default value is
+3 *
+.BR quorum.device.timeout ", so"
+.IR 30000 .
+.TP
+.B mode
+Can be one of
+.IR on ", " sync " or " off
+and specifies mode of operation of heuristics. Default is
+.IR off ,
+which means heuristics are disabled. When
+.I sync
+is set, heuristics are executed only during startup, membership change and when connection
+to
+.B corosync-qnetd
+is established. When heuristics should be running also on regular basis, this option
+should be set to
+.I on
+value.
+.TP
+.B exec_NAME
+defines executables.
+.I NAME
+can be arbitrary valid cmap key name string and it has no special meaning.
+The value of this variable must contain a command to execute. The value is parsed (split)
+into arguments similarly as Bourne shell would do. Quoting is possible by
+using backslash and double quotes.
+
+.PP
+.B quorum.device.net
+subkey holds the configuration for
+.B model
+.IR net .
+.TP
+.B tls
+Can be one of
+.IR on ", " off " or " required
+and specifies if tls should be used.
+.I on
+means a connection with TLS is attempted first, but if the server doesn't advertise TLS support 
+then non-TLS will be used.
+.I off
+is used then TLS is not required and it's then not even tried. This mode is the
+only one which doesn't need a properly initialized NSS database.
+.I required
+means TLS is required and if the server doesn't support TLS, qdevice will
+exit with error message. Default is
+.IR on .
+.TP
+.B host
+Specifies the IP address or host name of the qnetd server to be used. This parameter
+is required.
+.TP
+.B port
+Specifies TCP port of qnetd server. Default is
+.IR 5403 .
+.TP
+.B algorithm
+Decision algorithm. Can be one of the
+.I ffsplit
+or
+.IR lms .
+(actually there are also
+.I test
+and
+.IR 2nodelms ,
+both of which are mainly for developers and shouldn't be used for production clusters).
+For a description of what each algorithm means and how the algorithms differ see their
+individual sections.
+Default value is
+.IR ffsplit .
+.TP
+.B tie_breaker
+can be one of
+.IR lowest ", " highest
+or valid_node_id (number) values. It's used as a fallback if qdevice has to decide between two or more
+equal partitions.
+.I lowest
+means the partition with the lowest node id is chosen.
+.I highest
+means the partition with highest node id is chosen. And valid_node_id means that the partition
+containing the node with the given node id is chosen.
+Default is
+.IR lowest .
+.TP
+.B connect_timeout
+Timeout when
+.B corosync-qdevice
+is trying to connect to
+.B corosync-qnetd
+host. Default is 0.8 *
+.BR quorum.sync_timeout .
+.TP
+.B force_ip_version
+can be one of
+.I 0|4|6
+and forces the software to use the given IP version.
+.I 0
+(default value) means IPv6 is preferred and IPv4 should be used as a fallback.
+
+.PP
+Logging configuration is within the
+.B logging
+directive.
+.B corosync-qdevice
+parses and supports most of the options with exception of
+.BR to_logfile ", " logfile
+and
+.BR logfile_priority .
+The 
+.B logger_subsys
+sub-directive can be also used if
+.B subsys
+is set to
+.IR QDEVICE .
+
+.PP
+For
+.B corosync-qdevice
+to work correctly, the
+.B nodelist
+directive has to be used and properly configured. Also the
+.I net
+model requires that
+.B totem.cluster_name
+option is set.
+
+.SH MODEL NET TLS CONFIGURATION
+For
+.B model
+.I net
+to work using TLS, it's necessary to create the NSS database, import Qnetd
+CA certificate, and get/distribute a valid client certificate.
+
+If pcs is used (recommended) the following steps are not needed because pcs does them automatically.
+
+.B corosync-qdevice-net-certutil
+is the tool to perform required actions semi-automatically. Please consult the help output of
+it and its man page. For a first time configuration it may make sense to start with the
+.B -Q
+option.
+
+If TLS is not required just edit corosync.conf file and set
+.B quorum.device.net.tls
+to
+.IR off .
+
+.SH MODEL NET ALGORITHMS
+Algorithms are used to change behavior of how
+.B corosync-qnetd
+provides votes to a given node/partition. Currently there are two algorithms supported.
+.TP
+.B ffsplit
+This one makes sense only for clusters with an even number of nodes. It provides exactly one
+vote to the partition with the highest number of active nodes. If there are two exactly
+similar partitions,
+it provides its vote to the partition with higher score. The score is computed
+as (number_of_connected_nodes +
+number_of_connected_nodes_with_passed_heuristics - number_of_connected_nodes_with_failed_heuristics)
+If the scores are equal, the vote is provided to partition with the most clients connected to the qnetd
+server. If this number is also equal, then the tie_breaker is used. It is able to transition
+its vote if the currently active partition becomes partitioned and a non-active partition
+still has at least 50% of the active nodes. Because of this, a vote is not provided
+if the qnetd connection is not active.
+
+To use this algorithm it's required to set the number of votes per node to 1 (default)
+and the qdevice number of votes has to be also 1. This is achieved by setting
+.B quorum.device.votes
+key in corosync.conf file to 1.
+.TP
+.B lms
+Last-man-standing. If the node is the only one left in the cluster that can see the
+qnetd server then we return a vote.
+
+If more than one node can see the qnetd server but some nodes can't
+see each other then the cluster is divided up into 'partitions' based on
+their ring_id and this algorithm returns a vote to the partition with highest
+heuristics score (computed the same way as for the
+.B ffsplit
+algorithm), or if there is more than 1 partition with equal scores,
+the largest active partition or,
+if there is more than 1 equal partition, the partition that contains the tie_breaker
+node (lowest, highest, etc). For LMS to work, the number
+of qdevice votes has to be set to default (so just delete
+.B quorum.device.votes
+key from corosync.conf).
+
+.SH ADVANCED SETTINGS
+Set by using
+.B -S
+option. The default value is shown in parentheses)  Options
+beginning with
+.B net_
+prefix are specific to
+.B model
+.IR net .
+.TP
+.B lock_file
+Lock file location. (/var/run/corosync-qdevice/corosync-qdevice.pid)
+.TP
+.B local_socket_file
+Internal IPC socket file location. (/var/run/corosync-qdevice/corosync-qdevice.sock)
+.TP
+.B local_socket_backlog
+Parameter passed to listen syscall. (10)
+.TP
+.B max_cs_try_again
+How many times to retry the call to a corosync function which has returned CS_ERR_TRY_AGAIN. (10)
+.TP
+.B votequorum_device_name
+Name used for qdevice registration. (Qdevice)
+.TP
+.B ipc_max_clients
+Maximum allowed simultaneous IPC clients. (10)
+.TP
+.B ipc_max_receive_size
+Maximum size of a message received by IPC client. (4096)
+.TP
+.B ipc_max_send_size
+Maximum size of a message allowed to be sent to an IPC client. (65536)
+.TP
+.B master_wins
+Force enable/disable master wins. (default is model)
+.TP
+.B heuristics_ipc_max_send_buffers
+Maximum number of heuristics worker send buffers. (128)
+.TP
+.B heuristics_ipc_max_send_receive_size
+Maximum size of a message allowed to be send to, or received from heuristics worker. (4096)
+.TP
+.B heuristics_min_timeout
+Minimum heuristics timeout accepted by client in ms. (1000)
+.TP
+.B heuristics_max_timeout
+Maximum heuristics timeout accepted by client in ms. (120000)
+.TP
+.B heuristics_min_interval
+Minimum heuristics interval accepted by client in ms. (1000)
+.TP
+.B heuristics_max_interval
+Maximum heuristics interval accepted by client in ms. (3600000)
+.TP
+.B heuristics_max_execs
+Maximum number of exec_ commands. (32)
+.TP
+.B heuristics_use_execvp
+Use execvp instead of execv for executing commands. (off)
+.TP
+.B heuristics_max_processes
+Maximum number of processes running at one time. (160)
+.TP
+.B heuristics_kill_list_interval
+Interval between status is gathered and eventually signal is sent
+to processes which didn't finished on time in ms. (5000)
+.TP
+.B net_nss_db_dir
+NSS database directory. (/etc/corosync/qdevice/net/nssdb)
+.TP
+.B net_initial_msg_receive_size
+Initial (used during connection parameters negotiation)
+maximum size of the receive buffer for message (maximum
+allowed message size received from qnetd). (32768)
+.TP
+.B net_initial_msg_send_size
+Initial (used during connection parameter negotiation)
+maximum size of one send buffer (message) to be sent to server. (32768)
+.TP
+.B net_min_msg_send_size
+Minimum required size of one send buffer (message) to be sent to server. (32768)
+.TP
+.B net_max_msg_receive_size
+Maximum allowed size of receive buffer for a message sent by server. (16777216)
+.TP
+.B net_max_send_buffers
+Maximum number of send buffers. (10)
+.TP
+.B net_nss_qnetd_cn
+Canonical name of qnetd server certificate. (Qnetd Server)
+.TP
+.B net_nss_client_cert_nickname
+NSS nickname of qdevice client certificate. (Cluster Cert)
+.TP
+.B net_heartbeat_interval_min
+Minimum heartbeat timeout accepted by client in ms. (1000)
+.TP
+.B net_heartbeat_interval_max
+Maximum heartbeat timeout accepted by client in ms. (120000)
+.TP
+.B net_min_connect_timeout
+Minimum connection timeout accepted by client in ms. (1000)
+.TP
+.B net_max_connect_timeout
+Maximum connection timeout accepted by client in ms. (120000)
+.TP
+.B net_test_algorithm_enabled
+Enable test algorithm. (if built with --enable-debug on, otherwise off)
+
+.SH EXAMPLE
+Define qdevice with
+.I net
+model connecting to qnetd running on qnetd.example.org host, using
+.I ffsplit
+algorithm.
+Heuristics is set to
+.I sync
+mode and executes two commands.
+
+.nf
+quorum {
+  provider: corosync_votequorum
+  device {
+    votes: 1
+    model: net
+    net {
+      tls: on
+      host: qnetd.example.org
+      algorithm: ffsplit
+    }
+    heuristics {
+      mode: sync
+      exec_ping: /bin/ping -q -c 1 "www.example.org"
+      exec_test_txt_exists: /usr/bin/test -f /tmp/test.txt
+    }
+}
+.fi
+.SH SEE ALSO
+.BR corosync-qdevice-tool (8)
+.BR corosync-qdevice-net-certutil (8)
+.BR corosync-qnetd (8)
+.BR corosync.conf (5)
+.SH AUTHOR
+Jan Friesse
+.PP
diff --git a/man/corosync-qnetd-certutil.8 b/man/corosync-qnetd-certutil.8
new file mode 100644 (file)
index 0000000..d55ac28
--- /dev/null
@@ -0,0 +1,73 @@
+.\"/*
+.\" * Copyright (C) 2016 Red Hat, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Jan Friesse <jfriesse@redhat.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of Red Hat, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH COROSYNC-QNETD-CERTUTIL 8 2016-06-28
+.SH NAME
+corosync-qnetd-certutil - tool to generate qnetd TLS certificates
+.SH SYNOPSIS
+.B "corosync-qnetd-certutil [-i|-s] [-c certificate] [-n cluster_name]"
+.SH DESCRIPTION
+.B corosync-qnetd-certutil
+is a frontend for the NSS certutil, it is used for generating the QNetd CA (Certificate Authority), 
+server certificate and signing cluster certificate used by
+.B corosync-qdevice
+when using the model 'net'.
+.SH OPTIONS
+.TP
+.B -i
+Initialize the QNetd NSS certificate database and generate the QNetd CA and server certificates.
+The default directory for the database is /etc/corosync/qnetd. This directory must be
+writeable by the current user. The QNetd CA certificate is also exported into the file
+/etc/corosync/qnetd/nssdb/qnetd-cacert.crt.
+.TP
+.B -s
+Sign the cluster certificate. It is necessary to pass the cluster name (as
+configured in corosync.conf) and the certificate request file - see options below.
+The signed certificate will be written to the 
+file /etc/corosync/qnetd/nssdb/cluster-$ClusterName.crt
+.TP
+.B -c
+Certificate request file to sign.
+.TP
+.B -n
+Name of the cluster.
+.SH NOTES
+If qnetd is executed by a non root user, /etc/corosync/qnetd and its subdirectories must be owned by (or have group access for) the given user. If
+.B corosync-qnetd-certutil
+is executed as root it tries to copy the owner and group of /etc/corosync/qnetd to all of the created files.
+.SH SEE ALSO
+.BR corosync-qnetd (8)
+.BR corosync-qdevice (8)
+.SH AUTHOR
+Jan Friesse
+.PP
diff --git a/man/corosync-qnetd-tool.8 b/man/corosync-qnetd-tool.8
new file mode 100644 (file)
index 0000000..81fb516
--- /dev/null
@@ -0,0 +1,128 @@
+.\"/*
+.\" * Copyright (C) 2016 Red Hat, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Jan Friesse <jfriesse@redhat.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of Red Hat, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH COROSYNC-QNETD-TOOL 8 2016-06-23
+.SH NAME
+corosync-qnetd-tool \- corosync-qnetd control interface.
+.SH SYNOPSIS
+.B "corosync-qnetd-tool [-Hhlsv] [-c cluster_name] [-p qnetd_ipc_socket_path]"
+.SH DESCRIPTION
+.B corosync-qnetd-tool
+is a frontend to the internal corosync-qnetd IPC. Its main purpose is to show important
+information about the current internal state of
+.B corosync-qnetd.
+.SH OPTIONS
+.TP
+.B -H
+Properly shutdown the
+.B corosync-qnetd
+process
+.TP
+.B -h
+Display a short usage text
+.TP
+.B -l
+List all clients connected to the
+.B corosync-qnetd
+process. The output is described in its own section below.
+.TP
+.B -s
+Display status of the
+.B corosync-qnetd
+process.
+.TP
+.B -v
+Display more verbose output for options
+.B -l
+and
+.B -s
+.TP
+.B -c
+Used only with the
+.B -l
+option. By default, information about all clients from all clusters is displayed, with
+this option it's possible to filter information from a single cluster given the
+.I cluster_name.
+.TP
+.B -p
+Path to the
+.B corosync-qnetd
+communication socket.
+
+.SH LIST COMMAND OUTPUT
+.nf
+Cluster "Cluster":
+    Algorithm:          Fifty-Fifty split
+    Tie-breaker:        Node with lowest node ID
+    Node ID 1:
+        Client address:         ::ffff:127.0.0.1:52000
+        HB interval:            8000ms
+        Configured node list:   1, 2
+        Ring ID:                1.a00000000021b40
+        Membership node list:   1, 2
+        TLS active:             Yes (client certificate verified)
+        Vote:                   No change (ACK)
+ ...
+.fi
+
+The output contains a list of clusters. Each cluster has the cluster common options
+.I Algorithm
+and
+.I Tie-breaker
+as configured in the corosync.conf file. Information about nodes follows.
+.I Client address
+is the IP address and port of the client.
+.I HB interval
+is the heartbeat interval between
+.B corosync-qnetd
+and
+.B corosync-qdevice
+client. This option can be configured in corosync.conf.
+.I Configured node list
+is the list of nodes configured in corosync.conf.
+.I Ring ID
+and
+.I Membership node list
+are self-explanatory.
+.I TLS active
+describes if an encrypted transport is used between server and client.
+.I Vote
+is last vote sent to
+.B corosync-qdevice
+client. The last ACK/NACK vote (if it exists) is in parentheses.
+.SH SEE ALSO
+.BR corosync-qnetd (8)
+.BR corosync-qdevice (8)
+.SH AUTHOR
+Jan Friesse
+.PP
diff --git a/man/corosync-qnetd.8 b/man/corosync-qnetd.8
new file mode 100644 (file)
index 0000000..fa58224
--- /dev/null
@@ -0,0 +1,227 @@
+.\"/*
+.\" * Copyright (C) 2016 Red Hat, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Jan Friesse <jfriesse@redhat.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of Red Hat, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH COROSYNC-QNETD 8 2016-06-29
+.SH NAME
+corosync-qnetd \- QNet daemon
+.SH SYNOPSIS
+.B "corosync-qnetd [-46dfhv] [-l listen_addr] [-p listen_port] [-s tls]
+.B [-c client_cert_required] [-m max_clients] [-S option=value[,option2=value2,...]]"
+
+.SH DESCRIPTION
+.B corosync-qnetd
+is a daemon running outside of the cluster with the purpose of providing a vote to the
+.B corosync-qdevice
+model net. It's designed to support multiple clusters and be almost configuration
+and state free. New clusters are handled dynamically and no configuration file exists.
+It's also able to run as non-root user - which is recommended. Connection between the
+.B corosync-qdevice
+model net client can be optionally configured with TLS client certificate checking. 
+The communication protocol between server and client is designed to be very simple 
+and allow backwards compatibility.
+.SH OPTIONS
+.TP
+.B -4
+and its counterpart
+.B -6
+are used to force IPv4 or IPv6 communication. The default is to listen on both address families.
+.TP
+.B -d
+Turn on debug logging. By default the messages sent to syslog are purely operational, this 
+option sends additional debug messages. For even more detail use the
+.B -d
+parameter twice.
+.TP
+.B -f
+Do not daemonize, run in the foreground.
+.TP
+.B -h
+Show short help text
+.TP
+.B -v
+Show version and supported communication protocol messages/options.
+.TP
+.B -l
+IP address to listen on. By default the daemon listens on all addresses (wildcard).
+.TP
+.B -p
+TCP port to listen on. Default port is 5403.
+.TP
+.B -s
+Determines if TLS should be used and can be one of
+.I on/off/required
+(the default is
+.I on
+).
+.I on
+means TLS is enabled but the client is not required to start TLS,
+.I off
+means TLS is completely disabled, and
+.I required
+means TLS is required.
+.I on
+and
+.I required
+require the NSS database to be properly initialized by running the
+.B corosync-qnetd-certutil
+command.
+.TP
+.B -c
+can be set to
+.I on/off.
+This option only makes sense if TLS is enabled. When
+.B -c
+is
+.I on
+a client is required to send its client certificate (default).
+.TP
+.B -m
+Maximum simultaneous clients. The default is 0 which means no limit.
+.TP
+.B -S
+Set advanced settings described in its own section below. This option
+shouldn't be generally used because most of the options are
+not safe to change.
+.SH UNPRIVILEGED USER CONFIGURATION
+It's generally recommended to run
+.B corosync-qnetd
+as a non root user. If you get a package from a distribution its highly
+possible that the packager has done all the hard work for you. If the installation 
+is performed from source code, a few steps have to be taken.
+
+First it's necessary to create an unprivileged user/group. The following commands
+can be used (executed as root):
+
+.nf
+# groupadd -r coroqnetd
+# useradd -r -g coroqnetd -d / -s /sbin/nologin -c "User for corosync-qnetd" coroqnetd
+.fi
+
+The next step is to set the correct owner and group on /etc/corosync/qnetd and /var/run/corosync-qnetd
+directories.
+
+.nf
+# chown -R coroqnetd:coroqnetd /etc/corosync/qnetd /var/run/corosync-qnetd
+.fi
+
+Some systems have the /var/run directory on a tmpfs file system which gets discarded after
+a reboot. The solution is to use an initscript which takes care of the /var/run/corosync-qnetd
+creation and sets the correct owner and permissions. For systems with systemd it's possible
+to use a tmpfile.d configuration file (installed by default if systemd is enabled during
+corosync compilation).
+
+The last step is to make sure
+.B corosync-qnetd
+is really executed as an unprivileged user. For initscript systems it's enough to set the
+line COROSYNC_QNETD_RUNAS in /etc/(sysconfig|default)/corosync-qnetd file. If the file
+is not already installed then use the one provided in the corosync source code
+(init/corosync-qnetd.sysconfig.example). For systemd, overwrite/copy the
+corosync-qnetd.service unit file and uncomment/change the "User=" directive.
+
+.SH TLS CONFIGURATION
+For TLS to work its necessary to create the NSS database. If pcs is used then the following
+steps are not needed because pcs does them automatically.
+
+.B corosync-qnetd-certutil
+is the tool to perform required actions. Just run:
+
+.nf
+# corosync-qnetd-certutil -i
+.fi
+
+If TLS is not required then simply edit /etc/(sysconfig|default)/corosync-qnetd or
+systemd unit file and add the parameter
+.B -s
+.I off
+in the proper place.
+
+.SH ADVANCED SETTINGS
+Set by the
+.B -S
+option. The default value is shown in parentheses.
+.TP
+.B listen_backlog
+Parameter passed to the listen syscall on the network socket. (10)
+.TP
+.B max_client_send_buffers
+Maximum number of send buffers for one client. (32)
+.TP
+.B max_client_send_size
+Maximum size of one send buffer (message) to be sent to a client. (32768)
+.TP
+.B max_client_receive_size
+Maximum size of the receive buffer for a client message (maximum
+allowed message size received by client). (32768)
+.TP
+.B nss_db_dir
+NSS database directory. (/etc/corosync/qnetd/nssdb)
+.TP
+.B cert_nickname
+NSS nickname of qnetd server certificate. (QNetd Cert)
+.TP
+.B heartbeat_interval_min
+Minimum heartbeat timeout accepted by server in ms. (1000)
+.TP
+.B heartbeat_interval_max
+Maximum heartbeat timeout accepted by server in ms. (120000)
+.TP
+.B dpd_enabled
+Dead peer detection enabled. (on)
+.TP
+.B dpd_interval
+How often the DPD algorithm detects dead peers in ms. (10000)
+.TP
+.B lock_file
+Lock file location. (/var/run/corosync-qnetd/corosync-qnetd.pid)
+.TP
+.B local_socket_file
+Internal IPC socket file location. (/var/run/corosync-qnetd/corosync-qnetd.sock)
+.TP
+.B local_socket_backlog
+Parameter passed to listen syscall on the local socket. (10)
+.TP
+.B ipc_max_clients
+Maximum allowed simultaneous IPC clients. (10)
+.TP
+.B ipc_max_receive_size
+Maximum size of a message received by IPC client. (4096)
+.TP
+.B ipc_max_send_size
+Maximum size of a message sent to an IPC client. (10485760)
+.SH SEE ALSO
+.BR corosync-qnetd-tool (8)
+.BR corosync-qnetd-certutil (8)
+.BR corosync-qdevice (8)
+.SH AUTHOR
+Jan Friesse
+.PP
diff --git a/qdevices/.gitignore b/qdevices/.gitignore
new file mode 100644 (file)
index 0000000..779cdee
--- /dev/null
@@ -0,0 +1,7 @@
+corosync-qdevice
+corosync-qdevice-tool
+corosync-qnetd-certutil
+corosync-qdevice-net-certutil
+corosync-qnetd
+corosync-qnetd-tool
+*.test
diff --git a/qdevices/Makefile.am b/qdevices/Makefile.am
new file mode 100644 (file)
index 0000000..d48b004
--- /dev/null
@@ -0,0 +1,180 @@
+# Copyright (c) 2012-2018 Red Hat, Inc.
+#
+# Authors: Jan Friesse (jfriesse@redhat.com)
+#          Fabio M. Di Nitto (fdinitto@redhat.com)
+#
+# This software licensed under BSD license, the text of which follows:
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+MAINTAINERCLEANFILES    = Makefile.in
+
+SUBDIRS                        =
+
+bin_PROGRAMS           =
+sbin_PROGRAMS          =
+bin_SCRIPTS            =
+sbin_SCRIPTS           =
+EXTRA_DIST             = corosync-qnetd-certutil.sh corosync-qdevice-net-certutil.sh
+
+if BUILD_QNETD
+
+bin_PROGRAMS           += corosync-qnetd corosync-qnetd-tool
+
+bin_SCRIPTS             += corosync-qnetd-certutil
+
+corosync_qnetd_SOURCES = corosync-qnetd.c \
+                          dynar.c dynar.h msg.c msg.h msgio.c msgio.h \
+                          nss-sock.c nss-sock.h qnetd-client.c qnetd-client.h \
+                          qnetd-client-list.c qnetd-client-list.h qnetd-log.c qnetd-log.h \
+                          pr-poll-array.c pr-poll-array.h timer-list.c timer-list.h tlv.c tlv.h \
+                          send-buffer-list.c send-buffer-list.h node-list.c node-list.h \
+                          qnetd-algo-test.c qnetd-algo-test.h qnetd-algorithm.c qnetd-algorithm.h \
+                          qnetd-algo-utils.c qnetd-algo-utils.h \
+                          qnetd-algo-ffsplit.c qnetd-algo-ffsplit.h \
+                          qnetd-cluster.c qnetd-cluster.h \
+                          qnetd-cluster-list.c qnetd-cluster-list.h \
+                          qnetd-client-send.c qnetd-client-send.h \
+                          qnetd-algo-2nodelms.c qnetd-algo-2nodelms.h qnetd-algo-lms.c qnetd-algo-lms.h \
+                          utils.c utils.h qnetd-instance.c qnetd-instance.h \
+                          qnetd-client-net.c qnetd-client-net.h \
+                          qnetd-client-msg-received.c qnetd-client-msg-received.h \
+                          qnetd-log-debug.c qnetd-log-debug.h \
+                          qnetd-client-algo-timer.c qnetd-client-algo-timer.h \
+                          qnetd-dpd-timer.c qnetd-dpd-timer.h \
+                          qnetd-ipc.c qnetd-ipc.h unix-socket-ipc.c unix-socket-ipc.h \
+                          dynar-simple-lex.c dynar-simple-lex.h dynar-str.c dynar-str.h \
+                          unix-socket-client.c unix-socket-client.h \
+                          unix-socket-client-list.c unix-socket-client-list.h \
+                          unix-socket.c unix-socket.h qnetd-ipc-cmd.c qnetd-ipc-cmd.h \
+                          qnetd-poll-array-user-data.h qnet-config.h dynar-getopt-lex.c \
+                          dynar-getopt-lex.h qnetd-advanced-settings.c qnetd-advanced-settings.h
+
+corosync_qnetd_tool_SOURCES = corosync-qnetd-tool.c unix-socket.c unix-socket.h dynar.c dynar.h \
+                              dynar-str.c dynar-str.h utils.c utils.h
+
+corosync_qnetd_CFLAGS          = $(nss_CFLAGS) $(libsystemd_CFLAGS)
+corosync_qnetd_LDADD           = $(nss_LIBS)   $(libsystemd_LIBS)
+
+corosync-qnetd-certutil: corosync-qnetd-certutil.sh
+       sed -e 's#@''DATADIR@#${datadir}#g' \
+           -e 's#@''BASHPATH@#${BASHPATH}#g' \
+           -e 's#@''COROSYSCONFDIR@#${COROSYSCONFDIR}#g' \
+           $< > $@
+
+endif
+
+if BUILD_QDEVICES
+
+sbin_PROGRAMS          += corosync-qdevice corosync-qdevice-tool
+
+sbin_SCRIPTS            += corosync-qdevice-net-certutil
+
+corosync_qdevice_SOURCES = corosync-qdevice.c \
+                           qdevice-cmap.c qdevice-cmap.h \
+                           qdevice-instance.c qdevice-instance.h node-list.c node-list.h \
+                           utils.c utils.h qdevice-log.c qdevice-log.h \
+                           qdevice-log-debug.c qdevice-log-debug.h \
+                           qdevice-votequorum.c qdevice-votequorum.h \
+                           qdevice-model.c qdevice-model.h qdevice-model-net.c qdevice-model-net.h \
+                           qdevice-net-instance.c qdevice-net-instance.h dynar.c dynar.h \
+                           send-buffer-list.c send-buffer-list.h timer-list.c timer-list.h \
+                           msg.c msg.h msgio.c msgio.h nss-sock.c nss-sock.h tlv.c tlv.h \
+                           unix-socket.c unix-socket.h unix-socket-client.c unix-socket-client.h \
+                           unix-socket-client-list.c unix-socket-client-list.h \
+                           unix-socket-ipc.c unix-socket-ipc.h qdevice-ipc.c qdevice-ipc.h \
+                           pr-poll-array.c pr-poll-array.h dynar-simple-lex.c dynar-simple-lex.h \
+                           dynar-str.c dynar-str.h qdevice-ipc-cmd.c qdevice-ipc-cmd.h \
+                           qdevice-net-ipc-cmd.c qdevice-net-ipc-cmd.h \
+                           qdevice-net-poll.c qdevice-net-poll.h \
+                           qdevice-net-send.c qdevice-net-send.h \
+                           qdevice-net-votequorum.c qdevice-net-votequorum.h \
+                           qdevice-net-socket.c qdevice-net-socket.h \
+                           qdevice-net-nss.c qdevice-net-nss.h \
+                           qdevice-net-msg-received.c qdevice-net-msg-received.h \
+                           qdevice-net-cast-vote-timer.c qdevice-net-cast-vote-timer.h \
+                           qdevice-net-echo-request-timer.c qdevice-net-echo-request-timer.h \
+                           qdevice-net-algorithm.c qdevice-net-algorithm.h \
+                           qdevice-net-algo-test.c qdevice-net-algo-test.h \
+                           qdevice-net-algo-ffsplit.c qdevice-net-algo-ffsplit.h \
+                           qdevice-net-algo-2nodelms.c qdevice-net-algo-2nodelms.h \
+                           qdevice-net-algo-lms.c qdevice-net-algo-lms.h \
+                           qdevice-net-poll-array-user-data.h \
+                           qdevice-config.h qnet-config.h qdevice-net-disconnect-reason.h \
+                           qdevice-model-type.h qdevice-advanced-settings.c \
+                           qdevice-advanced-settings.h dynar-getopt-lex.c dynar-getopt-lex.h \
+                           qdevice-heuristics.h qdevice-heuristics.c \
+                           qdevice-heuristics-worker.h qdevice-heuristics-worker.c \
+                           qdevice-heuristics-io.h qdevice-heuristics-io.c \
+                           qdevice-heuristics-worker-instance.h \
+                           qdevice-heuristics-worker-log.h qdevice-heuristics-worker-log.c \
+                           qdevice-heuristics-log.h qdevice-heuristics-log.c \
+                           qdevice-heuristics-instance.h qdevice-heuristics-instance.c \
+                           qdevice-heuristics-mode.h qdevice-heuristics-mode.c \
+                           qdevice-heuristics-exec-list.c qdevice-heuristics-exec-list.h \
+                           qdevice-heuristics-cmd.c qdevice-heuristics-cmd.h \
+                           qdevice-heuristics-worker-cmd.c qdevice-heuristics-worker-cmd.h \
+                           qdevice-heuristics-cmd-str.h \
+                           qdevice-heuristics-exec-result.c qdevice-heuristics-exec-result.h \
+                           process-list.h process-list.c \
+                           qdevice-net-heuristics.c qdevice-net-heuristics.h \
+                           qdevice-heuristics-result-notifier.c qdevice-heuristics-result-notifier.h
+
+corosync_qdevice_tool_SOURCES = corosync-qdevice-tool.c unix-socket.c unix-socket.h dynar.c dynar.h \
+                                dynar-str.c dynar-str.h utils.c utils.h
+
+corosync_qdevice_CFLAGS = $(nss_CFLAGS) $(libsystemd_CFLAGS) $(cmap_CFLAGS) \
+                          $(qb_CFLAGS) $(votequorum_CFLAGS) $(corosync_common_CFLAGS)
+corosync_qdevice_LDADD  = $(nss_LIBS) $(libsystemd_LIBS) $(cmap_LIBS) \
+                          $(qb_LIBS) $(votequorum_LIBS) $(corosync_common_LIBS)
+
+corosync-qdevice-net-certutil: corosync-qdevice-net-certutil.sh
+       sed -e 's#@''DATADIR@#${datadir}#g' \
+           -e 's#@''BASHPATH@#${BASHPATH}#g' \
+           -e 's#@''COROSYSCONFDIR@#${COROSYSCONFDIR}#g' \
+           $< > $@
+
+TESTS                          = qnetd-cluster-list.test dynar.test dynar-simple-lex.test \
+                                  dynar-getopt-lex.test process-list.test
+check_PROGRAMS                 = qnetd-cluster-list.test dynar.test dynar-simple-lex.test \
+                                  dynar-getopt-lex.test process-list.test
+
+qnetd_cluster_list_test_SOURCES        = qnetd-cluster-list.c test-qnetd-cluster-list.c \
+                                  qnetd-cluster.c qnetd-cluster.h \
+                                  qnetd-client-list.c qnetd-client.c dynar.c node-list.c \
+                                  send-buffer-list.c
+qnetd_cluster_list_test_CFLAGS  = $(nss_CFLAGS)
+qnetd_cluster_list_test_LDADD  = $(nss_LIBS)
+
+dynar_test_SOURCES             = test-dynar.c dynar.c dynar-str.c
+dynar_simple_lex_test_SOURCES  = test-dynar-simple-lex.c dynar.c dynar-str.c dynar-simple-lex.c
+dynar_getopt_lex_test_SOURCES  = test-dynar-getopt-lex.c dynar.c dynar-str.c dynar-getopt-lex.c
+process_list_test_SOURCES      = test-process-list.c dynar.c dynar-str.c dynar-simple-lex.c \
+                                  process-list.c
+
+endif
+
+clean-local:
+       rm -rf $(bin_SCRIPTS) $(sbin_SCRIPTS)
diff --git a/qdevices/corosync-qdevice-net-certutil.sh b/qdevices/corosync-qdevice-net-certutil.sh
new file mode 100644 (file)
index 0000000..10b47c8
--- /dev/null
@@ -0,0 +1,404 @@
+#!@BASHPATH@
+
+#
+# Copyright (c) 2015-2016 Red Hat, Inc.
+#
+# All rights reserved.
+#
+# Author: Jan Friesse (jfriesse@redhat.com)
+#
+# This software licensed under BSD license, the text of which follows:
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+BASE_DIR="@COROSYSCONFDIR@/qdevice/net"
+DB_DIR_QNETD="@COROSYSCONFDIR@/qnetd/nssdb"
+DB_DIR_NODE="$BASE_DIR/nssdb"
+# Validity of certificate (months)
+CRT_VALIDITY=1200
+CA_NICKNAME="QNet CA"
+SERVER_NICKNAME="QNetd Cert"
+CLUSTER_NICKNAME="Cluster Cert"
+CA_SUBJECT="CN=QNet CA"
+SERVER_SUBJECT="CN=Qnetd Server"
+PWD_FILE_BASE="pwdfile.txt"
+NOISE_FILE_BASE="noise.txt"
+SERIAL_NO_FILE_BASE="serial.txt"
+CA_EXPORT_FILE="$DB_DIR_QNETD/qnetd-cacert.crt"
+CRQ_FILE_BASE="qdevice-net-node.crq"
+CRT_FILE_BASE="" # Generated from cluster name
+P12_FILE_BASE="qdevice-net-node.p12"
+QNETD_CERTUTIL_CMD="corosync-qnetd-certutil"
+
+usage() {
+    echo "$0: [-i|-m|-M|-r|-s|-Q] [-c certificate] [-n cluster_name]"
+    echo
+    echo " -i      Initialize node CA. Needs CA certificate from server"
+    echo " -m      Import cluster certificate on node (needs pk12 certificate)"
+    echo " -r      Generate cluster certificate request"
+    echo " -M      Import signed cluster certificate and export certificate with key to pk12 file"
+    echo " -Q      Quick start. Uses ssh/scp to initialze both qnetd and nodes."
+    echo ""
+    echo " -c certificate      Ether CA, CRQ, CRT or pk12 certificate (operation dependant)"
+    echo " -n cluster_name     Name of cluster (for -r and -s operations)"
+    echo ""
+    echo "Typical usage:"
+    echo "- Initialize database on QNetd server by running $QNETD_CERTUTIL_CMD -i"
+    echo "- Copy exported QNetd CA certificate ($CA_EXPORT_FILE) to every node"
+    echo "- On one of cluster node initialize database by running $0 -i -c `basename $CA_EXPORT_FILE`"
+    echo "- Generate certificate request: $0 -r -n Cluster (Cluster name must match cluster_name key in the corosync.conf)"
+    echo "- Copy exported CRQ to QNetd server"
+    echo "- On QNetd server sign and export cluster certificate by running $QNETD_CERTUTIL_CMD -s -c `basename $CRQ_FILE_BASE` -n Cluster"
+    echo "- Copy exported CRT to node where certificate request was created"
+    echo "- Import certificate on node where certificate request was created by running $0 -M -c cluster-Cluster.crt"
+    echo "- Copy output $P12_FILE_BASE to all other cluster nodes"
+    echo "- On all other nodes in cluster:"
+    echo "  - Init database by running $0 -i -c `basename $CA_EXPORT_FILE`"
+    echo "  - Import cluster certificate and key: $0 -m -c `basename $P12_FILE_BASE`"
+    echo ""
+    echo "It is also possible to use Quick start (-Q). This needs properly configured ssh."
+    echo "  $0 -Q -n Cluster qnetd_server node1 node2 ... nodeN"
+
+    exit 0
+}
+
+create_new_noise_file() {
+    local noise_file="$1"
+
+    if [ ! -e "$noise_file" ];then
+        echo "Creating new noise file $noise_file"
+
+        (ps -elf; date; w) | sha1sum | (read sha_sum rest; echo $sha_sum) > "$noise_file"
+
+        chown root:root "$noise_file"
+        chmod 0660 "$noise_file"
+    else
+        echo "Using existing noise file $noise_file"
+    fi
+}
+
+get_serial_no() {
+    local serial_no
+
+    if ! [ -f "$SERIAL_NO_FILE" ];then
+        echo "100" > $SERIAL_NO_FILE
+        chown root:root "$DB_DIR"
+        chmod 0660 "$SERIAL_NO_FILE"
+    fi
+    serial_no=`cat $SERIAL_NO_FILE`
+    serial_no=$((serial_no+1))
+    echo "$serial_no" > $SERIAL_NO_FILE
+    echo "$serial_no"
+}
+
+init_node_ca() {
+    if [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database already exists. Delete it to continue" >&2
+
+        exit 1
+    fi
+
+    if ! [ -d "$DB_DIR" ];then
+        echo "Creating $DB_DIR"
+        mkdir -p "$DB_DIR"
+        chown root:root "$DB_DIR"
+        chmod 0770 "$DB_DIR"
+    fi
+
+    echo "Creating new key and cert db"
+    echo -n "" > "$PWD_FILE"
+    chown root:root "$PWD_FILE"
+    chmod 0660 "$PWD_FILE"
+    certutil -N -d "$DB_DIR" -f "$PWD_FILE"
+    chown root:root "$DB_DIR/key3.db" "$DB_DIR/cert8.db" "$DB_DIR/secmod.db"
+    chmod 0660 "$DB_DIR/key3.db" "$DB_DIR/cert8.db" "$DB_DIR/secmod.db"
+
+    create_new_noise_file "$NOISE_FILE"
+
+    echo "Importing CA"
+
+    certutil -d "$DB_DIR" -A -t "CT,c,c" -n "$CA_NICKNAME" -f "$PWD_FILE" \
+        -i "$CERTIFICATE_FILE"
+}
+
+gen_cluster_cert_req() {
+    if ! [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database doesn't exists. Use $0 -i to create it" >&2
+
+        exit 1
+    fi
+
+    echo "Creating new certificate request"
+
+    certutil -R -s "CN=$CLUSTER_NAME" -o "$CRQ_FILE" -d "$DB_DIR" -f "$PWD_FILE" -z "$NOISE_FILE"
+
+    echo "Certificate request stored in $CRQ_FILE"
+}
+
+import_signed_cert() {
+    if ! [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database doesn't exists. Use $0 -i to create it" >&2
+
+        exit 1
+    fi
+
+    echo "Importing signed cluster certificate"
+    certutil -d "$DB_DIR" -A -t "u,u,u" -n "$CLUSTER_NICKNAME" -i "$CERTIFICATE_FILE"
+
+    pk12util -d "$DB_DIR" -o "$P12_FILE" -W "" -n "$CLUSTER_NICKNAME"
+
+    echo "Certificate stored in $P12_FILE"
+}
+
+import_pk12() {
+    if ! [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database doesn't exists. Use $0 -i to create it" >&2
+
+        exit 1
+    fi
+
+    echo "Importing cluster certificate and key"
+    pk12util -i "$CERTIFICATE_FILE" -d "$DB_DIR" -W ""
+}
+
+quick_start() {
+    qnetd_addr="$1"
+    master_node="$2"
+    other_nodes="$3"
+
+    # Sanity check
+    for i in "$master_node" $other_nodes;do
+        if ssh root@$i "[ -d \"$DB_DIR_NODE\" ]";then
+            echo "Node $i seems to be already initialized. Please delete $DB_DIR_NODE" >&2
+
+            exit 1
+        fi
+
+        if ! ssh "root@$i" "$0" > /dev/null;then
+            echo "Node $i doesn't have $0 installed" >&2
+
+            exit 1
+        fi
+    done
+
+    # Initialize qnetd server (it's no problem if server is already initialized)
+    ssh "root@$qnetd_addr" "$QNETD_CERTUTIL_CMD -i"
+
+    # Copy CA cert to all nodes and initialize them
+    for node in "$master_node" $other_nodes;do
+        scp "root@$qnetd_addr:$CA_EXPORT_FILE" "$node:/tmp"
+        ssh "root@$node" "$0 -i -c \"/tmp/`basename $CA_EXPORT_FILE`\" && rm /tmp/`basename $CA_EXPORT_FILE`"
+    done
+
+    # Generate cert request
+    ssh "root@$master_node" "$0 -r -n \"$CLUSTER_NAME\""
+
+    # Copy exported cert request to qnetd server
+    scp "root@$master_node:$DB_DIR_NODE/$CRQ_FILE_BASE" "root@$qnetd_addr:/tmp"
+
+    # Sign and export cluster certificate
+    ssh "root@$qnetd_addr" "$QNETD_CERTUTIL_CMD -s -c \"/tmp/$CRQ_FILE_BASE\" -n \"$CLUSTER_NAME\""
+
+    # Copy exported CRT to master node
+    scp "root@$qnetd_addr:$DB_DIR_QNETD/cluster-$CLUSTER_NAME.crt" "root@$master_node:$DB_DIR_NODE"
+
+    # Import certificate
+    ssh "root@$master_node" "$0 -M -c \"$DB_DIR_NODE/cluster-$CLUSTER_NAME.crt\""
+
+    # Copy pk12 cert to all nodes and import it
+    for node in $other_nodes;do
+        scp "root@$master_node:$DB_DIR_NODE/$P12_FILE" "$node:$DB_DIR_NODE/$P12_FILE"
+        ssh "root@$node" "$0 -m -c \"$DB_DIR_NODE/$P12_FILE\""
+    done
+}
+
+OPERATION=""
+CERTIFICATE_FILE=""
+CLUSTER_NAME=""
+
+while getopts ":hiMmQrc:n:" opt; do
+    case $opt in
+        r)
+            OPERATION=gen_cluster_cert_req
+            ;;
+        i)
+            OPERATION=init_node_ca
+            ;;
+        m)
+            OPERATION=import_pk12
+            ;;
+        M)
+            OPERATION=import_signed_cert
+            ;;
+        Q)
+            OPERATION=quick_start
+            ;;
+        n)
+            CLUSTER_NAME="$OPTARG"
+            ;;
+        h)
+            usage
+            ;;
+        c)
+            CERTIFICATE_FILE="$OPTARG"
+            ;;
+        \?)
+            echo "Invalid option: -$OPTARG" >&2
+
+            exit 1
+            ;;
+        :)
+            echo "Option -$OPTARG requires an argument." >&2
+
+            exit 1
+            ;;
+   esac
+done
+
+case "$OPERATION" in
+    "init_qnetd_ca")
+        DB_DIR="$DB_DIR_QNETD"
+    ;;
+    "init_node_ca")
+        DB_DIR="$DB_DIR_NODE"
+    ;;
+    "gen_cluster_cert_req")
+        DB_DIR="$DB_DIR_NODE"
+    ;;
+    "sign_cluster_cert")
+        DB_DIR="$DB_DIR_QNETD"
+    ;;
+    "import_signed_cert")
+        DB_DIR="$DB_DIR_NODE"
+    ;;
+    "import_pk12")
+        DB_DIR="$DB_DIR_NODE"
+    ;;
+    "quick_start")
+        DB_DIR=""
+    ;;
+    *)
+        usage
+    ;;
+esac
+
+PWD_FILE="$DB_DIR/$PWD_FILE_BASE"
+NOISE_FILE="$DB_DIR/$NOISE_FILE_BASE"
+SERIAL_NO_FILE="$DB_DIR/$SERIAL_NO_FILE_BASE"
+CRQ_FILE="$DB_DIR/$CRQ_FILE_BASE"
+CRT_FILE="$DB_DIR/cluster-$CLUSTER_NAME.crt"
+P12_FILE="$DB_DIR/$P12_FILE_BASE"
+
+case "$OPERATION" in
+    "init_qnetd_ca")
+        init_qnetd_ca
+    ;;
+    "init_node_ca")
+        if ! [ -e "$CERTIFICATE_FILE" ];then
+            echo "Can't open certificate file $CERTIFICATE_FILE" >&2
+
+            exit 2
+        fi
+
+        init_node_ca
+    ;;
+    "gen_cluster_cert_req")
+        if [ "$CLUSTER_NAME" == "" ];then
+            echo "You have to specify cluster name" >&2
+
+            exit 2
+        fi
+
+        gen_cluster_cert_req
+    ;;
+    "sign_cluster_cert")
+        if ! [ -e "$CERTIFICATE_FILE" ];then
+            echo "Can't open certificate file $CERTIFICATE_FILE" >&2
+
+            exit 2
+        fi
+
+        if [ "$CLUSTER_NAME" == "" ];then
+            echo "You have to specify cluster name" >&2
+
+            exit 2
+        fi
+
+        sign_cluster_cert
+    ;;
+    "import_signed_cert")
+        if ! [ -e "$CERTIFICATE_FILE" ];then
+            echo "Can't open certificate file $CERTIFICATE_FILE" >&2
+
+            exit 2
+        fi
+
+        import_signed_cert
+    ;;
+    "import_pk12")
+        if ! [ -e "$CERTIFICATE_FILE" ];then
+            echo "Can't open certificate file $CERTIFICATE_FILE" >&2
+
+            exit 2
+        fi
+
+        import_pk12
+    ;;
+    "quick_start")
+        shift $((OPTIND-1))
+
+        qnetd_addr="$1"
+
+        shift 1
+
+        master_node="$1"
+        shift 1
+        other_nodes="$@"
+
+        if [ "$CLUSTER_NAME" == "" ];then
+            echo "You have to specify cluster name" >&2
+
+            exit 2
+        fi
+
+        if [ "$qnetd_addr" == "" ];then
+            echo "No QNetd server address provided." >&2
+
+            exit 2
+        fi
+
+        if [ "$master_node" == "" ];then
+            echo "No nodes provided." >&2
+
+            exit 2
+        fi
+
+        quick_start "$qnetd_addr" "$master_node" "$other_nodes"
+    ;;
+    *)
+        usage
+    ;;
+esac
diff --git a/qdevices/corosync-qdevice-tool.c b/qdevices/corosync-qdevice-tool.c
new file mode 100644 (file)
index 0000000..e30730e
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "qdevice-config.h"
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "utils.h"
+#include "unix-socket.h"
+
+#define IPC_READ_BUF_SIZE      512
+
+enum qdevice_tool_operation {
+       QDEVICE_TOOL_OPERATION_NONE,
+       QDEVICE_TOOL_OPERATION_SHUTDOWN,
+       QDEVICE_TOOL_OPERATION_STATUS,
+};
+
+enum qdevice_tool_exit_code {
+       QDEVICE_TOOL_EXIT_CODE_NO_ERROR = 0,
+       QDEVICE_TOOL_EXIT_CODE_USAGE = 1,
+       QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR = 2,
+       QDEVICE_TOOL_EXIT_CODE_SOCKET_CONNECT = 3,
+       QDEVICE_TOOL_EXIT_CODE_QDEVICE_RETURNED_ERROR = 4,
+};
+
+static void
+usage(void)
+{
+
+       printf("usage: %s [-Hhsv] [-p qdevice_ipc_socket_path]\n",
+           QDEVICE_TOOL_PROGRAM_NAME);
+}
+
+static void
+cli_parse(int argc, char * const argv[], enum qdevice_tool_operation *operation,
+    int *verbose, char **socket_path)
+{
+       int ch;
+
+       *operation = QDEVICE_TOOL_OPERATION_NONE;
+       *verbose = 0;
+       *socket_path = strdup(QDEVICE_DEFAULT_LOCAL_SOCKET_FILE);
+
+       if (*socket_path == NULL) {
+               errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR,
+                   "Can't alloc memory for socket path string");
+       }
+
+       while ((ch = getopt(argc, argv, "Hhsvp:")) != -1) {
+               switch (ch) {
+               case 'H':
+                       *operation = QDEVICE_TOOL_OPERATION_SHUTDOWN;
+                       break;
+               case 's':
+                       *operation = QDEVICE_TOOL_OPERATION_STATUS;
+                       break;
+               case 'v':
+                       *verbose = 1;
+                       break;
+               case 'p':
+                       free(*socket_path);
+                       *socket_path = strdup(optarg);
+                       if (*socket_path == NULL) {
+                               errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR,
+                                   "Can't alloc memory for socket path string");
+                       }
+                       break;
+               case 'h':
+               case '?':
+                       usage();
+                       exit(QDEVICE_TOOL_EXIT_CODE_USAGE);
+                       break;
+               }
+       }
+
+       if (*operation == QDEVICE_TOOL_OPERATION_NONE) {
+               usage();
+               exit(QDEVICE_TOOL_EXIT_CODE_USAGE);
+       }
+}
+
+static int
+store_command(struct dynar *str, enum qdevice_tool_operation operation, int verbose)
+{
+       const char *nline = "\n\0";
+       const int nline_len = 2;
+
+       switch (operation) {
+       case QDEVICE_TOOL_OPERATION_NONE:
+               errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Unhandled operation none");
+               break;
+       case QDEVICE_TOOL_OPERATION_SHUTDOWN:
+               if (dynar_str_cat(str, "shutdown ") != 0) {
+                       return (-1);
+               }
+               break;
+       case QDEVICE_TOOL_OPERATION_STATUS:
+               if (dynar_str_cat(str, "status ") != 0) {
+                       return (-1);
+               }
+               break;
+       }
+
+       if (verbose) {
+               if (dynar_str_cat(str, "verbose ") != 0) {
+                       return (-1);
+               }
+       }
+
+       if (dynar_cat(str, nline, nline_len) != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+/*
+ * -1 - Internal error (can't alloc memory)
+ *  0 - No error
+ *  1 - IPC returned error
+ *  2 - Unknown status line
+ */
+static int
+read_ipc_reply(FILE *f)
+{
+       struct dynar read_str;
+       int ch;
+       int status_readed;
+       int res;
+       static const char *ok_str = "OK";
+       static const char *err_str = "Error";
+       int err_set;
+       char c;
+
+       dynar_init(&read_str, IPC_READ_BUF_SIZE);
+
+       status_readed = 0;
+       err_set = 0;
+       res = 0;
+
+       while ((ch = fgetc(f)) != EOF) {
+               if (status_readed) {
+                       putc(ch, (err_set ? stderr : stdout));
+               } else {
+                       if (ch == '\r') {
+                       } else if (ch == '\n') {
+                               status_readed = 1;
+
+                               c = '\0';
+                               if (dynar_cat(&read_str, &c, sizeof(c)) != 0) {
+                                       res = -1;
+                                       goto exit_destroy;
+                               }
+
+                               if (strcasecmp(dynar_data(&read_str), ok_str) == 0) {
+                               } else if (strcasecmp(dynar_data(&read_str), err_str) == 0) {
+                                       err_set = 1;
+                                       res = 1;
+                                       fprintf(stderr, "Error: ");
+                               } else {
+                                       res = 2;
+                                       goto exit_destroy;
+                               }
+                       } else {
+                               c = ch;
+                               if (dynar_cat(&read_str, &c, sizeof(c)) != 0) {
+                                       res = -1;
+                                       goto exit_destroy;
+                               }
+                       }
+               }
+       }
+
+exit_destroy:
+       dynar_destroy(&read_str);
+
+       return (res);
+}
+
+int
+main(int argc, char * const argv[])
+{
+       enum qdevice_tool_operation operation;
+       int verbose;
+       char *socket_path;
+       int sock_fd;
+       FILE *sock;
+       struct dynar send_str;
+       int res;
+       int exit_code;
+
+       exit_code = QDEVICE_TOOL_EXIT_CODE_NO_ERROR;
+
+       cli_parse(argc, argv, &operation, &verbose, &socket_path);
+
+       dynar_init(&send_str, QDEVICE_DEFAULT_IPC_MAX_RECEIVE_SIZE);
+
+       sock_fd = unix_socket_client_create(socket_path, 0);
+       if (sock_fd == -1) {
+               err(QDEVICE_TOOL_EXIT_CODE_SOCKET_CONNECT,
+                   "Can't connect to QDevice socket (is QDevice running?)");
+       }
+
+       sock = fdopen(sock_fd, "w+t");
+       if (sock == NULL) {
+               err(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't open QDevice socket fd");
+       }
+
+       if (store_command(&send_str, operation, verbose) != 0) {
+               errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't store command");
+       }
+
+       res = fprintf(sock, "%s", dynar_data(&send_str));
+       if (res < 0 || (size_t)res != strlen(dynar_data(&send_str)) ||
+           fflush(sock) != 0) {
+               errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't send command");
+       }
+
+       res = read_ipc_reply(sock);
+       switch (res) {
+       case -1:
+               errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Internal error during IPC status line read");
+               break;
+       case 0:
+               break;
+       case 1:
+               exit_code = QDEVICE_TOOL_EXIT_CODE_QDEVICE_RETURNED_ERROR;
+               break;
+       case 2:
+               errx(QDEVICE_TOOL_EXIT_CODE_SOCKET_CONNECT, "Unknown status line returned by IPC server");
+               break;
+       }
+
+       if (fclose(sock) != 0) {
+               warn("Can't close QDevice socket");
+       }
+
+       free(socket_path);
+       dynar_destroy(&send_str);
+
+       return (exit_code);
+}
diff --git a/qdevices/corosync-qdevice.c b/qdevices/corosync-qdevice.c
new file mode 100644 (file)
index 0000000..dc7c79c
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <err.h>
+#include <signal.h>
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "dynar-getopt-lex.h"
+#include "qdevice-advanced-settings.h"
+#include "qdevice-config.h"
+#include "qdevice-cmap.h"
+#include "qdevice-heuristics.h"
+#include "qdevice-ipc.h"
+#include "qdevice-log.h"
+#include "qdevice-model.h"
+#include "qdevice-votequorum.h"
+#include "utils.h"
+
+#ifdef HAVE_LIBSYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+struct qdevice_instance *global_instance;
+
+static void
+signal_int_handler(int sig)
+{
+       qdevice_log(LOG_DEBUG, "SIGINT received - closing local unix socket");
+       qdevice_ipc_close(global_instance);
+}
+
+static void
+signal_term_handler(int sig)
+{
+       qdevice_log(LOG_DEBUG, "SIGTERM received - closing server socket");
+       qdevice_ipc_close(global_instance);
+}
+
+static void
+signal_handlers_register(void)
+{
+       struct sigaction act;
+
+       act.sa_handler = signal_int_handler;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGINT, &act, NULL);
+
+       act.sa_handler = signal_term_handler;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGTERM, &act, NULL);
+
+       act.sa_handler = SIG_DFL;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGCHLD, &act, NULL);
+
+       act.sa_handler = SIG_IGN;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGPIPE, &act, NULL);
+}
+
+static void
+usage(void)
+{
+
+       printf("usage: %s [-dfh] [-S option=value[,option2=value2,...]]\n", QDEVICE_PROGRAM_NAME);
+}
+
+static void
+cli_parse_long_opt(struct qdevice_advanced_settings *advanced_settings, const char *long_opt)
+{
+       struct dynar_getopt_lex lex;
+       struct dynar dynar_long_opt;
+       const char *opt;
+       const char *val;
+       int res;
+
+       dynar_init(&dynar_long_opt, strlen(long_opt) + 1);
+       if (dynar_str_cpy(&dynar_long_opt, long_opt) != 0) {
+               errx(1, "Can't alloc memory for long option");
+       }
+
+       dynar_getopt_lex_init(&lex, &dynar_long_opt);
+
+       while (dynar_getopt_lex_token_next(&lex) == 0 && strcmp(dynar_data(&lex.option), "") != 0) {
+               opt = dynar_data(&lex.option);
+               val = dynar_data(&lex.value);
+
+               res = qdevice_advanced_settings_set(advanced_settings, opt, val);
+               switch (res) {
+               case -1:
+                       errx(1, "Unknown option '%s'", opt);
+                       break;
+               case -2:
+                       errx(1, "Invalid value '%s' for option '%s'", val, opt);
+                       break;
+               }
+       }
+
+       dynar_getopt_lex_destroy(&lex);
+       dynar_destroy(&dynar_long_opt);
+}
+
+static void
+cli_parse(int argc, char * const argv[], int *foreground, int *force_debug,
+    struct qdevice_advanced_settings *advanced_settings)
+{
+       int ch;
+
+       *foreground = 0;
+       *force_debug = 0;
+
+       while ((ch = getopt(argc, argv, "dfhS:")) != -1) {
+               switch (ch) {
+               case 'd':
+                       *force_debug = 1;
+                       break;
+               case 'f':
+                       *foreground = 1;
+                       break;
+               case 'S':
+                       cli_parse_long_opt(advanced_settings, optarg);
+                       break;
+               case 'h':
+               case '?':
+                       usage();
+                       exit(1);
+                       break;
+               }
+       }
+}
+
+int
+main(int argc, char * const argv[])
+{
+       struct qdevice_instance instance;
+       struct qdevice_advanced_settings advanced_settings;
+       int foreground;
+       int force_debug;
+       int lock_file;
+       int another_instance_running;
+
+       if (qdevice_advanced_settings_init(&advanced_settings) != 0) {
+               errx(1, "Can't alloc memory for advanced settings");
+       }
+
+       cli_parse(argc, argv, &foreground, &force_debug, &advanced_settings);
+
+       qdevice_instance_init(&instance, &advanced_settings);
+
+       qdevice_heuristics_init(&instance.heuristics_instance, &advanced_settings);
+       instance.heuristics_instance.qdevice_instance_ptr = &instance;
+
+       qdevice_cmap_init(&instance);
+       qdevice_log_init(&instance, force_debug);
+
+       /*
+        * Daemonize
+        */
+       if (!foreground) {
+               utils_tty_detach();
+       }
+
+       if ((lock_file = utils_flock(advanced_settings.lock_file, getpid(),
+           &another_instance_running)) == -1) {
+               if (another_instance_running) {
+                       qdevice_log(LOG_ERR, "Another instance is running");
+               } else {
+                       qdevice_log_err(LOG_ERR, "Can't acquire lock");
+               }
+
+               exit(1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Initializing votequorum");
+       qdevice_votequorum_init(&instance);
+
+       qdevice_log(LOG_DEBUG, "Initializing local socket");
+       if (qdevice_ipc_init(&instance) != 0) {
+               return (1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Registering qdevice models");
+       qdevice_model_register_all();
+
+       qdevice_log(LOG_DEBUG, "Configuring qdevice");
+       if (qdevice_instance_configure_from_cmap(&instance) != 0) {
+               return (1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Configuring master_wins");
+       if (qdevice_votequorum_master_wins(&instance, (advanced_settings.master_wins ==
+           QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_ON ? 1 : 0)) != 0) {
+               return (1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Getting configuration node list");
+       if (qdevice_cmap_store_config_node_list(&instance) != 0) {
+               return (1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Initializing qdevice model");
+       if (qdevice_model_init(&instance) != 0) {
+               return (1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Initializing cmap tracking");
+       if (qdevice_cmap_add_track(&instance) != 0) {
+               return (1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Waiting for ring id");
+       if (qdevice_votequorum_wait_for_ring_id(&instance) != 0) {
+               return (1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Waiting for initial heuristics exec result");
+       if (qdevice_heuristics_wait_for_initial_exec_result(&instance.heuristics_instance) != 0) {
+               return (1);
+       }
+
+       global_instance = &instance;
+       signal_handlers_register();
+
+       qdevice_log(LOG_DEBUG, "Running qdevice model");
+#ifdef HAVE_LIBSYSTEMD
+       sd_notify (0, "READY=1");
+#endif
+       if (qdevice_model_run(&instance) != 0) {
+               return (1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Removing cmap tracking");
+       if (qdevice_cmap_del_track(&instance) != 0) {
+               return (1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Destroying qdevice model");
+       qdevice_model_destroy(&instance);
+
+       qdevice_log(LOG_DEBUG, "Destroying qdevice ipc");
+       qdevice_ipc_destroy(&instance);
+
+       qdevice_log(LOG_DEBUG, "Destroying votequorum and cmap");
+       qdevice_votequorum_destroy(&instance);
+       qdevice_cmap_destroy(&instance);
+
+       qdevice_log(LOG_DEBUG, "Destroying heuristics");
+       qdevice_heuristics_destroy(&instance.heuristics_instance);
+
+       qdevice_log(LOG_DEBUG, "Closing log");
+       qdevice_log_close(&instance);
+
+       qdevice_instance_destroy(&instance);
+
+       qdevice_advanced_settings_destroy(&advanced_settings);
+
+       return (0);
+}
diff --git a/qdevices/corosync-qnetd-certutil.sh b/qdevices/corosync-qnetd-certutil.sh
new file mode 100644 (file)
index 0000000..726f482
--- /dev/null
@@ -0,0 +1,215 @@
+#!@BASHPATH@
+
+#
+# Copyright (c) 2015-2016 Red Hat, Inc.
+#
+# All rights reserved.
+#
+# Author: Jan Friesse (jfriesse@redhat.com)
+#
+# This software licensed under BSD license, the text of which follows:
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+CONFIG_DIR="@COROSYSCONFDIR@/qnetd"
+DB_DIR="$CONFIG_DIR/nssdb"
+# Validity of certificate (months)
+CRT_VALIDITY=1200
+CA_NICKNAME="QNet CA"
+SERVER_NICKNAME="QNetd Cert"
+CLUSTER_NICKNAME="Cluster Cert"
+CA_SUBJECT="CN=QNet CA"
+SERVER_SUBJECT="CN=Qnetd Server"
+PWD_FILE="$DB_DIR/pwdfile.txt"
+NOISE_FILE="$DB_DIR/noise.txt"
+SERIAL_NO_FILE="$DB_DIR/serial.txt"
+CA_EXPORT_FILE="$DB_DIR/qnetd-cacert.crt"
+CRT_FILE_BASE="" # Generated from cluster name
+
+usage() {
+    echo "$0: [-i|-s] [-c certificate] [-n cluster_name]"
+    echo
+    echo " -i                  Initialize QNetd CA and generate server certificate"
+    echo " -s                  Sign cluster certificate (needs cluster certificate)"
+    echo " -c certificate      CRQ certificate file name"
+    echo " -n cluster_name     Name of cluster (for -s operation)"
+
+    exit 0
+}
+
+chown_ref_cfgdir() {
+    if [ "$UID" == "0" ];then
+        chown --reference="$CONFIG_DIR" "$@" 2>/dev/null || chown `stat -f "%u:%g" "$CONFIG_DIR"` "$@" 2>/dev/null || return $?
+    fi
+}
+
+create_new_noise_file() {
+    local noise_file="$1"
+
+    if [ ! -e "$noise_file" ];then
+        echo "Creating new noise file $noise_file"
+
+        (ps -elf; date; w) | sha1sum | (read sha_sum rest; echo $sha_sum) > "$noise_file"
+
+        chown_ref_cfgdir "$noise_file"
+        chmod 0660 "$noise_file"
+    else
+        echo "Using existing noise file $noise_file"
+    fi
+}
+
+get_serial_no() {
+    local serial_no
+
+    if ! [ -f "$SERIAL_NO_FILE" ];then
+        echo "100" > $SERIAL_NO_FILE
+        chown_ref_cfgdir "$SERIAL_NO_FILE"
+        chmod 0660 "$SERIAL_NO_FILE"
+    fi
+    serial_no=`cat $SERIAL_NO_FILE`
+    serial_no=$((serial_no+1))
+    echo "$serial_no" > $SERIAL_NO_FILE
+    echo "$serial_no"
+}
+
+init_qnetd_ca() {
+    if [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database ($DB_DIR) already exists. Delete it to initialize new db" >&2
+
+        exit 1
+    fi
+
+    if ! [ -d "$DB_DIR" ];then
+        echo "Creating $DB_DIR"
+        mkdir -p "$DB_DIR"
+        chown_ref_cfgdir "$DB_DIR"
+        chmod 0770 "$DB_DIR"
+    fi
+
+    echo "Creating new key and cert db"
+    echo -n "" > "$PWD_FILE"
+    chown_ref_cfgdir "$PWD_FILE"
+    chmod 0660 "$PWD_FILE"
+
+    certutil -N -d "$DB_DIR" -f "$PWD_FILE"
+    chown_ref_cfgdir "$DB_DIR/key3.db" "$DB_DIR/cert8.db" "$DB_DIR/secmod.db"
+    chmod 0660 "$DB_DIR/key3.db" "$DB_DIR/cert8.db" "$DB_DIR/secmod.db"
+
+    create_new_noise_file "$NOISE_FILE"
+
+    echo "Creating new CA"
+    # Create self-signed certificate (CA). Asks 3 questions (is this CA, lifetime and critical extension
+    echo -e "y\n0\ny\n" | certutil -S -n "$CA_NICKNAME" -s "$CA_SUBJECT" -x \
+        -t "CT,," -m `get_serial_no` -v $CRT_VALIDITY -d "$DB_DIR" \
+        -z "$NOISE_FILE" -f "$PWD_FILE" -2
+    # Export CA certificate in ascii
+    certutil -L -d "$DB_DIR" -n "$CA_NICKNAME" > "$CA_EXPORT_FILE"
+    certutil -L -d "$DB_DIR" -n "$CA_NICKNAME" -a >> "$CA_EXPORT_FILE"
+    chown_ref_cfgdir "$CA_EXPORT_FILE"
+
+    certutil -S -n "$SERVER_NICKNAME" -s "$SERVER_SUBJECT" -c "$CA_NICKNAME" -t "u,u,u" -m `get_serial_no` \
+        -v $CRT_VALIDITY -d "$DB_DIR" -z "$NOISE_FILE" -f "$PWD_FILE"
+
+    echo "QNetd CA certificate is exported as $CA_EXPORT_FILE"
+}
+
+
+sign_cluster_cert() {
+    if ! [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database doesn't exists. Use $0 -I to create it" >&2
+
+        exit 1
+    fi
+
+    echo "Signing cluster certificate"
+    certutil -C -v "$CRT_VALIDITY" -m `get_serial_no` -i "$CERTIFICATE_FILE" -o "$CRT_FILE" -c "$CA_NICKNAME" -d "$DB_DIR"
+    chown_ref_cfgdir "$CRT_FILE"
+
+    echo "Certificate stored in $CRT_FILE"
+}
+
+
+OPERATION=""
+CERTIFICATE_FILE=""
+CLUSTER_NAME=""
+
+while getopts ":hisc:n:" opt; do
+    case $opt in
+        i)
+            OPERATION=init_qnetd_ca
+            ;;
+        s)
+            OPERATION=sign_cluster_cert
+            ;;
+        h)
+            usage
+            ;;
+        c)
+            CERTIFICATE_FILE="$OPTARG"
+            ;;
+        n)
+            CLUSTER_NAME="$OPTARG"
+            ;;
+        \?)
+            echo "Invalid option: -$OPTARG" >&2
+
+            exit 1
+            ;;
+        :)
+            echo "Option -$OPTARG requires an argument." >&2
+
+            exit 1
+            ;;
+   esac
+done
+
+[ "$OPERATION" == "" ] && usage
+
+CRT_FILE="$DB_DIR/cluster-$CLUSTER_NAME.crt"
+
+case "$OPERATION" in
+    "init_qnetd_ca")
+        init_qnetd_ca
+    ;;
+    "sign_cluster_cert")
+        if ! [ -e "$CERTIFICATE_FILE" ];then
+            echo "Can't open certificate file $CERTIFICATE_FILE" >&2
+
+            exit 2
+        fi
+
+        if [ "$CLUSTER_NAME" == "" ];then
+            echo "You have to specify cluster name" >&2
+
+            exit 2
+        fi
+
+        sign_cluster_cert
+    ;;
+    *)
+        usage
+    ;;
+esac
diff --git a/qdevices/corosync-qnetd-tool.c b/qdevices/corosync-qnetd-tool.c
new file mode 100644 (file)
index 0000000..e52d1ce
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "qnet-config.h"
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "utils.h"
+#include "unix-socket.h"
+
+#define IPC_READ_BUF_SIZE      512
+
+enum qnetd_tool_operation {
+       QNETD_TOOL_OPERATION_NONE,
+       QNETD_TOOL_OPERATION_SHUTDOWN,
+       QNETD_TOOL_OPERATION_STATUS,
+       QNETD_TOOL_OPERATION_LIST,
+};
+
+enum qnetd_tool_exit_code {
+       QNETD_TOOL_EXIT_CODE_NO_ERROR = 0,
+       QNETD_TOOL_EXIT_CODE_USAGE = 1,
+       QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR = 2,
+       QNETD_TOOL_EXIT_CODE_SOCKET_CONNECT = 3,
+       QNETD_TOOL_EXIT_CODE_QNETD_RETURNED_ERROR = 4,
+};
+
+static void
+usage(void)
+{
+
+       printf("usage: %s [-Hhlsv] [-c cluster_name] [-p qnetd_ipc_socket_path]\n",
+           QNETD_TOOL_PROGRAM_NAME);
+}
+
+static void
+cli_parse(int argc, char * const argv[], enum qnetd_tool_operation *operation,
+    int *verbose, char **cluster_name, char **socket_path)
+{
+       int ch;
+
+       *operation = QNETD_TOOL_OPERATION_NONE;
+       *verbose = 0;
+       *cluster_name = NULL;
+       *socket_path = strdup(QNETD_DEFAULT_LOCAL_SOCKET_FILE);
+
+       if (*socket_path == NULL) {
+               errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR,
+                   "Can't alloc memory for socket path string");
+       }
+
+       while ((ch = getopt(argc, argv, "Hhlsvc:p:")) != -1) {
+               switch (ch) {
+               case 'H':
+                       *operation = QNETD_TOOL_OPERATION_SHUTDOWN;
+                       break;
+               case 'l':
+                       *operation = QNETD_TOOL_OPERATION_LIST;
+                       break;
+               case 's':
+                       *operation = QNETD_TOOL_OPERATION_STATUS;
+                       break;
+               case 'v':
+                       *verbose = 1;
+                       break;
+               case 'c':
+                       free(*cluster_name);
+                       *cluster_name = strdup(optarg);
+                       if (*cluster_name == NULL) {
+                               errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR,
+                                   "Can't alloc memory for cluster name string");
+                       }
+                       break;
+               case 'p':
+                       free(*socket_path);
+                       *socket_path = strdup(optarg);
+                       if (*socket_path == NULL) {
+                               errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR,
+                                   "Can't alloc memory for socket path string");
+                       }
+                       break;
+               case 'h':
+               case '?':
+                       usage();
+                       exit(QNETD_TOOL_EXIT_CODE_USAGE);
+                       break;
+               }
+       }
+
+       if (*operation == QNETD_TOOL_OPERATION_NONE) {
+               usage();
+               exit(QNETD_TOOL_EXIT_CODE_USAGE);
+       }
+}
+
+static int
+store_command(struct dynar *str, enum qnetd_tool_operation operation, int verbose,
+    const char *cluster_name)
+{
+       const char *nline = "\n\0";
+       const int nline_len = 2;
+
+       switch (operation) {
+       case QNETD_TOOL_OPERATION_NONE:
+               errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Unhandled operation none");
+               break;
+       case QNETD_TOOL_OPERATION_SHUTDOWN:
+               if (dynar_str_cat(str, "shutdown ") != 0) {
+                       return (-1);
+               }
+               break;
+       case QNETD_TOOL_OPERATION_STATUS:
+               if (dynar_str_cat(str, "status ") != 0) {
+                       return (-1);
+               }
+               break;
+       case QNETD_TOOL_OPERATION_LIST:
+               if (dynar_str_cat(str, "list ") != 0) {
+                       return (-1);
+               }
+               break;
+       }
+
+       if (verbose) {
+               if (dynar_str_cat(str, "verbose ") != 0) {
+                       return (-1);
+               }
+       }
+
+       if (cluster_name != NULL) {
+               if (dynar_str_cat(str, "cluster ") != 0 ||
+                   dynar_str_quote_cat(str, cluster_name) != 0 ||
+                   dynar_str_cat(str, " ") != 0) {
+                       return (-1);
+               }
+       }
+
+       if (dynar_cat(str, nline, nline_len) != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+/*
+ * -1 - Internal error (can't alloc memory)
+ *  0 - No error
+ *  1 - IPC returned error
+ *  2 - Unknown status line
+ */
+static int
+read_ipc_reply(FILE *f)
+{
+       struct dynar read_str;
+       int ch;
+       int status_readed;
+       int res;
+       static const char *ok_str = "OK";
+       static const char *err_str = "Error";
+       int err_set;
+       char c;
+
+       dynar_init(&read_str, IPC_READ_BUF_SIZE);
+
+       status_readed = 0;
+       err_set = 0;
+       res = 0;
+
+       while ((ch = fgetc(f)) != EOF) {
+               if (status_readed) {
+                       putc(ch, (err_set ? stderr : stdout));
+               } else {
+                       if (ch == '\r') {
+                       } else if (ch == '\n') {
+                               status_readed = 1;
+
+                               c = '\0';
+                               if (dynar_cat(&read_str, &c, sizeof(c)) != 0) {
+                                       res = -1;
+                                       goto exit_destroy;
+                               }
+
+                               if (strcasecmp(dynar_data(&read_str), ok_str) == 0) {
+                               } else if (strcasecmp(dynar_data(&read_str), err_str) == 0) {
+                                       err_set = 1;
+                                       res = 1;
+                                       fprintf(stderr, "Error: ");
+                               } else {
+                                       res = 2;
+                                       goto exit_destroy;
+                               }
+                       } else {
+                               c = ch;
+                               if (dynar_cat(&read_str, &c, sizeof(c)) != 0) {
+                                       res = -1;
+                                       goto exit_destroy;
+                               }
+                       }
+               }
+       }
+
+exit_destroy:
+       dynar_destroy(&read_str);
+
+       return (res);
+}
+
+int
+main(int argc, char * const argv[])
+{
+       enum qnetd_tool_operation operation;
+       int verbose;
+       char *cluster_name;
+       char *socket_path;
+       int sock_fd;
+       FILE *sock;
+       struct dynar send_str;
+       int res;
+       int exit_code;
+
+       exit_code = QNETD_TOOL_EXIT_CODE_NO_ERROR;
+
+       cli_parse(argc, argv, &operation, &verbose, &cluster_name, &socket_path);
+
+       dynar_init(&send_str, QNETD_DEFAULT_IPC_MAX_RECEIVE_SIZE);
+
+       sock_fd = unix_socket_client_create(socket_path, 0);
+       if (sock_fd == -1) {
+               err(QNETD_TOOL_EXIT_CODE_SOCKET_CONNECT,
+                   "Can't connect to qnetd socket (is QNetd running?)");
+       }
+
+       sock = fdopen(sock_fd, "w+t");
+       if (sock == NULL) {
+               err(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't open QNetd socket fd");
+       }
+
+       if (store_command(&send_str, operation, verbose, cluster_name) != 0) {
+               errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't store command");
+       }
+
+       res = fprintf(sock, "%s", dynar_data(&send_str));
+       if (res < 0 || (size_t)res != strlen(dynar_data(&send_str)) ||
+           fflush(sock) != 0) {
+               errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't send command");
+       }
+
+       res = read_ipc_reply(sock);
+       switch (res) {
+       case -1:
+               errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Internal error during IPC status line read");
+               break;
+       case 0:
+               break;
+       case 1:
+               exit_code = QNETD_TOOL_EXIT_CODE_QNETD_RETURNED_ERROR;
+               break;
+       case 2:
+               errx(QNETD_TOOL_EXIT_CODE_SOCKET_CONNECT, "Unknown status line returned by IPC server");
+               break;
+       }
+
+       if (fclose(sock) != 0) {
+               warn("Can't close QNetd socket");
+       }
+
+       free(cluster_name);
+       free(socket_path);
+       dynar_destroy(&send_str);
+
+       return (exit_code);
+}
diff --git a/qdevices/corosync-qnetd.c b/qdevices/corosync-qnetd.c
new file mode 100644 (file)
index 0000000..45cf5db
--- /dev/null
@@ -0,0 +1,662 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "qnet-config.h"
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "dynar-getopt-lex.h"
+#include "nss-sock.h"
+#include "pr-poll-array.h"
+#include "qnetd-advanced-settings.h"
+#include "qnetd-algorithm.h"
+#include "qnetd-instance.h"
+#include "qnetd-ipc.h"
+#include "qnetd-log.h"
+#include "qnetd-client-net.h"
+#include "qnetd-client-msg-received.h"
+#include "qnetd-poll-array-user-data.h"
+#include "utils.h"
+#include "msg.h"
+
+#ifdef HAVE_LIBSYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+/*
+ * This is global variable used for comunication with main loop and signal (calls close)
+ */
+struct qnetd_instance *global_instance;
+
+enum tlv_decision_algorithm_type
+    qnetd_static_supported_decision_algorithms[QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE] = {
+       TLV_DECISION_ALGORITHM_TYPE_TEST,
+       TLV_DECISION_ALGORITHM_TYPE_FFSPLIT,
+       TLV_DECISION_ALGORITHM_TYPE_2NODELMS,
+       TLV_DECISION_ALGORITHM_TYPE_LMS,
+};
+
+static void
+qnetd_err_nss(void)
+{
+
+       qnetd_log_nss(LOG_CRIT, "NSS error");
+
+       exit(1);
+}
+
+static void
+qnetd_warn_nss(void)
+{
+
+       qnetd_log_nss(LOG_WARNING, "NSS warning");
+}
+
+static PRPollDesc *
+qnetd_pr_poll_array_create(struct qnetd_instance *instance)
+{
+       struct pr_poll_array *poll_array;
+       const struct qnetd_client_list *client_list;
+       struct qnetd_client *client;
+       PRPollDesc *poll_desc;
+       struct qnetd_poll_array_user_data *user_data;
+       const struct unix_socket_client_list *ipc_client_list;
+       struct unix_socket_client *ipc_client;
+
+       poll_array = &instance->poll_array;
+       client_list = &instance->clients;
+       ipc_client_list = &instance->local_ipc.clients;
+
+       pr_poll_array_clean(poll_array);
+
+       if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+               return (NULL);
+       }
+
+       poll_desc->fd = instance->server.socket;
+       poll_desc->in_flags = PR_POLL_READ;
+
+       user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET;
+
+       if (qnetd_ipc_is_closed(instance)) {
+               qnetd_log(LOG_DEBUG, "Listening socket is closed");
+
+               return (NULL);
+       }
+
+       if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+               return (NULL);
+       }
+
+       poll_desc->fd = instance->ipc_socket_poll_fd;
+       poll_desc->in_flags = PR_POLL_READ;
+       user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET;
+
+       TAILQ_FOREACH(client, client_list, entries) {
+               if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+                       return (NULL);
+               }
+               poll_desc->fd = client->socket;
+               poll_desc->in_flags = PR_POLL_READ;
+
+               if (!send_buffer_list_empty(&client->send_buffer_list)) {
+                       poll_desc->in_flags |= PR_POLL_WRITE;
+               }
+
+               user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT;
+               user_data->client = client;
+       }
+
+       TAILQ_FOREACH(ipc_client, ipc_client_list, entries) {
+               if (!ipc_client->reading_line && !ipc_client->writing_buffer) {
+                       continue;
+               }
+
+               if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+                       return (NULL);
+               }
+
+               poll_desc->fd = ((struct qnetd_ipc_user_data *)ipc_client->user_data)->nspr_poll_fd;
+               if (ipc_client->reading_line) {
+                       poll_desc->in_flags |= PR_POLL_READ;
+               }
+
+               if (ipc_client->writing_buffer) {
+                       poll_desc->in_flags |= PR_POLL_WRITE;
+               }
+
+               user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT;
+               user_data->ipc_client = ipc_client;
+       }
+
+       pr_poll_array_gc(poll_array);
+
+       return (poll_array->array);
+}
+
+static int
+qnetd_poll(struct qnetd_instance *instance)
+{
+       struct qnetd_client *client;
+       PRPollDesc *pfds;
+       PRInt32 poll_res;
+       ssize_t i;
+       int client_disconnect;
+       struct qnetd_poll_array_user_data *user_data;
+       struct unix_socket_client *ipc_client;
+
+       client = NULL;
+       client_disconnect = 0;
+
+       pfds = qnetd_pr_poll_array_create(instance);
+       if (pfds == NULL) {
+               return (-1);
+       }
+
+       if ((poll_res = PR_Poll(pfds, pr_poll_array_size(&instance->poll_array),
+           timer_list_time_to_expire(&instance->main_timer_list))) >= 0) {
+               timer_list_expire(&instance->main_timer_list);
+
+               /*
+                * Walk thru pfds array and process events
+                */
+               for (i = 0; i < pr_poll_array_size(&instance->poll_array); i++) {
+                       user_data = pr_poll_array_get_user_data(&instance->poll_array, i);
+
+                       client = NULL;
+                       ipc_client = NULL;
+                       client_disconnect = 0;
+
+                       switch (user_data->type) {
+                       case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
+                               break;
+                       case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT:
+                               client = user_data->client;
+                               client_disconnect = client->schedule_disconnect;
+                               break;
+                       case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
+                               break;
+                       case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
+                               ipc_client = user_data->ipc_client;
+                               client_disconnect = ipc_client->schedule_disconnect;
+                       }
+
+                       if (!client_disconnect && poll_res > 0 &&
+                           pfds[i].out_flags & PR_POLL_READ) {
+                               switch (user_data->type) {
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
+                                       qnetd_client_net_accept(instance);
+                                       break;
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT:
+                                       if (qnetd_client_net_read(instance, client) == -1) {
+                                               client_disconnect = 1;
+                                       }
+                                       break;
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
+                                       qnetd_ipc_accept(instance, &ipc_client);
+                                       break;
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
+                                       qnetd_ipc_io_read(instance, ipc_client);
+                                       break;
+                               }
+                       }
+
+                       if (!client_disconnect && poll_res > 0 &&
+                           pfds[i].out_flags & PR_POLL_WRITE) {
+                               switch (user_data->type) {
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
+                                       /*
+                                        * Poll write on listen socket -> fatal error
+                                        */
+                                       qnetd_log(LOG_CRIT, "POLL_WRITE on listening socket");
+
+                                       return (-1);
+                                       break;
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT:
+                                       if (qnetd_client_net_write(instance, client) == -1) {
+                                               client_disconnect = 1;
+                                       }
+                                       break;
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
+                                       qnetd_log(LOG_CRIT, "POLL_WRITE on listening IPC socket");
+                                       return (-1);
+                                       break;
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
+                                       qnetd_ipc_io_write(instance, ipc_client);
+                                       break;
+                               }
+                       }
+
+                       if (!client_disconnect && poll_res > 0 &&
+                           (pfds[i].out_flags & (PR_POLL_ERR|PR_POLL_NVAL|PR_POLL_HUP|PR_POLL_EXCEPT)) &&
+                           !(pfds[i].out_flags & (PR_POLL_READ|PR_POLL_WRITE))) {
+                               switch (user_data->type) {
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
+                                       if (pfds[i].out_flags != PR_POLL_NVAL) {
+                                               /*
+                                                * Poll ERR on listening socket is fatal error.
+                                                * POLL_NVAL is used as a signal to quit poll loop.
+                                                */
+                                                qnetd_log(LOG_CRIT, "POLL_ERR (%u) on listening "
+                                                   "socket", pfds[i].out_flags);
+                                       } else {
+                                               qnetd_log(LOG_DEBUG, "Listening socket is closed");
+                                       }
+
+                                       return (-1);
+                                       break;
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT:
+                                       qnetd_log(LOG_DEBUG, "POLL_ERR (%u) on client socket. "
+                                           "Disconnecting.", pfds[i].out_flags);
+
+                                       client_disconnect = 1;
+                                       break;
+                               case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
+                                       qnetd_log(LOG_DEBUG, "POLL_ERR (%u) on ipc client socket."
+                                           " Disconnecting.", pfds[i].out_flags);
+
+                                       client_disconnect = 1;
+                                       break;
+                               }
+                       }
+
+                       /*
+                        * If client is scheduled for disconnect, disconnect it
+                        */
+                       if (user_data->type == QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT &&
+                           client_disconnect) {
+                               qnetd_instance_client_disconnect(instance, client, 0);
+                       } else if (user_data->type == QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT &&
+                           (client_disconnect || ipc_client->schedule_disconnect)) {
+                               qnetd_ipc_client_disconnect(instance, ipc_client);
+                       }
+               }
+       }
+
+
+       return (0);
+}
+
+static void
+signal_int_handler(int sig)
+{
+
+       qnetd_log(LOG_DEBUG, "SIGINT received - closing server IPC socket");
+
+       qnetd_ipc_close(global_instance);
+}
+
+static void
+signal_term_handler(int sig)
+{
+
+       qnetd_log(LOG_DEBUG, "SIGTERM received - closing server IPC socket");
+
+       qnetd_ipc_close(global_instance);
+}
+
+static void
+signal_handlers_register(void)
+{
+       struct sigaction act;
+
+       act.sa_handler = signal_int_handler;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGINT, &act, NULL);
+
+       act.sa_handler = signal_term_handler;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGTERM, &act, NULL);
+}
+
+static void
+usage(void)
+{
+
+       printf("usage: %s [-46dfhv] [-l listen_addr] [-p listen_port] [-s tls]\n", QNETD_PROGRAM_NAME);
+       printf("%14s[-c client_cert_required] [-m max_clients] [-S option=value[,option2=value2,...]]\n", "");
+}
+
+static void
+display_version(void)
+{
+       enum msg_type *supported_messages;
+       size_t no_supported_messages;
+       size_t zi;
+
+       msg_get_supported_messages(&supported_messages, &no_supported_messages);
+       printf("Corosync Qdevice Network Daemon, version '%s'\n\n", VERSION);
+       printf("Supported algorithms: ");
+       for (zi = 0; zi < QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE; zi++) {
+               if (zi != 0) {
+                       printf(", ");
+               }
+               printf("%s (%u)",
+                   tlv_decision_algorithm_type_to_str(qnetd_static_supported_decision_algorithms[zi]),
+                   qnetd_static_supported_decision_algorithms[zi]);
+       }
+       printf("\n");
+       printf("Supported message types: ");
+       for (zi = 0; zi < no_supported_messages; zi++) {
+               if (zi != 0) {
+                       printf(", ");
+               }
+               printf("%s (%u)", msg_type_to_str(supported_messages[zi]), supported_messages[zi]);
+       }
+       printf("\n");
+}
+
+static void
+cli_parse_long_opt(struct qnetd_advanced_settings *advanced_settings, const char *long_opt)
+{
+       struct dynar_getopt_lex lex;
+       struct dynar dynar_long_opt;
+       const char *opt;
+       const char *val;
+       int res;
+
+       dynar_init(&dynar_long_opt, strlen(long_opt) + 1);
+       if (dynar_str_cpy(&dynar_long_opt, long_opt) != 0) {
+               errx(1, "Can't alloc memory for long option");
+       }
+
+       dynar_getopt_lex_init(&lex, &dynar_long_opt);
+
+       while (dynar_getopt_lex_token_next(&lex) == 0 && strcmp(dynar_data(&lex.option), "") != 0) {
+               opt = dynar_data(&lex.option);
+               val = dynar_data(&lex.value);
+
+               res = qnetd_advanced_settings_set(advanced_settings, opt, val);
+               switch (res) {
+               case -1:
+                       errx(1, "Unknown option '%s'", opt);
+                       break;
+               case -2:
+                       errx(1, "Invalid value '%s' for option '%s'", val, opt);
+                       break;
+               }
+       }
+
+       dynar_getopt_lex_destroy(&lex);
+       dynar_destroy(&dynar_long_opt);
+}
+
+static void
+cli_parse(int argc, char * const argv[], char **host_addr, uint16_t *host_port, int *foreground,
+    int *debug_log, int *bump_log_priority, enum tlv_tls_supported *tls_supported,
+    int *client_cert_required, size_t *max_clients, PRIntn *address_family,
+    struct qnetd_advanced_settings *advanced_settings)
+{
+       int ch;
+       char *ep;
+       long long int tmpll;
+
+       *host_addr = NULL;
+       *host_port = QNETD_DEFAULT_HOST_PORT;
+       *foreground = 0;
+       *debug_log = 0;
+       *bump_log_priority = 0;
+       *tls_supported = QNETD_DEFAULT_TLS_SUPPORTED;
+       *client_cert_required = QNETD_DEFAULT_TLS_CLIENT_CERT_REQUIRED;
+       *max_clients = QNETD_DEFAULT_MAX_CLIENTS;
+       *address_family = PR_AF_UNSPEC;
+
+       while ((ch = getopt(argc, argv, "46dfhvc:l:m:p:S:s:")) != -1) {
+               switch (ch) {
+               case '4':
+                       *address_family = PR_AF_INET;
+                       break;
+               case '6':
+                       *address_family = PR_AF_INET6;
+                       break;
+               case 'f':
+                       *foreground = 1;
+                       break;
+               case 'd':
+                       if (*debug_log) {
+                               *bump_log_priority = 1;
+                       }
+                       *debug_log = 1;
+                       break;
+               case 'c':
+                       if ((*client_cert_required = utils_parse_bool_str(optarg)) == -1) {
+                               errx(1, "client_cert_required should be on/yes/1, off/no/0");
+                       }
+                       break;
+               case 'l':
+                       free(*host_addr);
+                       *host_addr = strdup(optarg);
+                       if (*host_addr == NULL) {
+                               errx(1, "Can't alloc memory for host addr string");
+                       }
+                       break;
+               case 'm':
+                       errno = 0;
+
+                       tmpll = strtoll(optarg, &ep, 10);
+                       if (tmpll < 0 || errno != 0 || *ep != '\0') {
+                               errx(1, "max clients value %s is invalid", optarg);
+                       }
+                       *max_clients = (size_t)tmpll;
+                       break;
+               case 'p':
+                       *host_port = strtol(optarg, &ep, 10);
+                       if (*host_port <= 0 || *host_port > ((uint16_t)~0) || *ep != '\0') {
+                               errx(1, "host port must be in range 0-65535");
+                       }
+                       break;
+               case 'S':
+                       cli_parse_long_opt(advanced_settings, optarg);
+                       break;
+               case 's':
+                       if (strcasecmp(optarg, "on") == 0) {
+                               *tls_supported = QNETD_DEFAULT_TLS_SUPPORTED;
+                       } else if (strcasecmp(optarg, "off") == 0) {
+                               *tls_supported = TLV_TLS_UNSUPPORTED;
+                       } else if (strcasecmp(optarg, "req") == 0) {
+                               *tls_supported = TLV_TLS_REQUIRED;
+                       } else {
+                               errx(1, "tls must be one of on, off, req");
+                       }
+                       break;
+               case 'v':
+                       display_version();
+                       exit(1);
+                       break;
+               case 'h':
+               case '?':
+                       usage();
+                       exit(1);
+                       break;
+               }
+       }
+}
+
+int
+main(int argc, char * const argv[])
+{
+       struct qnetd_instance instance;
+       struct qnetd_advanced_settings advanced_settings;
+       char *host_addr;
+       uint16_t host_port;
+       int foreground;
+       int debug_log;
+       int bump_log_priority;
+       enum tlv_tls_supported tls_supported;
+       int client_cert_required;
+       size_t max_clients;
+       PRIntn address_family;
+       int lock_file;
+       int another_instance_running;
+
+       if (qnetd_advanced_settings_init(&advanced_settings) != 0) {
+               errx(1, "Can't alloc memory for advanced settings");
+       }
+
+       cli_parse(argc, argv, &host_addr, &host_port, &foreground, &debug_log, &bump_log_priority,
+           &tls_supported, &client_cert_required, &max_clients, &address_family, &advanced_settings);
+
+       if (foreground) {
+               qnetd_log_init(QNETD_LOG_TARGET_STDERR);
+       } else {
+               qnetd_log_init(QNETD_LOG_TARGET_SYSLOG);
+       }
+
+       qnetd_log_set_debug(debug_log);
+       qnetd_log_set_priority_bump(bump_log_priority);
+
+       /*
+        * Daemonize
+        */
+       if (!foreground) {
+               utils_tty_detach();
+       }
+
+       if ((lock_file = utils_flock(advanced_settings.lock_file, getpid(),
+           &another_instance_running)) == -1) {
+               if (another_instance_running) {
+                       qnetd_log(LOG_ERR, "Another instance is running");
+               } else {
+                       qnetd_log_err(LOG_ERR, "Can't acquire lock");
+               }
+
+               exit(1);
+       }
+
+       qnetd_log(LOG_DEBUG, "Initializing nss");
+       if (nss_sock_init_nss((tls_supported != TLV_TLS_UNSUPPORTED ?
+           advanced_settings.nss_db_dir : NULL)) != 0) {
+               qnetd_err_nss();
+       }
+
+       if (SSL_ConfigServerSessionIDCache(0, 0, 0, NULL) != SECSuccess) {
+               qnetd_err_nss();
+       }
+
+       if (qnetd_instance_init(&instance, tls_supported, client_cert_required,
+           max_clients, &advanced_settings) == -1) {
+               qnetd_log(LOG_ERR, "Can't initialize qnetd");
+               exit(1);
+       }
+       instance.host_addr = host_addr;
+       instance.host_port = host_port;
+
+       if (tls_supported != TLV_TLS_UNSUPPORTED && qnetd_instance_init_certs(&instance) == -1) {
+               qnetd_err_nss();
+       }
+
+       qnetd_log(LOG_DEBUG, "Initializing local socket");
+       if (qnetd_ipc_init(&instance) != 0) {
+               return (1);
+       }
+
+       qnetd_log(LOG_DEBUG, "Creating listening socket");
+       instance.server.socket = nss_sock_create_listen_socket(instance.host_addr,
+           instance.host_port, address_family);
+       if (instance.server.socket == NULL) {
+               qnetd_err_nss();
+       }
+
+       if (nss_sock_set_non_blocking(instance.server.socket) != 0) {
+               qnetd_err_nss();
+       }
+
+       if (PR_Listen(instance.server.socket, instance.advanced_settings->listen_backlog) !=
+           PR_SUCCESS) {
+               qnetd_err_nss();
+       }
+
+       global_instance = &instance;
+       signal_handlers_register();
+
+       qnetd_log(LOG_DEBUG, "Registering algorithms");
+       if (qnetd_algorithm_register_all() != 0) {
+               exit(1);
+       }
+
+       qnetd_log(LOG_DEBUG, "QNetd ready to provide service");
+
+#ifdef HAVE_LIBSYSTEMD
+       sd_notify(0, "READY=1");
+#endif
+
+       /*
+        * MAIN LOOP
+        */
+       while (qnetd_poll(&instance) == 0) {
+       }
+
+       /*
+        * Cleanup
+        */
+       qnetd_ipc_destroy(&instance);
+
+       if (PR_Close(instance.server.socket) != PR_SUCCESS) {
+               qnetd_warn_nss();
+       }
+
+       CERT_DestroyCertificate(instance.server.cert);
+       SECKEY_DestroyPrivateKey(instance.server.private_key);
+
+       SSL_ClearSessionCache();
+
+       SSL_ShutdownServerSessionIDCache();
+
+       qnetd_instance_destroy(&instance);
+
+       qnetd_advanced_settings_destroy(&advanced_settings);
+
+       if (NSS_Shutdown() != SECSuccess) {
+               qnetd_warn_nss();
+       }
+
+       if (PR_Cleanup() != PR_SUCCESS) {
+               qnetd_warn_nss();
+       }
+
+       qnetd_log_close();
+
+       return (0);
+}
diff --git a/qdevices/dynar-getopt-lex.c b/qdevices/dynar-getopt-lex.c
new file mode 100644 (file)
index 0000000..3ed3a8c
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "dynar-getopt-lex.h"
+
+void
+dynar_getopt_lex_init(struct dynar_getopt_lex *lex, struct dynar *input)
+{
+
+       memset(lex, 0, sizeof(*lex));
+       lex->input = input;
+       dynar_init(&lex->option, dynar_max_size(input));
+       dynar_init(&lex->value, dynar_max_size(input));
+}
+
+void
+dynar_getopt_lex_destroy(struct dynar_getopt_lex *lex)
+{
+
+       dynar_destroy(&lex->option);
+       dynar_destroy(&lex->value);
+       memset(lex, 0, sizeof(*lex));
+}
+
+/*
+ * 0 - no error
+ * -1 - Can't add character
+ */
+int
+dynar_getopt_lex_token_next(struct dynar_getopt_lex *lex)
+{
+       size_t pos;
+       size_t size;
+       char *str;
+       char ch;
+       int state;
+
+       dynar_clean(&lex->option);
+       dynar_clean(&lex->value);
+
+       size = dynar_size(lex->input);
+       str = dynar_data(lex->input);
+
+       state = 1;
+       pos = lex->pos;
+
+       while (state != 0 && pos < size) {
+               ch = str[pos];
+
+               switch (state) {
+               case 1:
+                       /*
+                        * Read option name, wait for = or ,
+                        */
+                       if (ch == '=') {
+                               pos++;
+                               state = 2;
+                       } else if (ch == ',') {
+                               pos++;
+                               state = 0;
+                       } else {
+                               pos++;
+                               if (dynar_cat(&lex->option, &ch, sizeof(ch)) != 0) {
+                                       return (-1);
+                               }
+                       }
+                       break;
+               case 2:
+                       /*
+                        * Wait for end of str or ,
+                        */
+                       if (ch == ',') {
+                               pos++;
+                               state = 0;
+                       } else {
+                               pos++;
+                               if (dynar_cat(&lex->value, &ch, sizeof(ch)) != 0) {
+                                       return (-1);
+                               }
+                       }
+                       break;
+               }
+       }
+
+       ch = '\0';
+       if (dynar_cat(&lex->option, &ch, sizeof(ch)) != 0) {
+               return (-1);
+       }
+       if (dynar_cat(&lex->value, &ch, sizeof(ch)) != 0) {
+               return (-1);
+       }
+
+       lex->pos = pos;
+
+       return (0);
+}
diff --git a/qdevices/dynar-getopt-lex.h b/qdevices/dynar-getopt-lex.h
new file mode 100644 (file)
index 0000000..b8ce2f9
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _DYNAR_GETOPT_LEX_H_
+#define _DYNAR_GETOPT_LEX_H_
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct dynar_getopt_lex {
+       struct dynar option;
+       struct dynar value;
+       struct dynar *input;
+       size_t pos;
+};
+
+extern void            dynar_getopt_lex_init(struct dynar_getopt_lex *lex, struct dynar *input);
+
+extern void            dynar_getopt_lex_destroy(struct dynar_getopt_lex *lex);
+
+extern int             dynar_getopt_lex_token_next(struct dynar_getopt_lex *lex);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DYNAR_GETOPT_LEX_H_ */
diff --git a/qdevices/dynar-simple-lex.c b/qdevices/dynar-simple-lex.c
new file mode 100644 (file)
index 0000000..bd5080e
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "dynar-simple-lex.h"
+
+/*
+ * Simple_lex is going to be used in protocol and it's not good idea to depend on locale
+ */
+static int
+dynar_simple_lex_is_space(char ch)
+{
+       return (ch == ' ' || ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\v');
+}
+
+void
+dynar_simple_lex_init(struct dynar_simple_lex *lex, struct dynar *input,
+    enum dynar_simple_lex_type lex_type)
+{
+
+       memset(lex, 0, sizeof(*lex));
+       lex->input = input;
+       lex->lex_type = lex_type;
+       dynar_init(&lex->token, dynar_max_size(input));
+}
+
+void
+dynar_simple_lex_destroy(struct dynar_simple_lex *lex)
+{
+
+       dynar_destroy(&lex->token);
+       memset(lex, 0, sizeof(*lex));
+}
+
+struct dynar *
+dynar_simple_lex_token_next(struct dynar_simple_lex *lex)
+{
+       size_t pos;
+       size_t size;
+       char *str;
+       char ch, ch2;
+       int add_char;
+       int state;
+
+       dynar_clean(&lex->token);
+
+       size = dynar_size(lex->input);
+       str = dynar_data(lex->input);
+
+       state = 1;
+       pos = lex->pos;
+
+       while (state != 0) {
+               if (pos < size) {
+                       ch = str[pos];
+               } else {
+                       ch = '\0';
+               }
+
+               add_char = 0;
+
+               switch (state) {
+               case 1:
+                       /*
+                        * Skip spaces. Newline is special and means end of processing
+                        */
+                       if (pos >= size || ch == '\n' || ch == '\r') {
+                               state = 0;
+                       } else if (dynar_simple_lex_is_space(ch)) {
+                               pos++;
+                       } else {
+                               state = 2;
+                       }
+                       break;
+               case 2:
+                       /*
+                        * Read word
+                        */
+                       if (pos >= size) {
+                               state = 0;
+                       } else if ((lex->lex_type == DYNAR_SIMPLE_LEX_TYPE_BACKSLASH ||
+                           lex->lex_type == DYNAR_SIMPLE_LEX_TYPE_QUOTE) && ch == '\\') {
+                               pos++;
+                               state = 3;
+                       } else if (lex->lex_type == DYNAR_SIMPLE_LEX_TYPE_QUOTE &&
+                           ch == '"') {
+                               pos++;
+                               state = 4;
+                       } else if (dynar_simple_lex_is_space(ch)) {
+                               state = 0;
+                       } else {
+                               pos++;
+                               add_char = 1;
+                       }
+                       break;
+               case 3:
+                       /*
+                        * Process backslash
+                        */
+                       if (pos >= size || ch == '\n' || ch == '\r') {
+                               /*
+                                * End of string. Do not include backslash (it's just ignored)
+                                */
+                               state = 0;
+                       } else {
+                               add_char = 1;
+                               state = 2;
+                               pos++;
+                       }
+                       break;
+               case 4:
+                       /*
+                        * Quote word
+                        */
+                       if (pos >= size) {
+                               state = 0;
+                       } else if (ch == '\\') {
+                               state = 5;
+                               pos++;
+                       } else if (ch == '"') {
+                               state = 2;
+                               pos++;
+                       } else if (ch == '\n' || ch == '\r') {
+                               state = 0;
+                       } else {
+                               pos++;
+                               add_char = 1;
+                       }
+                       break;
+               case 5:
+                       /*
+                        * Quote word backslash
+                        */
+                       if (pos >= size || ch == '\n' || ch == '\r') {
+                               /*
+                                * End of string. Do not include backslash (it's just ignored)
+                                */
+                               state = 0;
+                       } else if (ch == '\\' || ch == '"') {
+                               add_char = 1;
+                               state = 4;
+                               pos++;
+                       } else {
+                               ch2 = '\\';
+                               if (dynar_cat(&lex->token, &ch2, sizeof(ch2)) != 0) {
+                                       return (NULL);
+                               }
+
+                               add_char = 1;
+                               state = 4;
+                               pos++;
+                       }
+                       break;
+               }
+
+               if (add_char) {
+                       if (dynar_cat(&lex->token, &ch, sizeof(ch)) != 0) {
+                               return (NULL);
+                       }
+               }
+       }
+
+       ch = '\0';
+       if (dynar_cat(&lex->token, &ch, sizeof(ch)) != 0) {
+               return (NULL);
+       }
+
+       lex->pos = pos;
+
+       return (&lex->token);
+}
diff --git a/qdevices/dynar-simple-lex.h b/qdevices/dynar-simple-lex.h
new file mode 100644 (file)
index 0000000..029c4a0
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _DYNAR_SIMPLE_LEX_H_
+#define _DYNAR_SIMPLE_LEX_H_
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum dynar_simple_lex_type {
+       DYNAR_SIMPLE_LEX_TYPE_PLAIN,
+       DYNAR_SIMPLE_LEX_TYPE_BACKSLASH,
+       DYNAR_SIMPLE_LEX_TYPE_QUOTE,
+};
+
+struct dynar_simple_lex {
+       struct dynar token;
+       struct dynar *input;
+       enum dynar_simple_lex_type lex_type;
+       size_t pos;
+};
+
+extern void             dynar_simple_lex_init(struct dynar_simple_lex *lex, struct dynar *input,
+    enum dynar_simple_lex_type lex_type);
+
+extern void             dynar_simple_lex_destroy(struct dynar_simple_lex *lex);
+
+extern struct dynar    *dynar_simple_lex_token_next(struct dynar_simple_lex *lex);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DYNAR_SIMPLE_LEX_H_ */
diff --git a/qdevices/dynar-str.c b/qdevices/dynar-str.c
new file mode 100644 (file)
index 0000000..597392e
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "dynar-str.h"
+
+int
+dynar_str_cpy(struct dynar *dest, const char *str)
+{
+
+       if (strlen(str) > dynar_max_size(dest)) {
+               return (-1);
+       }
+
+       dynar_clean(dest);
+
+       return (dynar_str_cat(dest, str));
+}
+
+int
+dynar_str_cat(struct dynar *dest, const char *str)
+{
+
+       return (dynar_cat(dest, str, strlen(str)));
+}
+
+int
+dynar_str_prepend(struct dynar *dest, const char *str)
+{
+
+       return (dynar_prepend(dest, str, strlen(str)));
+}
+
+int
+dynar_str_vcatf(struct dynar *dest, const char *format, va_list ap)
+{
+       int to_write;
+       int written;
+       va_list ap_copy;
+       size_t allocated;
+       char buf;
+       char *p;
+
+       /*
+        * Find out how much bytes is needed
+        */
+       va_copy(ap_copy, ap);
+       to_write = vsnprintf(&buf, sizeof(buf), format, ap_copy);
+       va_end(ap_copy);
+
+       if (to_write < 0) {
+               return (-1);
+       }
+
+       if ((size_t)to_write < sizeof(buf)) {
+               /*
+                * Writing 1 byte string (snprintf writes also '\0') means string is empty
+                */
+
+               return (0);
+       }
+
+       allocated = to_write + 1;
+       if (dynar_prealloc(dest, allocated) != 0) {
+               return (-1);
+       }
+
+       p = dynar_data(dest) + dynar_size(dest);
+
+       va_copy(ap_copy, ap);
+       written = vsnprintf(p, allocated, format, ap_copy);
+       va_end(ap_copy);
+
+       if (written < 0) {
+               return (-1);
+       }
+
+       if ((size_t)written >= allocated) {
+               return (-1);
+       }
+
+       dest->size += written;
+
+       return (written);
+}
+
+int
+dynar_str_catf(struct dynar *dest, const char *format, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, format);
+       res = dynar_str_vcatf(dest, format, ap);
+       va_end(ap);
+
+       return (res);
+}
+
+int
+dynar_str_quote_cat(struct dynar *dest, const char *str)
+{
+       size_t zi;
+
+       if (dynar_str_cat(dest, "\"") != 0) {
+               return (-1);
+       }
+
+       for (zi = 0; zi < strlen(str); zi++) {
+               if (str[zi] == '"' || str[zi] == '\\') {
+                       if (dynar_str_cat(dest, "\\") != 0) {
+                               return (-1);
+                       }
+               }
+
+               if (dynar_cat(dest, &str[zi], sizeof(str[zi])) != 0) {
+                       return (-1);
+               }
+       }
+
+       if (dynar_str_cat(dest, "\"") != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+dynar_str_quote_cpy(struct dynar *dest, const char *str)
+{
+
+       dynar_clean(dest);
+
+       return (dynar_str_quote_cat(dest, str));
+}
diff --git a/qdevices/dynar-str.h b/qdevices/dynar-str.h
new file mode 100644 (file)
index 0000000..98ec187
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _DYNAR_STR_H_
+#define _DYNAR_STR_H_
+
+#include <stdarg.h>
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             dynar_str_cpy(struct dynar *dest, const char *str);
+
+extern int             dynar_str_cat(struct dynar *dest, const char *str);
+
+extern int             dynar_str_catf(struct dynar *dest, const char *format, ...)
+    __attribute__((__format__(__printf__, 2, 3)));
+
+extern int             dynar_str_vcatf(struct dynar *dest, const char *format, va_list ap)
+    __attribute__((__format__(__printf__, 2, 0)));
+
+extern int             dynar_str_prepend(struct dynar *dest, const char *str);
+
+extern int             dynar_str_quote_cat(struct dynar *dest, const char *str);
+
+extern int             dynar_str_quote_cpy(struct dynar *dest, const char *str);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DYNAR_STR_H_ */
diff --git a/qdevices/dynar.c b/qdevices/dynar.c
new file mode 100644 (file)
index 0000000..b8e7775
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "dynar.h"
+
+void
+dynar_init(struct dynar *array, size_t maximum_size)
+{
+
+       memset(array, 0, sizeof(*array));
+       array->maximum_size = maximum_size;
+}
+
+void
+dynar_set_max_size(struct dynar *array, size_t maximum_size)
+{
+
+       array->maximum_size = maximum_size;
+}
+
+int
+dynar_set_size(struct dynar *array, size_t size)
+{
+
+       if (size > dynar_max_size(array)) {
+               dynar_set_max_size(array, size);
+       }
+
+       if (size > dynar_size(array)) {
+               if (dynar_prealloc(array, size - dynar_size(array)) == -1) {
+                       return (-1);
+               }
+       }
+
+       array->size = size;
+
+       return (0);
+}
+
+void
+dynar_destroy(struct dynar *array)
+{
+
+       free(array->data);
+       dynar_init(array, array->maximum_size);
+}
+
+void
+dynar_clean(struct dynar *array)
+{
+
+       array->size = 0;
+}
+
+size_t
+dynar_size(const struct dynar *array)
+{
+
+       return (array->size);
+}
+
+size_t
+dynar_max_size(const struct dynar *array)
+{
+
+       return (array->maximum_size);
+}
+
+char *
+dynar_data(const struct dynar *array)
+{
+
+       return (array->data);
+}
+
+static int
+dynar_realloc(struct dynar *array, size_t new_array_size)
+{
+       char *new_data;
+
+       if (new_array_size > array->maximum_size) {
+               return (-1);
+       }
+
+       new_data = realloc(array->data, new_array_size);
+
+       if (new_data == NULL) {
+               return (-1);
+       }
+
+       array->allocated = new_array_size;
+       array->data = new_data;
+
+       return (0);
+}
+
+int
+dynar_prealloc(struct dynar *array, size_t size)
+{
+       size_t new_size;
+
+       if (array->size + size > array->maximum_size) {
+               return (-1);
+       }
+
+       if (array->size + size > array->allocated) {
+               new_size = (array->allocated + size) * 2;
+               if (new_size > array->maximum_size) {
+                       new_size = array->maximum_size;
+               }
+
+               if (dynar_realloc(array, new_size) == -1) {
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+int
+dynar_cat(struct dynar *array, const void *src, size_t size)
+{
+
+       if (dynar_prealloc(array, size) != 0) {
+               return (-1);
+       }
+
+       memmove(array->data + array->size, src, size);
+       array->size += size;
+
+       return (0);
+}
+
+int
+dynar_prepend(struct dynar *array, const void *src, size_t size)
+{
+
+       if (dynar_prealloc(array, size) != 0) {
+               return (-1);
+       }
+
+       memmove(array->data + size, array->data, array->size);
+       memmove(array->data, src, size);
+       array->size += size;
+
+       return (0);
+}
diff --git a/qdevices/dynar.h b/qdevices/dynar.h
new file mode 100644 (file)
index 0000000..26f3ac5
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _DYNAR_H_
+#define _DYNAR_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Dynamic array structure
+ */
+struct dynar {
+       char *data;
+       size_t size;
+       size_t allocated;
+       size_t maximum_size;
+};
+
+extern void     dynar_init(struct dynar *array, size_t maximum_size);
+
+extern void     dynar_destroy(struct dynar *array);
+
+extern void     dynar_clean(struct dynar *array);
+
+extern size_t   dynar_size(const struct dynar *array);
+
+extern size_t   dynar_max_size(const struct dynar *array);
+
+extern void     dynar_set_max_size(struct dynar *array, size_t maximum_size);
+
+extern char    *dynar_data(const struct dynar *array);
+
+extern int      dynar_cat(struct dynar *array, const void *src, size_t size);
+
+extern int      dynar_prealloc(struct dynar *array, size_t size);
+
+extern int      dynar_prepend(struct dynar *array, const void *src, size_t size);
+
+extern int      dynar_set_size(struct dynar *array, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DYNAR_H_ */
diff --git a/qdevices/msg.c b/qdevices/msg.c
new file mode 100644 (file)
index 0000000..8f2b13b
--- /dev/null
@@ -0,0 +1,1095 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "msg.h"
+
+#define MSG_TYPE_LENGTH                2
+#define MSG_LENGTH_LENGTH      4
+
+#define MSG_STATIC_SUPPORTED_MESSAGES_SIZE     18
+
+enum msg_type msg_static_supported_messages[MSG_STATIC_SUPPORTED_MESSAGES_SIZE] = {
+    MSG_TYPE_PREINIT,
+    MSG_TYPE_PREINIT_REPLY,
+    MSG_TYPE_STARTTLS,
+    MSG_TYPE_INIT,
+    MSG_TYPE_INIT_REPLY,
+    MSG_TYPE_SERVER_ERROR,
+    MSG_TYPE_SET_OPTION,
+    MSG_TYPE_SET_OPTION_REPLY,
+    MSG_TYPE_ECHO_REQUEST,
+    MSG_TYPE_ECHO_REPLY,
+    MSG_TYPE_NODE_LIST,
+    MSG_TYPE_NODE_LIST_REPLY,
+    MSG_TYPE_ASK_FOR_VOTE,
+    MSG_TYPE_ASK_FOR_VOTE_REPLY,
+    MSG_TYPE_VOTE_INFO,
+    MSG_TYPE_VOTE_INFO_REPLY,
+    MSG_TYPE_HEURISTICS_CHANGE,
+    MSG_TYPE_HEURISTICS_CHANGE_REPLY,
+};
+
+size_t
+msg_get_header_length(void)
+{
+       return (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH);
+}
+
+static void
+msg_add_type(struct dynar *msg, enum msg_type type)
+{
+       uint16_t ntype;
+
+       ntype = htons((uint16_t)type);
+       dynar_cat(msg, &ntype, sizeof(ntype));
+}
+
+enum msg_type
+msg_get_type(const struct dynar *msg)
+{
+       uint16_t ntype;
+       uint16_t type;
+
+       memcpy(&ntype, dynar_data(msg), sizeof(ntype));
+       type = ntohs(ntype);
+
+       return (type);
+}
+
+/*
+ * We don't know size of message before call of this function, so zero is
+ * added. Real value is set afterwards by msg_set_len.
+ */
+static void
+msg_add_len(struct dynar *msg)
+{
+       uint32_t len;
+
+       len = 0;
+       dynar_cat(msg, &len, sizeof(len));
+}
+
+static void
+msg_set_len(struct dynar *msg, uint32_t len)
+{
+       uint32_t nlen;
+
+       nlen = htonl(len);
+       memcpy(dynar_data(msg) + MSG_TYPE_LENGTH, &nlen, sizeof(nlen));
+}
+
+/*
+ * Used only for echo reply msg. All other messages should use msg_add_type.
+ */
+static void
+msg_set_type(struct dynar *msg, enum msg_type type)
+{
+       uint16_t ntype;
+
+       ntype = htons((uint16_t)type);
+       memcpy(dynar_data(msg), &ntype, sizeof(ntype));
+}
+
+uint32_t
+msg_get_len(const struct dynar *msg)
+{
+       uint32_t nlen;
+       uint32_t len;
+
+       memcpy(&nlen, dynar_data(msg) + MSG_TYPE_LENGTH, sizeof(nlen));
+       len = ntohl(nlen);
+
+       return (len);
+}
+
+
+size_t
+msg_create_preinit(struct dynar *msg, const char *cluster_name, int add_msg_seq_number,
+    uint32_t msg_seq_number)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_PREINIT);
+       msg_add_len(msg);
+
+       if (add_msg_seq_number) {
+               if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (tlv_add_cluster_name(msg, cluster_name) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_preinit_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_tls_supported tls_supported, int tls_client_cert_required)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_PREINIT_REPLY);
+       msg_add_len(msg);
+
+       if (add_msg_seq_number) {
+               if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (tlv_add_tls_supported(msg, tls_supported) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_tls_client_cert_required(msg, tls_client_cert_required) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_starttls(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_STARTTLS);
+       msg_add_len(msg);
+
+       if (add_msg_seq_number) {
+               if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_server_error(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_reply_error_code reply_error_code)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_SERVER_ERROR);
+       msg_add_len(msg);
+
+       if (add_msg_seq_number) {
+               if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (tlv_add_reply_error_code(msg, reply_error_code) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+static uint16_t *
+msg_convert_msg_type_array_to_u16_array(const enum msg_type *msg_type_array, size_t array_size)
+{
+       uint16_t *u16a;
+       size_t i;
+
+       u16a = malloc(sizeof(*u16a) * array_size);
+       if (u16a == NULL) {
+               return (NULL);
+       }
+
+       for (i = 0; i < array_size; i++) {
+               u16a[i] = (uint16_t)msg_type_array[i];
+       }
+
+       return (u16a);
+}
+
+size_t
+msg_create_init(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_decision_algorithm_type decision_algorithm,
+    const enum msg_type *supported_msgs, size_t no_supported_msgs,
+    const enum tlv_opt_type *supported_opts, size_t no_supported_opts, uint32_t node_id,
+    uint32_t heartbeat_interval, const struct tlv_tie_breaker *tie_breaker,
+    const struct tlv_ring_id *ring_id)
+{
+       uint16_t *u16a;
+       int res;
+
+       u16a = NULL;
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_INIT);
+       msg_add_len(msg);
+
+       if (add_msg_seq_number) {
+               if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (supported_msgs != NULL && no_supported_msgs > 0) {
+               u16a = msg_convert_msg_type_array_to_u16_array(supported_msgs, no_supported_msgs);
+
+               if (u16a == NULL) {
+                       goto small_buf_err;
+               }
+
+               res = tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_MESSAGES, u16a, no_supported_msgs);
+
+               free(u16a);
+
+               if (res == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (supported_opts != NULL && no_supported_opts > 0) {
+               if (tlv_add_supported_options(msg, supported_opts, no_supported_opts) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (tlv_add_node_id(msg, node_id) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_decision_algorithm(msg, decision_algorithm) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_heartbeat_interval(msg, heartbeat_interval) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_tie_breaker(msg, tie_breaker) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_ring_id(msg, ring_id) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_init_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_reply_error_code reply_error_code,
+    const enum msg_type *supported_msgs, size_t no_supported_msgs,
+    const enum tlv_opt_type *supported_opts, size_t no_supported_opts,
+    size_t server_maximum_request_size, size_t server_maximum_reply_size,
+    const enum tlv_decision_algorithm_type *supported_decision_algorithms,
+    size_t no_supported_decision_algorithms)
+{
+       uint16_t *u16a;
+       int res;
+
+       u16a = NULL;
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_INIT_REPLY);
+       msg_add_len(msg);
+
+       if (tlv_add_reply_error_code(msg, reply_error_code) == -1) {
+               goto small_buf_err;
+       }
+
+       if (supported_msgs != NULL && no_supported_msgs > 0) {
+               u16a = msg_convert_msg_type_array_to_u16_array(supported_msgs, no_supported_msgs);
+
+               if (u16a == NULL) {
+                       goto small_buf_err;
+               }
+
+               res = tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_MESSAGES, u16a, no_supported_msgs);
+
+               free(u16a);
+
+               if (res == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (supported_opts != NULL && no_supported_opts > 0) {
+               if (tlv_add_supported_options(msg, supported_opts, no_supported_opts) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (add_msg_seq_number) {
+               if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (tlv_add_server_maximum_request_size(msg, server_maximum_request_size) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_server_maximum_reply_size(msg, server_maximum_reply_size) == -1) {
+               goto small_buf_err;
+       }
+
+       if (supported_decision_algorithms != NULL && no_supported_decision_algorithms > 0) {
+               if (tlv_add_supported_decision_algorithms(msg, supported_decision_algorithms,
+                   no_supported_decision_algorithms) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_set_option(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    int add_heartbeat_interval, uint32_t heartbeat_interval)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_SET_OPTION);
+       msg_add_len(msg);
+
+       if (add_msg_seq_number) {
+               if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (add_heartbeat_interval) {
+               if (tlv_add_heartbeat_interval(msg, heartbeat_interval) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_set_option_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    uint32_t heartbeat_interval)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_SET_OPTION_REPLY);
+       msg_add_len(msg);
+
+       if (add_msg_seq_number) {
+               if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (tlv_add_heartbeat_interval(msg, heartbeat_interval) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_echo_request(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_ECHO_REQUEST);
+       msg_add_len(msg);
+
+       if (add_msg_seq_number) {
+               if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_echo_reply(struct dynar *msg, const struct dynar *echo_request_msg)
+{
+
+       dynar_clean(msg);
+
+       if (dynar_cat(msg, dynar_data(echo_request_msg), dynar_size(echo_request_msg)) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_type(msg, MSG_TYPE_ECHO_REPLY);
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_node_list(struct dynar *msg,
+    uint32_t msg_seq_number, enum tlv_node_list_type node_list_type,
+    int add_ring_id, const struct tlv_ring_id *ring_id,
+    int add_config_version, uint64_t config_version,
+    int add_quorate, enum tlv_quorate quorate,
+    int add_heuristics, enum tlv_heuristics heuristics,
+    const struct node_list *nodes)
+{
+       struct node_list_entry *node_info;
+       struct tlv_node_info tlv_ni;
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_NODE_LIST);
+       msg_add_len(msg);
+
+       if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_node_list_type(msg, node_list_type) == -1) {
+               goto small_buf_err;
+       }
+
+       if (add_ring_id) {
+               if (tlv_add_ring_id(msg, ring_id) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (add_config_version) {
+               if (tlv_add_config_version(msg, config_version) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (add_quorate) {
+               if (tlv_add_quorate(msg, quorate) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       TAILQ_FOREACH(node_info, nodes, entries) {
+               node_list_entry_to_tlv_node_info(node_info, &tlv_ni);
+
+               if (tlv_add_node_info(msg, &tlv_ni) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       if (add_heuristics && heuristics != TLV_HEURISTICS_UNDEFINED) {
+               if (tlv_add_heuristics(msg, heuristics) == -1) {
+                       goto small_buf_err;
+               }
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_node_list_reply(struct dynar *msg, uint32_t msg_seq_number,
+    enum tlv_node_list_type node_list_type, const struct tlv_ring_id *ring_id,
+    enum tlv_vote vote)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_NODE_LIST_REPLY);
+       msg_add_len(msg);
+
+       if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_node_list_type(msg, node_list_type) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_ring_id(msg, ring_id) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_vote(msg, vote) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_ask_for_vote(struct dynar *msg, uint32_t msg_seq_number)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_ASK_FOR_VOTE);
+       msg_add_len(msg);
+
+       if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_ask_for_vote_reply(struct dynar *msg, uint32_t msg_seq_number,
+    const struct tlv_ring_id *ring_id, enum tlv_vote vote)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_ASK_FOR_VOTE_REPLY);
+       msg_add_len(msg);
+
+       if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_vote(msg, vote) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_ring_id(msg, ring_id) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_vote_info(struct dynar *msg, uint32_t msg_seq_number, const struct tlv_ring_id *ring_id,
+    enum tlv_vote vote)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_VOTE_INFO);
+       msg_add_len(msg);
+
+       if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_vote(msg, vote) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_ring_id(msg, ring_id) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_vote_info_reply(struct dynar *msg, uint32_t msg_seq_number)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_VOTE_INFO_REPLY);
+       msg_add_len(msg);
+
+       if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_heuristics_change(struct dynar *msg, uint32_t msg_seq_number,
+    enum tlv_heuristics heuristics)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_HEURISTICS_CHANGE);
+       msg_add_len(msg);
+
+       if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_heuristics(msg, heuristics) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+size_t
+msg_create_heuristics_change_reply(struct dynar *msg, uint32_t msg_seq_number,
+    const struct tlv_ring_id *ring_id, enum tlv_heuristics heuristics, enum tlv_vote vote)
+{
+
+       dynar_clean(msg);
+
+       msg_add_type(msg, MSG_TYPE_HEURISTICS_CHANGE_REPLY);
+       msg_add_len(msg);
+
+       if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_vote(msg, vote) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_ring_id(msg, ring_id) == -1) {
+               goto small_buf_err;
+       }
+
+       if (tlv_add_heuristics(msg, heuristics) == -1) {
+               goto small_buf_err;
+       }
+
+       msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+       return (dynar_size(msg));
+
+small_buf_err:
+       return (0);
+}
+
+int
+msg_is_valid_msg_type(const struct dynar *msg)
+{
+       enum msg_type type;
+       size_t i;
+
+       type = msg_get_type(msg);
+
+       for (i = 0; i < MSG_STATIC_SUPPORTED_MESSAGES_SIZE; i++) {
+               if (msg_static_supported_messages[i] == type) {
+                       return (1);
+               }
+       }
+
+       return (0);
+}
+
+void
+msg_decoded_init(struct msg_decoded *decoded_msg)
+{
+
+       memset(decoded_msg, 0, sizeof(*decoded_msg));
+
+       node_list_init(&decoded_msg->nodes);
+}
+
+void
+msg_decoded_destroy(struct msg_decoded *decoded_msg)
+{
+
+       free(decoded_msg->cluster_name);
+       free(decoded_msg->supported_messages);
+       free(decoded_msg->supported_options);
+       free(decoded_msg->supported_decision_algorithms);
+       node_list_free(&decoded_msg->nodes);
+
+       msg_decoded_init(decoded_msg);
+}
+
+/*
+ *  0 - No error
+ * -1 - option with invalid length
+ * -2 - Unable to allocate memory
+ * -3 - Inconsistent msg (tlv len > msg size)
+ * -4 - invalid option content
+ */
+int
+msg_decode(const struct dynar *msg, struct msg_decoded *decoded_msg)
+{
+       struct tlv_iterator tlv_iter;
+       uint16_t *u16a;
+       uint32_t u32;
+       uint64_t u64;
+       struct tlv_ring_id ring_id;
+       struct tlv_node_info node_info;
+       struct tlv_tie_breaker tie_breaker;
+       size_t zi;
+       enum tlv_opt_type opt_type;
+       int iter_res;
+       int res;
+
+       msg_decoded_destroy(decoded_msg);
+
+       decoded_msg->type = msg_get_type(msg);
+
+       tlv_iter_init(msg, msg_get_header_length(), &tlv_iter);
+
+       while ((iter_res = tlv_iter_next(&tlv_iter)) > 0) {
+               opt_type = tlv_iter_get_type(&tlv_iter);
+
+               switch (opt_type) {
+               case TLV_OPT_MSG_SEQ_NUMBER:
+                       if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->seq_number_set = 1;
+                       decoded_msg->seq_number = u32;
+                       break;
+               case TLV_OPT_CLUSTER_NAME:
+                       if ((res = tlv_iter_decode_str(&tlv_iter, &decoded_msg->cluster_name,
+                           &decoded_msg->cluster_name_len)) != 0) {
+                               return (-2);
+                       }
+                       break;
+               case TLV_OPT_TLS_SUPPORTED:
+                       if ((res = tlv_iter_decode_tls_supported(&tlv_iter,
+                           &decoded_msg->tls_supported)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->tls_supported_set = 1;
+                       break;
+               case TLV_OPT_TLS_CLIENT_CERT_REQUIRED:
+                       if ((res = tlv_iter_decode_client_cert_required(&tlv_iter,
+                           &decoded_msg->tls_client_cert_required)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->tls_client_cert_required_set = 1;
+                       break;
+               case TLV_OPT_SUPPORTED_MESSAGES:
+                       free(decoded_msg->supported_messages);
+
+                       if ((res = tlv_iter_decode_u16_array(&tlv_iter, &u16a,
+                           &decoded_msg->no_supported_messages)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->supported_messages =
+                           malloc(sizeof(enum msg_type) * decoded_msg->no_supported_messages);
+
+                       if (decoded_msg->supported_messages == NULL) {
+                               free(u16a);
+                               return (-2);
+                       }
+
+                       for (zi = 0; zi < decoded_msg->no_supported_messages; zi++) {
+                               decoded_msg->supported_messages[zi] = (enum msg_type)u16a[zi];
+                       }
+
+                       free(u16a);
+                       break;
+               case TLV_OPT_SUPPORTED_OPTIONS:
+                       free(decoded_msg->supported_options);
+
+                       if ((res = tlv_iter_decode_supported_options(&tlv_iter,
+                           &decoded_msg->supported_options,
+                           &decoded_msg->no_supported_options)) != 0) {
+                               return (res);
+                       }
+                       break;
+               case TLV_OPT_REPLY_ERROR_CODE:
+                       if ((res = tlv_iter_decode_reply_error_code(&tlv_iter,
+                           &decoded_msg->reply_error_code)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->reply_error_code_set = 1;
+                       break;
+               case TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE:
+                       if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->server_maximum_request_size_set = 1;
+                       decoded_msg->server_maximum_request_size = u32;
+                       break;
+               case TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE:
+                       if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->server_maximum_reply_size_set = 1;
+                       decoded_msg->server_maximum_reply_size = u32;
+                       break;
+               case TLV_OPT_NODE_ID:
+                       if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->node_id_set = 1;
+                       decoded_msg->node_id = u32;
+                       break;
+               case TLV_OPT_SUPPORTED_DECISION_ALGORITHMS:
+                       free(decoded_msg->supported_decision_algorithms);
+
+                       if ((res = tlv_iter_decode_supported_decision_algorithms(&tlv_iter,
+                           &decoded_msg->supported_decision_algorithms,
+                           &decoded_msg->no_supported_decision_algorithms)) != 0) {
+                               return (res);
+                       }
+                       break;
+               case TLV_OPT_DECISION_ALGORITHM:
+                       if ((res = tlv_iter_decode_decision_algorithm(&tlv_iter,
+                           &decoded_msg->decision_algorithm)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->decision_algorithm_set = 1;
+                       break;
+               case TLV_OPT_HEARTBEAT_INTERVAL:
+                       if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->heartbeat_interval_set = 1;
+                       decoded_msg->heartbeat_interval = u32;
+                       break;
+               case TLV_OPT_RING_ID:
+                       if ((res = tlv_iter_decode_ring_id(&tlv_iter, &ring_id)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->ring_id_set = 1;
+                       memcpy(&decoded_msg->ring_id, &ring_id, sizeof(ring_id));
+                       break;
+               case TLV_OPT_CONFIG_VERSION:
+                       if ((res = tlv_iter_decode_u64(&tlv_iter, &u64)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->config_version_set = 1;
+                       decoded_msg->config_version = u64;
+                       break;
+               case TLV_OPT_DATA_CENTER_ID:
+                       if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->data_center_id = u32;
+                       break;
+               case TLV_OPT_NODE_STATE:
+                       if ((res = tlv_iter_decode_node_state(&tlv_iter,
+                           &decoded_msg->node_state)) != 0) {
+                               return (res);
+                       }
+                       break;
+               case TLV_OPT_NODE_INFO:
+                       if ((res = tlv_iter_decode_node_info(&tlv_iter, &node_info)) != 0) {
+                               return (res);
+                       }
+
+                       if (node_list_add_from_node_info(&decoded_msg->nodes, &node_info) == NULL) {
+                               return (-2);
+                       }
+                       break;
+               case TLV_OPT_NODE_LIST_TYPE:
+                       if ((res = tlv_iter_decode_node_list_type(&tlv_iter,
+                           &decoded_msg->node_list_type)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->node_list_type_set = 1;
+                       break;
+               case TLV_OPT_VOTE:
+                       if ((res = tlv_iter_decode_vote(&tlv_iter, &decoded_msg->vote)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->vote_set = 1;
+                       break;
+               case TLV_OPT_QUORATE:
+                       if ((res = tlv_iter_decode_quorate(&tlv_iter,
+                           &decoded_msg->quorate)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->quorate_set = 1;
+                       break;
+               case TLV_OPT_TIE_BREAKER:
+                       if ((res = tlv_iter_decode_tie_breaker(&tlv_iter, &tie_breaker)) != 0) {
+                               return (res);
+                       }
+
+                       decoded_msg->tie_breaker_set = 1;
+                       memcpy(&decoded_msg->tie_breaker, &tie_breaker, sizeof(tie_breaker));
+                       break;
+               case TLV_OPT_HEURISTICS:
+                       if ((res = tlv_iter_decode_heuristics(&tlv_iter,
+                           &decoded_msg->heuristics)) != 0) {
+                               return (res);
+                       }
+                       break;
+               /*
+                * Default is not defined intentionally. Compiler shows warning when
+                * new tlv option is added. Also protocol ignores unknown options so
+                * no extra work is needed.
+                */
+               }
+       }
+
+       if (iter_res != 0) {
+               return (-3);
+       }
+
+       return (0);
+}
+
+void
+msg_get_supported_messages(enum msg_type **supported_messages, size_t *no_supported_messages)
+{
+
+       *supported_messages = msg_static_supported_messages;
+       *no_supported_messages = MSG_STATIC_SUPPORTED_MESSAGES_SIZE;
+}
+
+const char *
+msg_type_to_str(enum msg_type type)
+{
+
+       switch (type) {
+       case MSG_TYPE_PREINIT: return ("Preinit"); break;
+       case MSG_TYPE_PREINIT_REPLY: return ("Preinit reply"); break;
+       case MSG_TYPE_STARTTLS: return ("StartTLS"); break;
+       case MSG_TYPE_INIT: return ("Init"); break;
+       case MSG_TYPE_INIT_REPLY: return ("Init reply"); break;
+       case MSG_TYPE_SERVER_ERROR: return ("Server error"); break;
+       case MSG_TYPE_SET_OPTION: return ("Set option"); break;
+       case MSG_TYPE_SET_OPTION_REPLY: return ("Set option reply"); break;
+       case MSG_TYPE_ECHO_REQUEST: return ("Echo request"); break;
+       case MSG_TYPE_ECHO_REPLY: return ("Echo reply"); break;
+       case MSG_TYPE_NODE_LIST: return ("Node list"); break;
+       case MSG_TYPE_NODE_LIST_REPLY: return ("Node list reply"); break;
+       case MSG_TYPE_ASK_FOR_VOTE: return ("Ask for vote"); break;
+       case MSG_TYPE_ASK_FOR_VOTE_REPLY: return ("Ask for vote reply"); break;
+       case MSG_TYPE_VOTE_INFO: return ("Vote info"); break;
+       case MSG_TYPE_VOTE_INFO_REPLY: return ("Vote info reply"); break;
+       case MSG_TYPE_HEURISTICS_CHANGE: return ("Heuristics change"); break;
+       case MSG_TYPE_HEURISTICS_CHANGE_REPLY: return ("Heuristics change reply"); break;
+       }
+
+       return ("Unknown message type");
+}
diff --git a/qdevices/msg.h b/qdevices/msg.h
new file mode 100644 (file)
index 0000000..bb2b390
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _MSG_H_
+#define _MSG_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include "dynar.h"
+#include "tlv.h"
+#include "node-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum msg_type {
+       MSG_TYPE_PREINIT = 0,
+       MSG_TYPE_PREINIT_REPLY = 1,
+       MSG_TYPE_STARTTLS = 2,
+       MSG_TYPE_INIT = 3,
+       MSG_TYPE_INIT_REPLY = 4,
+       MSG_TYPE_SERVER_ERROR = 5,
+       MSG_TYPE_SET_OPTION = 6,
+       MSG_TYPE_SET_OPTION_REPLY = 7,
+       MSG_TYPE_ECHO_REQUEST = 8,
+       MSG_TYPE_ECHO_REPLY = 9,
+       MSG_TYPE_NODE_LIST = 10,
+       MSG_TYPE_NODE_LIST_REPLY = 11,
+       MSG_TYPE_ASK_FOR_VOTE = 12,
+       MSG_TYPE_ASK_FOR_VOTE_REPLY = 13,
+       MSG_TYPE_VOTE_INFO = 14,
+       MSG_TYPE_VOTE_INFO_REPLY = 15,
+       MSG_TYPE_HEURISTICS_CHANGE = 16,
+       MSG_TYPE_HEURISTICS_CHANGE_REPLY = 17,
+};
+
+struct msg_decoded {
+       enum msg_type type;
+       uint8_t seq_number_set;
+       uint32_t seq_number;    /* Only valid if seq_number_set != 0 */
+       size_t cluster_name_len;
+       /* Valid only if != NULL. Trailing \0 is added but not counted in cluster_name_len */
+       char *cluster_name;
+       uint8_t tls_supported_set;
+       enum tlv_tls_supported tls_supported;   /* Valid only if tls_supported_set != 0. */
+       uint8_t tls_client_cert_required_set;
+       uint8_t tls_client_cert_required;       /* Valid only if tls_client_cert_required_set != 0 */
+       size_t no_supported_messages;
+       enum msg_type *supported_messages;      /* Valid only if != NULL */
+       size_t no_supported_options;
+       enum tlv_opt_type *supported_options;   /* Valid only if != NULL */
+       uint8_t reply_error_code_set;
+       enum tlv_reply_error_code reply_error_code;     /* Valid only if reply_error_code_set != 0 */
+       uint8_t server_maximum_request_size_set;
+       /* Valid only if server_maximum_request_size_set != 0 */
+       size_t server_maximum_request_size;
+       uint8_t server_maximum_reply_size_set;
+       size_t server_maximum_reply_size;       /* Valid only if server_maximum_reply_size_set != 0 */
+       uint8_t node_id_set;
+       uint32_t node_id;
+       size_t no_supported_decision_algorithms;
+       /* Valid only if != NULL */
+       enum tlv_decision_algorithm_type *supported_decision_algorithms;
+       uint8_t decision_algorithm_set;
+       /* Valid only if decision_algorithm_set != 0 */
+       enum tlv_decision_algorithm_type decision_algorithm;
+       uint8_t heartbeat_interval_set;
+       uint32_t heartbeat_interval;    /* Valid only if heartbeat_interval_set != 0 */
+       uint8_t ring_id_set;
+       struct tlv_ring_id ring_id;     /* Valid only if ring_id_set != 0 */
+       uint8_t config_version_set;
+       uint64_t config_version;        /* Valid only if config_version_set != 0 */
+       uint32_t data_center_id;        /* Valid only if != 0 */
+       enum tlv_node_state node_state; /* Valid only if != TLV_NODE_STATE_NOT_SET */
+       struct node_list nodes;         /* Valid only if node_list_is_empty(nodes) != 0 */
+       int node_list_type_set;
+       enum tlv_node_list_type node_list_type; /* Valid only if node_list_type_set != 0 */
+       int vote_set;
+       enum tlv_vote vote;     /* Valid only if vote_set != 0 */
+       int quorate_set;
+       enum tlv_quorate quorate;       /* Valid only if quorate_set != 0 */
+       int tie_breaker_set;
+       struct tlv_tie_breaker tie_breaker;
+       enum tlv_heuristics heuristics; /* Always valid but can be TLV_HEURISTICS_UNDEFINED */
+};
+
+extern size_t          msg_create_preinit(struct dynar *msg, const char *cluster_name,
+    int add_msg_seq_number, uint32_t msg_seq_number);
+
+extern size_t          msg_create_preinit_reply(struct dynar *msg, int add_msg_seq_number,
+    uint32_t msg_seq_number, enum tlv_tls_supported tls_supported, int tls_client_cert_required);
+
+extern size_t          msg_create_starttls(struct dynar *msg, int add_msg_seq_number,
+    uint32_t msg_seq_number);
+
+extern size_t          msg_create_init(struct dynar *msg, int add_msg_seq_number,
+    uint32_t msg_seq_number, enum tlv_decision_algorithm_type decision_algorithm,
+    const enum msg_type *supported_msgs, size_t no_supported_msgs,
+    const enum tlv_opt_type *supported_opts, size_t no_supported_opts, uint32_t node_id,
+    uint32_t heartbeat_interval, const struct tlv_tie_breaker *tie_breaker,
+    const struct tlv_ring_id *ring_id);
+
+extern size_t          msg_create_server_error(struct dynar *msg, int add_msg_seq_number,
+    uint32_t msg_seq_number, enum tlv_reply_error_code reply_error_code);
+
+extern size_t          msg_create_init_reply(struct dynar *msg, int add_msg_seq_number,
+    uint32_t msg_seq_number, enum tlv_reply_error_code reply_error_code,
+    const enum msg_type *supported_msgs, size_t no_supported_msgs,
+    const enum tlv_opt_type *supported_opts, size_t no_supported_opts,
+    size_t server_maximum_request_size, size_t server_maximum_reply_size,
+    const enum tlv_decision_algorithm_type *supported_decision_algorithms,
+    size_t no_supported_decision_algorithms);
+
+extern size_t          msg_create_set_option(struct dynar *msg,
+    int add_msg_seq_number, uint32_t msg_seq_number,
+    int add_heartbeat_interval, uint32_t heartbeat_interval);
+
+extern size_t          msg_create_set_option_reply(struct dynar *msg,
+    int add_msg_seq_number, uint32_t msg_seq_number, uint32_t heartbeat_interval);
+
+extern size_t          msg_create_echo_request(struct dynar *msg, int add_msg_seq_number,
+    uint32_t msg_seq_number);
+
+extern size_t          msg_create_echo_reply(struct dynar *msg,
+    const struct dynar *echo_request_msg);
+
+extern size_t          msg_create_node_list(struct dynar *msg,
+    uint32_t msg_seq_number, enum tlv_node_list_type node_list_type,
+    int add_ring_id, const struct tlv_ring_id *ring_id,
+    int add_config_version, uint64_t config_version,
+    int add_quorate, enum tlv_quorate quorate,
+    int add_heuristics, enum tlv_heuristics heuristics,
+    const struct node_list *nodes);
+
+extern size_t          msg_create_node_list_reply(struct dynar *msg, uint32_t msg_seq_number,
+    enum tlv_node_list_type node_list_type, const struct tlv_ring_id *ring_id,
+    enum tlv_vote vote);
+
+extern size_t          msg_create_ask_for_vote(struct dynar *msg, uint32_t msg_seq_number);
+
+extern size_t          msg_create_ask_for_vote_reply(struct dynar *msg, uint32_t msg_seq_number,
+    const struct tlv_ring_id *ring_id, enum tlv_vote vote);
+
+extern size_t          msg_create_vote_info(struct dynar *msg, uint32_t msg_seq_number,
+    const struct tlv_ring_id *ring_id, enum tlv_vote vote);
+
+extern size_t          msg_create_vote_info_reply(struct dynar *msg, uint32_t msg_seq_number);
+
+extern size_t          msg_create_heuristics_change(struct dynar *msg, uint32_t msg_seq_number,
+    enum tlv_heuristics heuristics);
+
+extern size_t          msg_create_heuristics_change_reply(struct dynar *msg,
+    uint32_t msg_seq_number, const struct tlv_ring_id *ring_id, enum tlv_heuristics heuristics,
+    enum tlv_vote vote);
+
+extern size_t          msg_get_header_length(void);
+
+extern uint32_t                msg_get_len(const struct dynar *msg);
+
+extern enum msg_type   msg_get_type(const struct dynar *msg);
+
+extern int             msg_is_valid_msg_type(const struct dynar *msg);
+
+extern void            msg_decoded_init(struct msg_decoded *decoded_msg);
+
+extern void            msg_decoded_destroy(struct msg_decoded *decoded_msg);
+
+extern int             msg_decode(const struct dynar *msg, struct msg_decoded *decoded_msg);
+
+extern void            msg_get_supported_messages(enum msg_type **supported_messages,
+    size_t *no_supported_messages);
+
+extern const char *    msg_type_to_str(enum msg_type type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MSG_H_ */
diff --git a/qdevices/msgio.c b/qdevices/msgio.c
new file mode 100644 (file)
index 0000000..cedff34
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "msgio.h"
+#include "msg.h"
+
+#define MSGIO_LOCAL_BUF_SIZE                   (1 << 10)
+
+ssize_t
+msgio_send(PRFileDesc *sock, const char *msg, size_t msg_len, size_t *start_pos)
+{
+       ssize_t sent_bytes;
+
+       if ((sent_bytes = PR_Send(sock, msg + *start_pos,
+           msg_len - *start_pos, 0, PR_INTERVAL_NO_TIMEOUT)) != -1) {
+               *start_pos += sent_bytes;
+       }
+
+       return (sent_bytes);
+}
+
+ssize_t
+msgio_send_blocking(PRFileDesc *sock, const char *msg, size_t msg_len)
+{
+       PRPollDesc pfd;
+       size_t already_sent_bytes;
+       PRInt32 res;
+       ssize_t ret;
+
+       already_sent_bytes = 0;
+       ret = 0;
+
+       while (ret != -1 && already_sent_bytes < msg_len) {
+               pfd.fd = sock;
+               pfd.in_flags = PR_POLL_WRITE;
+               pfd.out_flags = 0;
+
+               if ((res = PR_Poll(&pfd, 1, PR_INTERVAL_NO_TIMEOUT)) > 0) {
+                       if (pfd.out_flags & PR_POLL_WRITE) {
+                               if ((msgio_send(sock, msg, msg_len, &already_sent_bytes) == -1) &&
+                                   PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+                                       ret = -1;
+                               } else {
+                                       ret = already_sent_bytes;
+                               }
+                       } else if (pfd.out_flags & (PR_POLL_ERR | PR_POLL_NVAL | PR_POLL_HUP)) {
+                               PR_SetError(PR_IO_ERROR, 0);
+                               ret = -1;
+                       }
+               } else {
+                       ret = -1;
+               }
+       }
+
+       return (ret);
+}
+
+/*
+ * -1 = send returned 0,
+ * -2 = unhandled error.
+ *  0 = success but whole buffer is still not sent
+ *  1 = all data was sent
+ */
+int
+msgio_write(PRFileDesc *sock, const struct dynar *msg, size_t *already_sent_bytes)
+{
+       PRInt32 sent;
+       PRInt32 to_send;
+
+       to_send = dynar_size(msg) - *already_sent_bytes;
+       if (to_send > MSGIO_LOCAL_BUF_SIZE) {
+               to_send = MSGIO_LOCAL_BUF_SIZE;
+       }
+
+       sent = PR_Send(sock, dynar_data(msg) + *already_sent_bytes, to_send, 0,
+           PR_INTERVAL_NO_TIMEOUT);
+
+       if (sent > 0) {
+               *already_sent_bytes += sent;
+
+               if (*already_sent_bytes == dynar_size(msg)) {
+                       /*
+                        * All data sent
+                        */
+                       return (1);
+               }
+       }
+
+       if (sent == 0) {
+               return (-1);
+       }
+
+       if (sent < 0 && PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+               return (-2);
+       }
+
+       return (0);
+}
+
+/*
+ *  1 Full message received
+ *  0 Partial read (no error)
+ * -1 End of connection
+ * -2 Unhandled error
+ * -3 Fatal error. Unable to store message header
+ * -4 Unable to store message
+ * -5 Invalid msg type
+ * -6 Msg too long
+ */
+int
+msgio_read(PRFileDesc *sock, struct dynar *msg, size_t *already_received_bytes, int *skipping_msg)
+{
+       char local_read_buffer[MSGIO_LOCAL_BUF_SIZE];
+       PRInt32 readed;
+       PRInt32 to_read;
+       int ret;
+
+       ret = 0;
+
+       if (*already_received_bytes < msg_get_header_length()) {
+               /*
+                * Complete reading of header
+                */
+               to_read = msg_get_header_length() - *already_received_bytes;
+       } else {
+               /*
+                * Read rest of message (or at least as much as possible)
+                */
+               to_read = (msg_get_header_length() + msg_get_len(msg)) - *already_received_bytes;
+       }
+
+       if (to_read > MSGIO_LOCAL_BUF_SIZE) {
+               to_read = MSGIO_LOCAL_BUF_SIZE;
+       }
+
+       readed = PR_Recv(sock, local_read_buffer, to_read, 0, PR_INTERVAL_NO_TIMEOUT);
+       if (readed > 0) {
+               *already_received_bytes += readed;
+
+               if (!*skipping_msg) {
+                       if (dynar_cat(msg, local_read_buffer, readed) == -1) {
+                               *skipping_msg = 1;
+                               ret = -4;
+                       }
+               }
+
+               if (*skipping_msg && *already_received_bytes < msg_get_header_length()) {
+                       /*
+                        * Fatal error. We were unable to store even message header
+                        */
+                       return (-3);
+               }
+
+               if (!*skipping_msg && *already_received_bytes == msg_get_header_length()) {
+                       /*
+                        * Full header received. Check type, maximum size, ...
+                        */
+                       if (!msg_is_valid_msg_type(msg)) {
+                               *skipping_msg = 1;
+                               ret = -5;
+                       } else if ((msg_get_header_length() + msg_get_len(msg)) >
+                           dynar_max_size(msg)) {
+                               *skipping_msg = 1;
+                               ret = -6;
+                       }
+               }
+
+               if (*already_received_bytes >= msg_get_header_length() &&
+                   *already_received_bytes == (msg_get_header_length() + msg_get_len(msg))) {
+                       /*
+                        * Full message skipped or received
+                        */
+                       ret = 1;
+               }
+
+       }
+
+       if (readed == 0) {
+               return (-1);
+       }
+
+       if (readed < 0 && PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+               return (-2);
+       }
+
+       return (ret);
+}
diff --git a/qdevices/msgio.h b/qdevices/msgio.h
new file mode 100644 (file)
index 0000000..aacb7a8
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _MSGIO_H_
+#define _MSGIO_H_
+
+#include <nspr.h>
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern ssize_t msgio_send(PRFileDesc *sock, const char *msg, size_t msg_len,
+    size_t *start_pos);
+
+extern ssize_t msgio_send_blocking(PRFileDesc *sock, const char *msg, size_t msg_len);
+
+extern int     msgio_write(PRFileDesc *sock, const struct dynar *msg, size_t *already_sent_bytes);
+
+extern int     msgio_read(PRFileDesc *sock, struct dynar *msg, size_t *already_received_bytes,
+    int *skipping_msg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MSGIO_H_ */
diff --git a/qdevices/node-list.c b/qdevices/node-list.c
new file mode 100644 (file)
index 0000000..ec0efc4
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "node-list.h"
+
+void
+node_list_init(struct node_list *list)
+{
+
+       TAILQ_INIT(list);
+}
+
+struct node_list_entry *
+node_list_add(struct node_list *list, uint32_t node_id, uint32_t data_center_id,
+    enum tlv_node_state node_state)
+{
+       struct node_list_entry *node;
+
+       node = (struct node_list_entry *)malloc(sizeof(*node));
+       if (node == NULL) {
+               return (NULL);
+       }
+
+       memset(node, 0, sizeof(*node));
+
+       node->node_id = node_id;
+       node->data_center_id = data_center_id;
+       node->node_state = node_state;
+
+       TAILQ_INSERT_TAIL(list, node, entries);
+
+       return (node);
+}
+
+struct node_list_entry *
+node_list_add_from_node_info(struct node_list *list, const struct tlv_node_info *node_info)
+{
+
+       return (node_list_add(list, node_info->node_id, node_info->data_center_id,
+           node_info->node_state));
+}
+
+int
+node_list_clone(struct node_list *dst_list, const struct node_list *src_list)
+{
+       struct node_list_entry *node_entry;
+
+       node_list_init(dst_list);
+
+       TAILQ_FOREACH(node_entry, src_list, entries) {
+               if (node_list_add(dst_list, node_entry->node_id, node_entry->data_center_id,
+                   node_entry->node_state) == NULL) {
+                       node_list_free(dst_list);
+
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+void
+node_list_entry_to_tlv_node_info(const struct node_list_entry *node,
+    struct tlv_node_info *node_info)
+{
+
+       node_info->node_id = node->node_id;
+       node_info->data_center_id = node->data_center_id;
+       node_info->node_state = node->node_state;
+}
+
+void
+node_list_free(struct node_list *list)
+{
+       struct node_list_entry *node;
+       struct node_list_entry *node_next;
+
+       node = TAILQ_FIRST(list);
+
+       while (node != NULL) {
+               node_next = TAILQ_NEXT(node, entries);
+
+               free(node);
+
+               node = node_next;
+       }
+
+       TAILQ_INIT(list);
+}
+
+void
+node_list_del(struct node_list *list, struct node_list_entry *node)
+{
+
+       TAILQ_REMOVE(list, node, entries);
+
+       free(node);
+}
+
+int
+node_list_is_empty(const struct node_list *list)
+{
+
+       return (TAILQ_EMPTY(list));
+}
+
+struct node_list_entry *
+node_list_find_node_id(const struct node_list *list, uint32_t node_id)
+{
+       struct node_list_entry *node_entry;
+
+       TAILQ_FOREACH(node_entry, list, entries) {
+               if (node_entry->node_id == node_id) {
+                       return (node_entry);
+               }
+       }
+
+       return (NULL);
+}
+
+int
+node_list_eq(const struct node_list *list1, const struct node_list *list2)
+{
+       struct node_list_entry *node1_entry;
+       struct node_list_entry *node2_entry;
+       struct node_list tmp_list;
+       int res;
+
+       res = 1;
+
+       if (node_list_clone(&tmp_list, list2) != 0) {
+               return (-1);
+       }
+
+       TAILQ_FOREACH(node1_entry, list1, entries) {
+               node2_entry = node_list_find_node_id(&tmp_list, node1_entry->node_id);
+               if (node2_entry == NULL) {
+                       res = 0;
+                       goto return_res;
+               }
+
+               if (node1_entry->node_id != node2_entry->node_id ||
+                   node1_entry->data_center_id != node2_entry->data_center_id ||
+                   node1_entry->node_state != node2_entry->node_state) {
+                       res = 0;
+                       goto return_res;
+               }
+
+               node_list_del(&tmp_list, node2_entry);
+       }
+
+       if (!node_list_is_empty(&tmp_list)) {
+               res = 0;
+               goto return_res;
+       }
+
+return_res:
+       node_list_free(&tmp_list);
+
+       return (res);
+}
+
+size_t
+node_list_size(const struct node_list *nlist)
+{
+       struct node_list_entry *node_entry;
+       size_t res;
+
+       res = 0;
+
+       TAILQ_FOREACH(node_entry, nlist, entries) {
+               res++;
+       }
+
+       return (res);
+}
diff --git a/qdevices/node-list.h b/qdevices/node-list.h
new file mode 100644 (file)
index 0000000..b05b62d
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NODE_LIST_H_
+#define _NODE_LIST_H_
+
+#include <sys/types.h>
+
+#include <sys/queue.h>
+#include <inttypes.h>
+
+#include "tlv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct node_list_entry {
+       uint32_t node_id;
+       uint32_t data_center_id;
+       enum tlv_node_state node_state;
+       TAILQ_ENTRY(node_list_entry) entries;
+};
+
+TAILQ_HEAD(node_list, node_list_entry);
+
+extern void                             node_list_init(struct node_list *list);
+
+extern struct node_list_entry          *node_list_add(struct node_list *list,
+    uint32_t node_id, uint32_t data_center_id, enum tlv_node_state node_state);
+
+extern struct node_list_entry          *node_list_add_from_node_info(
+    struct node_list *list, const struct tlv_node_info *node_info);
+
+extern int                              node_list_clone(struct node_list *dst_list,
+    const struct node_list *src_list);
+
+extern void                             node_list_free(struct node_list *list);
+
+extern void                             node_list_del(struct node_list *list,
+    struct node_list_entry *node);
+
+extern int                              node_list_is_empty(const struct node_list *list);
+
+extern void                             node_list_entry_to_tlv_node_info(
+    const struct node_list_entry *node, struct tlv_node_info *node_info);
+
+extern struct node_list_entry *                 node_list_find_node_id(const struct node_list *list,
+    uint32_t node_id);
+
+extern int                              node_list_eq(const struct node_list *list1,
+    const struct node_list *list2);
+
+extern size_t                           node_list_size(const struct node_list *nlist);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _NODE_LIST_H_ */
diff --git a/qdevices/nss-sock.c b/qdevices/nss-sock.c
new file mode 100644 (file)
index 0000000..f08ac88
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <limits.h>
+
+#include "nss-sock.h"
+
+int
+nss_sock_init_nss(char *config_dir)
+{
+       if (config_dir == NULL) {
+               if (NSS_NoDB_Init(NULL) != SECSuccess) {
+                       return (-1);
+               }
+       } else {
+               if (NSS_Init(config_dir) != SECSuccess) {
+                       return (-1);
+               }
+       }
+
+       if (NSS_SetDomesticPolicy() != SECSuccess) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+/*
+ * Set NSS socket non-blocking
+ */
+int
+nss_sock_set_non_blocking(PRFileDesc *sock)
+{
+       PRSocketOptionData sock_opt;
+
+       memset(&sock_opt, 0, sizeof(sock_opt));
+       sock_opt.option = PR_SockOpt_Nonblocking;
+       sock_opt.value.non_blocking = PR_TRUE;
+       if (PR_SetSocketOption(sock, &sock_opt) != PR_SUCCESS) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+/*
+ * Create TCP socket with af family. If reuse_addr is set, socket option
+ * for reuse address is set.
+ */
+static PRFileDesc *
+nss_sock_create_socket(PRIntn af, int reuse_addr)
+{
+       PRFileDesc *sock;
+       PRSocketOptionData socket_option;
+
+       sock = PR_OpenTCPSocket(af);
+       if (sock == NULL) {
+               return (NULL);
+       }
+
+       if (reuse_addr) {
+               socket_option.option = PR_SockOpt_Reuseaddr;
+               socket_option.value.reuse_addr = PR_TRUE;
+               if (PR_SetSocketOption(sock, &socket_option) != PR_SUCCESS) {
+                       return (NULL);
+               }
+       }
+
+       return (sock);
+}
+
+/*
+ * Create listen socket and bind it to address. hostname can be NULL and then
+ * any address is used. Address family (af) can be ether PR_AF_INET6,
+ * PR_AF_INET or PR_AF_UNSPEC.
+ */
+PRFileDesc *
+nss_sock_create_listen_socket(const char *hostname, uint16_t port, PRIntn af)
+{
+       PRNetAddr addr;
+       PRFileDesc *sock;
+       PRAddrInfo *addr_info;
+       void *addr_iter;
+
+       sock = NULL;
+
+       if (hostname == NULL) {
+               memset(&addr, 0, sizeof(addr));
+
+               if (PR_InitializeNetAddr(PR_IpAddrAny, port, &addr) != PR_SUCCESS) {
+                       return (NULL);
+               }
+               if (af == PR_AF_UNSPEC) {
+                       af = PR_AF_INET6;
+               }
+               addr.raw.family = af;
+
+               sock = nss_sock_create_socket(af, 1);
+               if (sock == NULL) {
+                       return (NULL);
+               }
+
+               if (PR_Bind(sock, &addr) != PR_SUCCESS) {
+                       PR_Close(sock);
+
+                       return (NULL);
+               }
+       } else {
+               addr_info = PR_GetAddrInfoByName(hostname, af, PR_AI_ADDRCONFIG);
+               if (addr_info == NULL) {
+                       return (NULL);
+               }
+
+               addr_iter = NULL;
+
+               while ((addr_iter = PR_EnumerateAddrInfo(addr_iter, addr_info, port,
+                   &addr)) != NULL) {
+                       if (af == PR_AF_UNSPEC || addr.raw.family == af) {
+                               sock = nss_sock_create_socket(addr.raw.family, 1);
+                               if (sock == NULL) {
+                                       continue;
+                               }
+
+                               if (PR_Bind(sock, &addr) != PR_SUCCESS) {
+                                       PR_Close(sock);
+                                       sock = NULL;
+
+                                       continue;
+                               }
+
+                               /*
+                                * Socket is sucesfully bound
+                                */
+                               break;
+                       }
+               }
+
+               PR_FreeAddrInfo(addr_info);
+
+               if (sock == NULL) {
+                       /*
+                        * No address succeeded
+                        */
+                       PR_SetError(PR_ADDRESS_NOT_AVAILABLE_ERROR, 0);
+
+                       return (NULL);
+               }
+       }
+
+       return (sock);
+}
+
+PRFileDesc *
+nss_sock_create_client_socket(const char *hostname, uint16_t port, PRIntn af,
+    PRIntervalTime timeout)
+{
+       PRNetAddr addr;
+       PRFileDesc *sock;
+       PRAddrInfo *addr_info;
+       void *addr_iter;
+       PRStatus res;
+       int connect_failed;
+       PRIntn tmp_af;
+
+       sock = NULL;
+       connect_failed = 0;
+
+       tmp_af = af;
+       if (af == PR_AF_INET6) {
+               tmp_af = PR_AF_UNSPEC;
+       }
+
+       addr_info = PR_GetAddrInfoByName(hostname, tmp_af, PR_AI_ADDRCONFIG);
+       if (addr_info == NULL) {
+               return (NULL);
+       }
+
+       addr_iter = NULL;
+
+       while ((addr_iter = PR_EnumerateAddrInfo(addr_iter, addr_info, port, &addr)) != NULL) {
+               if (af != PR_AF_UNSPEC && addr.raw.family != af) {
+                       continue;
+               }
+
+               sock = nss_sock_create_socket(addr.raw.family, 0);
+               if (sock == NULL) {
+                       continue;
+               }
+
+               if ((res = PR_Connect(sock, &addr, timeout)) != PR_SUCCESS) {
+                       PR_Close(sock);
+                       sock = NULL;
+                       connect_failed = 1;
+               }
+
+               /*
+                * Connection attempt finished
+                */
+               break;
+       }
+
+       PR_FreeAddrInfo(addr_info);
+
+       if (sock == NULL && !connect_failed) {
+               PR_SetError(PR_ADDRESS_NOT_AVAILABLE_ERROR, 0);
+       }
+
+       return (sock);
+}
+
+int
+nss_sock_non_blocking_client_init(const char *host_name, uint16_t port, PRIntn af,
+    struct nss_sock_non_blocking_client *client)
+{
+       PRIntn tmp_af;
+
+       client->destroyed = 1;
+
+       if ((client->host_name = strdup(host_name)) == NULL) {
+               PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+
+               return (-1);
+       }
+
+       client->port = port;
+       client->af = af;
+
+       tmp_af = af;
+       if (af == PR_AF_INET6) {
+               tmp_af = PR_AF_UNSPEC;
+       }
+
+       client->addr_info = PR_GetAddrInfoByName(client->host_name, tmp_af, PR_AI_ADDRCONFIG);
+       if (client->addr_info == NULL) {
+               free(client->host_name);
+
+               return (-1);
+       }
+       client->addr_iter = NULL;
+       client->connect_attempts = 0;
+       client->socket = NULL;
+       client->destroyed = 0;
+
+       return (0);
+}
+
+int
+nss_sock_non_blocking_client_try_next(struct nss_sock_non_blocking_client *client)
+{
+       PRNetAddr addr;
+       PRStatus res;
+
+       if (client->destroyed) {
+               PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+               return (-1);
+       }
+
+       if (client->socket != NULL) {
+               PR_Close(client->socket);
+               client->socket = NULL;
+       }
+
+       while ((client->addr_iter = PR_EnumerateAddrInfo(client->addr_iter, client->addr_info,
+           client->port, &addr)) != NULL) {
+               if (client->af != PR_AF_UNSPEC && addr.raw.family != client->af) {
+                       continue;
+               }
+
+               client->socket = nss_sock_create_socket(addr.raw.family, 0);
+               if (client->socket == NULL) {
+                       continue;
+               }
+
+               if (nss_sock_set_non_blocking(client->socket) == -1) {
+                       PR_Close(client->socket);
+                       client->socket = NULL;
+                       continue;
+               }
+
+               res = PR_Connect(client->socket, &addr, PR_INTERVAL_NO_TIMEOUT);
+               if (res == PR_SUCCESS || PR_GetError() == PR_IN_PROGRESS_ERROR) {
+                       return (0);
+               }
+
+               PR_Close(client->socket);
+               client->socket = NULL;
+
+               if (client->connect_attempts < INT_MAX) {
+                       client->connect_attempts++;
+               }
+       }
+
+       if (client->connect_attempts == 0) {
+               PR_SetError(PR_ADDRESS_NOT_AVAILABLE_ERROR, 0);
+       }
+
+       return (-1);
+}
+
+void
+nss_sock_non_blocking_client_destroy(struct nss_sock_non_blocking_client *client)
+{
+
+       if (client->destroyed) {
+               return ;
+       }
+
+       if (client->addr_info != NULL) {
+               PR_FreeAddrInfo(client->addr_info);
+               client->addr_info = NULL;
+       }
+
+       free(client->host_name);
+       client->host_name = NULL;
+
+       client->destroyed = 1;
+}
+
+/*
+ * -1 = Client connect failed
+ *  0 = Client connect still in progress
+ *  1 = Client successfuly connected
+ */
+int
+nss_sock_non_blocking_client_succeeded(const PRPollDesc *pfd)
+{
+       int res;
+
+       res = -1;
+
+       if (PR_GetConnectStatus(pfd) == PR_SUCCESS) {
+               res = 1;
+       } else {
+               if (PR_GetError() == PR_IN_PROGRESS_ERROR) {
+                       res = 0;
+               } else {
+                       res = -1;
+               }
+       }
+
+       return (res);
+}
+
+/*
+ * Start client side SSL connection. This can block.
+ *
+ * ssl_url is expected server URL, bad_cert_hook is callback called when server certificate
+ * verification fails.
+ */
+PRFileDesc *
+nss_sock_start_ssl_as_client(PRFileDesc *input_sock, const char *ssl_url,
+    SSLBadCertHandler bad_cert_hook, SSLGetClientAuthData client_auth_hook,
+    void *client_auth_hook_arg, int force_handshake, int *reset_would_block)
+{
+       PRFileDesc *ssl_sock;
+
+       if (force_handshake) {
+               *reset_would_block = 0;
+       }
+
+       ssl_sock = SSL_ImportFD(NULL, input_sock);
+       if (ssl_sock == NULL) {
+               return (NULL);
+       }
+
+       if (SSL_SetURL(ssl_sock, ssl_url) != SECSuccess) {
+               return (NULL);
+       }
+
+       if ((SSL_OptionSet(ssl_sock, SSL_SECURITY, PR_TRUE) != SECSuccess) ||
+           (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) ||
+           (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess)) {
+               return (NULL);
+       }
+       if (bad_cert_hook != NULL && SSL_BadCertHook(ssl_sock, bad_cert_hook, NULL) != SECSuccess) {
+               return (NULL);
+       }
+
+       if (client_auth_hook != NULL &&
+           (SSL_GetClientAuthDataHook(ssl_sock, client_auth_hook,
+           client_auth_hook_arg) != SECSuccess)) {
+               return (NULL);
+       }
+
+       if (SSL_ResetHandshake(ssl_sock, PR_FALSE) != SECSuccess) {
+               return (NULL);
+       }
+
+       if (force_handshake && SSL_ForceHandshake(ssl_sock) != SECSuccess) {
+               if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+                       /*
+                        * Mask would block error.
+                        */
+                       *reset_would_block = 1;
+               } else {
+                       return (NULL);
+               }
+       }
+
+       return (ssl_sock);
+}
+
+PRFileDesc *
+nss_sock_start_ssl_as_server(PRFileDesc *input_sock, CERTCertificate *server_cert,
+    SECKEYPrivateKey *server_key, int require_client_cert, int force_handshake,
+    int *reset_would_block)
+{
+       PRFileDesc *ssl_sock;
+
+       if (force_handshake) {
+               *reset_would_block = 0;
+       }
+
+       ssl_sock = SSL_ImportFD(NULL, input_sock);
+       if (ssl_sock == NULL) {
+               return (NULL);
+       }
+
+       if (SSL_ConfigSecureServer(ssl_sock, server_cert, server_key,
+           NSS_FindCertKEAType(server_cert)) != SECSuccess) {
+               return (NULL);
+       }
+
+       if ((SSL_OptionSet(ssl_sock, SSL_SECURITY, PR_TRUE) != SECSuccess) ||
+           (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess) ||
+           (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess) ||
+           (SSL_OptionSet(ssl_sock, SSL_REQUEST_CERTIFICATE, require_client_cert) != SECSuccess) ||
+           (SSL_OptionSet(ssl_sock, SSL_REQUIRE_CERTIFICATE, require_client_cert) != SECSuccess)) {
+               return (NULL);
+       }
+
+       if (SSL_ResetHandshake(ssl_sock, PR_TRUE) != SECSuccess) {
+               return (NULL);
+       }
+
+       if (force_handshake && SSL_ForceHandshake(ssl_sock) != SECSuccess) {
+               if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+                       /*
+                        * Mask would block error.
+                        */
+                       *reset_would_block = 1;
+               } else {
+                       return (NULL);
+               }
+       }
+
+       return (ssl_sock);
+}
diff --git a/qdevices/nss-sock.h b/qdevices/nss-sock.h
new file mode 100644 (file)
index 0000000..cc16d96
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NSS_SOCK_H_
+#define _NSS_SOCK_H_
+
+#include <nss.h>
+#include <ssl.h>
+#include <prnetdb.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct nss_sock_non_blocking_client {
+       char *host_name;
+       uint16_t port;
+       PRIntn af;
+       PRFileDesc *socket;
+       PRAddrInfo *addr_info;
+       void *addr_iter;
+       unsigned int connect_attempts;
+       int destroyed;
+};
+
+extern int             nss_sock_init_nss(char *config_dir);
+
+extern PRFileDesc      *nss_sock_create_listen_socket(const char *hostname, uint16_t port,
+    PRIntn af);
+
+extern int             nss_sock_set_non_blocking(PRFileDesc *sock);
+
+extern PRFileDesc      *nss_sock_create_client_socket(const char *hostname, uint16_t port,
+    PRIntn af, PRIntervalTime timeout);
+
+extern PRFileDesc      *nss_sock_start_ssl_as_client(PRFileDesc *input_sock, const char *ssl_url,
+    SSLBadCertHandler bad_cert_hook, SSLGetClientAuthData client_auth_hook,
+    void *client_auth_hook_arg, int force_handshake, int *reset_would_block);
+
+extern PRFileDesc      *nss_sock_start_ssl_as_server(PRFileDesc *input_sock,
+    CERTCertificate *server_cert, SECKEYPrivateKey *server_key, int require_client_cert,
+    int force_handshake, int *reset_would_block);
+
+extern int              nss_sock_non_blocking_client_init(const char *host_name,
+    uint16_t port, PRIntn af, struct nss_sock_non_blocking_client *client);
+
+extern int              nss_sock_non_blocking_client_try_next(
+    struct nss_sock_non_blocking_client *client);
+
+extern void             nss_sock_non_blocking_client_destroy(
+    struct nss_sock_non_blocking_client *client);
+
+extern int              nss_sock_non_blocking_client_succeeded(const PRPollDesc *pfd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _NSS_SOCK_H_ */
diff --git a/qdevices/pr-poll-array.c b/qdevices/pr-poll-array.c
new file mode 100644 (file)
index 0000000..a2ba45f
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pr-poll-array.h"
+
+void
+pr_poll_array_init(struct pr_poll_array *poll_array, size_t user_data_size)
+{
+
+       memset(poll_array, 0, sizeof(*poll_array));
+       poll_array->user_data_size = user_data_size;
+}
+
+void
+pr_poll_array_destroy(struct pr_poll_array *poll_array)
+{
+
+       free(poll_array->array);
+       free(poll_array->user_data_array);
+       pr_poll_array_init(poll_array, poll_array->user_data_size);
+}
+
+void
+pr_poll_array_clean(struct pr_poll_array *poll_array)
+{
+
+       poll_array->items = 0;
+}
+
+static int
+pr_poll_array_realloc(struct pr_poll_array *poll_array,
+    ssize_t new_array_size)
+{
+       PRPollDesc *new_array;
+       char *new_user_data_array;
+
+       new_array = realloc(poll_array->array,
+           sizeof(PRPollDesc) * new_array_size);
+
+       if (new_array == NULL) {
+               return (-1);
+       }
+
+       poll_array->allocated = new_array_size;
+       poll_array->array = new_array;
+
+       if (poll_array->user_data_size > 0) {
+               new_user_data_array = realloc(poll_array->user_data_array,
+                   poll_array->user_data_size * new_array_size);
+
+               if (new_user_data_array == NULL) {
+                       return (-1);
+               }
+
+               poll_array->user_data_array = new_user_data_array;
+       }
+
+       return (0);
+}
+
+ssize_t
+pr_poll_array_size(struct pr_poll_array *poll_array)
+{
+
+       return (poll_array->items);
+}
+
+ssize_t
+pr_poll_array_add(struct pr_poll_array *poll_array, PRPollDesc **pfds, void **user_data)
+{
+
+       if (pr_poll_array_size(poll_array) >= poll_array->allocated) {
+               if (pr_poll_array_realloc(poll_array, (poll_array->allocated * 2) + 1)) {
+                       return (-1);
+               }
+       }
+
+       *pfds = &poll_array->array[pr_poll_array_size(poll_array)];
+       memset(*pfds, 0, sizeof(**pfds));
+
+       *user_data = poll_array->user_data_array + (poll_array->items * poll_array->user_data_size);
+       memset(*user_data, 0, poll_array->user_data_size);
+
+       poll_array->items++;
+
+       return (poll_array->items - 1);
+}
+
+void
+pr_poll_array_gc(struct pr_poll_array *poll_array)
+{
+
+       if (poll_array->allocated > (pr_poll_array_size(poll_array) * 3) + 1) {
+               pr_poll_array_realloc(poll_array, (pr_poll_array_size(poll_array) * 2) + 1);
+       }
+}
+
+PRPollDesc *
+pr_poll_array_get(const struct pr_poll_array *poll_array, ssize_t pos)
+{
+
+       if (pos >= poll_array->items) {
+               return (NULL);
+       }
+
+       return (&poll_array->array[pos]);
+}
+
+void *
+pr_poll_array_get_user_data(const struct pr_poll_array *poll_array, ssize_t pos)
+{
+
+       if (pos >= poll_array->items) {
+               return (NULL);
+       }
+
+       return (poll_array->user_data_array + (pos * poll_array->user_data_size));
+}
diff --git a/qdevices/pr-poll-array.h b/qdevices/pr-poll-array.h
new file mode 100644 (file)
index 0000000..221754c
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PR_POLL_ARRAY_H_
+#define _PR_POLL_ARRAY_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <nspr.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pr_poll_array {
+       PRPollDesc *array;
+       char *user_data_array;
+       size_t user_data_size;
+       ssize_t allocated;
+       ssize_t items;
+};
+
+extern void             pr_poll_array_init(struct pr_poll_array *poll_array, size_t user_data_size);
+
+extern void             pr_poll_array_destroy(struct pr_poll_array *poll_array);
+
+extern void             pr_poll_array_clean(struct pr_poll_array *poll_array);
+
+extern ssize_t          pr_poll_array_size(struct pr_poll_array *poll_array);
+
+extern ssize_t          pr_poll_array_add(struct pr_poll_array *poll_array,  PRPollDesc **pfds,
+    void **user_data);
+
+extern PRPollDesc      *pr_poll_array_get(const struct pr_poll_array *poll_array,
+    ssize_t pos);
+
+extern void            *pr_poll_array_get_user_data(const struct pr_poll_array *poll_array,
+    ssize_t pos);
+
+extern void             pr_poll_array_gc(struct pr_poll_array *poll_array);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PR_POLL_ARRAY_H_ */
diff --git a/qdevices/process-list.c b/qdevices/process-list.c
new file mode 100644 (file)
index 0000000..ad2d598
--- /dev/null
@@ -0,0 +1,621 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "dynar-simple-lex.h"
+#include "process-list.h"
+
+static void            process_list_free_argv(size_t no_params, char **argv);
+
+static void            process_list_entry_free(struct process_list_entry *entry);
+
+static char            **process_list_parse_command(const char *command, size_t *no_params);
+
+static int             process_list_entry_exec(const struct process_list *plist,
+    struct process_list_entry *entry);
+
+void
+process_list_init(struct process_list *plist, size_t max_list_entries, int use_execvp,
+    process_list_notify_fn_t notify_fn, void *notify_fn_user_data)
+{
+
+       memset(plist, 0, sizeof(*plist));
+
+       plist->max_list_entries = max_list_entries;
+       plist->allocated_list_entries = 0;
+       plist->use_execvp = use_execvp;
+       plist->notify_fn = notify_fn;
+       plist->notify_fn_user_data = notify_fn_user_data;
+
+       TAILQ_INIT(&plist->active_list);
+       TAILQ_INIT(&plist->to_kill_list);
+}
+
+static void
+process_list_free_argv(size_t no_params, char **argv)
+{
+       size_t zi;
+
+       for (zi = 0; zi < no_params; zi++) {
+               free(argv[zi]);
+       }
+       free(argv);
+}
+
+static void
+process_list_entry_free(struct process_list_entry *entry)
+{
+
+       process_list_free_argv(entry->exec_argc, entry->exec_argv);
+       free(entry->name);
+       free(entry);
+}
+
+static char **
+process_list_parse_command(const char *command, size_t *no_params)
+{
+       struct dynar command_dstr;
+       struct dynar_simple_lex lex;
+       struct dynar *token;
+       int finished;
+       char **res_argv;
+       size_t zi;
+
+       res_argv = NULL;
+
+       dynar_init(&command_dstr, strlen(command) + 1);
+       if (dynar_str_cpy(&command_dstr, command) != 0) {
+               return (NULL);
+       }
+
+       dynar_simple_lex_init(&lex, &command_dstr, DYNAR_SIMPLE_LEX_TYPE_QUOTE);
+       *no_params = 0;
+       finished = 0;
+
+       while (!finished) {
+               token = dynar_simple_lex_token_next(&lex);
+               if (token == NULL) {
+                       goto exit_res;
+               }
+
+               if (strcmp(dynar_data(token), "") == 0) {
+                       finished = 1;
+               } else {
+                       (*no_params)++;
+               }
+       }
+
+       if (*no_params < 1) {
+               goto exit_res;
+       }
+
+       dynar_simple_lex_destroy(&lex);
+
+       res_argv = malloc(sizeof(char *) * (*no_params + 1));
+       if (res_argv == NULL) {
+               goto exit_res;
+       }
+       memset(res_argv, 0, sizeof(char *) * (*no_params + 1));
+
+       dynar_simple_lex_init(&lex, &command_dstr, DYNAR_SIMPLE_LEX_TYPE_QUOTE);
+
+       finished = 0;
+       zi = 0;
+       while (!finished) {
+               token = dynar_simple_lex_token_next(&lex);
+               if (token == NULL) {
+                       process_list_free_argv(*no_params, res_argv);
+                       res_argv = NULL;
+                       goto exit_res;
+               }
+
+               if (strcmp(dynar_data(token), "") == 0) {
+                       finished = 1;
+               } else {
+                       res_argv[zi] = strdup(dynar_data(token));
+                       if (res_argv[zi] == NULL) {
+                               process_list_free_argv(*no_params, res_argv);
+                               res_argv = NULL;
+                       }
+                       zi++;
+               }
+       }
+
+       if (zi != *no_params) {
+               /*
+                * If this happens it means something is seriously broken (memory corrupted)
+                */
+               process_list_free_argv(*no_params, res_argv);
+               res_argv = NULL;
+               goto exit_res;
+       }
+
+exit_res:
+       dynar_simple_lex_destroy(&lex);
+       dynar_destroy(&command_dstr);
+       return (res_argv);
+}
+
+struct process_list_entry *
+process_list_add(struct process_list *plist, const char *name, const char *command)
+{
+       struct process_list_entry *entry;
+
+       if (plist->allocated_list_entries + 1 > plist->max_list_entries) {
+               return (NULL);
+       }
+
+       /*
+        * Alloc new entry
+        */
+       entry = malloc(sizeof(*entry));
+       if (entry == NULL) {
+               return (NULL);
+       }
+
+       memset(entry, 0, sizeof(*entry));
+       entry->name = strdup(name);
+       if (entry->name == NULL) {
+               process_list_entry_free(entry);
+
+               return (NULL);
+       }
+
+       entry->state = PROCESS_LIST_ENTRY_STATE_INITIALIZED;
+       entry->exec_argv = process_list_parse_command(command, &entry->exec_argc);
+       if (entry->exec_argv == NULL) {
+               process_list_entry_free(entry);
+
+               return (NULL);
+       }
+
+       plist->allocated_list_entries++;
+       TAILQ_INSERT_TAIL(&plist->active_list, entry, entries);
+
+       return (entry);
+}
+
+void
+process_list_free(struct process_list *plist)
+{
+       struct process_list_entry *entry;
+       struct process_list_entry *entry_next;
+
+       entry = TAILQ_FIRST(&plist->active_list);
+
+       while (entry != NULL) {
+               entry_next = TAILQ_NEXT(entry, entries);
+
+               process_list_entry_free(entry);
+
+               entry = entry_next;
+       }
+
+       entry = TAILQ_FIRST(&plist->to_kill_list);
+
+       while (entry != NULL) {
+               entry_next = TAILQ_NEXT(entry, entries);
+
+               process_list_entry_free(entry);
+
+               entry = entry_next;
+       }
+
+       plist->allocated_list_entries = 0;
+
+       TAILQ_INIT(&plist->active_list);
+       TAILQ_INIT(&plist->to_kill_list);
+}
+
+static void
+process_list_entry_exec_helper_set_stdfd(void)
+{
+       int devnull;
+
+       devnull = open("/dev/null", O_RDWR);
+       if (devnull == -1) {
+               err(1, "Can't open /dev/null");
+       }
+
+       if (dup2(devnull, 0) < 0 || dup2(devnull, 1) < 0 || dup2(devnull, 2) < 0) {
+               close(devnull);
+               err(1, "Can't dup2 stdin/out/err to /dev/null");
+       }
+
+       close(devnull);
+}
+
+static int
+process_list_entry_exec(const struct process_list *plist, struct process_list_entry *entry)
+{
+       pid_t pid;
+
+       if (entry->state != PROCESS_LIST_ENTRY_STATE_INITIALIZED) {
+               return (-1);
+       }
+
+       pid = fork();
+       if (pid == -1) {
+               return (-1);
+       } else if (pid == 0) {
+               process_list_entry_exec_helper_set_stdfd();
+
+               if (!plist->use_execvp) {
+                       execv(entry->exec_argv[0], entry->exec_argv);
+               } else {
+                       execvp(entry->exec_argv[0], entry->exec_argv);
+               }
+
+               /*
+                * Exec returned -> exec failed
+                */
+               err(1, "Can't execute command %s (%s)", entry->name, entry->exec_argv[0]);
+       } else {
+               entry->pid = pid;
+               entry->state = PROCESS_LIST_ENTRY_STATE_RUNNING;
+
+               if (plist->notify_fn != NULL) {
+                       plist->notify_fn(PROCESS_LIST_NOTIFY_REASON_EXECUTED, entry,
+                           plist->notify_fn_user_data);
+               }
+       }
+
+       return (0);
+}
+
+int
+process_list_exec_initialized(struct process_list *plist)
+{
+       struct process_list_entry *entry;
+
+       TAILQ_FOREACH(entry, &plist->active_list, entries) {
+               if (entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED) {
+                       if (process_list_entry_exec(plist, entry) != 0) {
+                               return (-1);
+                       }
+               }
+       }
+
+       return (0);
+}
+
+static int
+process_list_entry_waitpid(const struct process_list *plist, struct process_list_entry *entry)
+{
+       pid_t wpid_res;
+       int status;
+
+       if (entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED ||
+           entry->state == PROCESS_LIST_ENTRY_STATE_FINISHED) {
+               return (0);
+       }
+
+       wpid_res = waitpid(entry->pid, &status, WNOHANG);
+       if (wpid_res == -1) {
+               return (-1);
+       }
+
+       if (wpid_res == 0) {
+               /*
+                * No change
+                */
+               return (0);
+       }
+
+       entry->exit_status = status;
+
+       if (entry->state == PROCESS_LIST_ENTRY_STATE_RUNNING) {
+               if (plist->notify_fn != NULL) {
+                       plist->notify_fn(PROCESS_LIST_NOTIFY_REASON_FINISHED, entry,
+                           plist->notify_fn_user_data);
+               }
+       }
+
+       entry->state = PROCESS_LIST_ENTRY_STATE_FINISHED;
+
+       return (0);
+}
+
+int
+process_list_waitpid(struct process_list *plist)
+{
+       struct process_list_entry *entry;
+       struct process_list_entry *entry_next;
+
+       TAILQ_FOREACH(entry, &plist->active_list, entries) {
+               if (process_list_entry_waitpid(plist, entry) != 0) {
+                       return (-1);
+               }
+       }
+
+       entry = TAILQ_FIRST(&plist->to_kill_list);
+
+       while (entry != NULL) {
+               entry_next = TAILQ_NEXT(entry, entries);
+
+               if (process_list_entry_waitpid(plist, entry) != 0) {
+                       return (-1);
+               }
+
+               if (entry->state == PROCESS_LIST_ENTRY_STATE_FINISHED) {
+                       /*
+                        * Process finished -> remove it from list
+                        */
+                       TAILQ_REMOVE(&plist->to_kill_list, entry, entries);
+                       process_list_entry_free(entry);
+                       plist->allocated_list_entries--;
+               }
+
+               entry = entry_next;
+       }
+
+       return (0);
+}
+
+size_t
+process_list_get_no_running(struct process_list *plist)
+{
+       struct process_list_entry *entry;
+       size_t res;
+
+       res = 0;
+
+       TAILQ_FOREACH(entry, &plist->active_list, entries) {
+               if (entry->state == PROCESS_LIST_ENTRY_STATE_RUNNING) {
+                       res++;
+               }
+       }
+
+       return (res);
+}
+
+/*
+ * -1 = Not all processes finished
+ *  0 = All processes finished sucesfully
+ *  1 - All processes finished but some of them not sucesfully
+ */
+int
+process_list_get_summary_result(struct process_list *plist)
+{
+       struct process_list_entry *entry;
+       int res;
+
+       res = 0;
+
+       TAILQ_FOREACH(entry, &plist->active_list, entries) {
+               if (entry->state != PROCESS_LIST_ENTRY_STATE_FINISHED) {
+                       return (-1);
+               }
+
+               if (!WIFEXITED(entry->exit_status) || WEXITSTATUS(entry->exit_status) != 0) {
+                       res = 1;
+               }
+       }
+
+       return (res);
+}
+
+/*
+ *  0 = All processes finished sucesfully
+ *  1 = Some process finished and failed
+ * -1 = Not all processed finished and none of finished failed
+ */
+int
+process_list_get_summary_result_short(struct process_list *plist)
+{
+       struct process_list_entry *entry;
+       int res;
+
+       res = 0;
+
+       TAILQ_FOREACH(entry, &plist->active_list, entries) {
+               if (entry->state == PROCESS_LIST_ENTRY_STATE_FINISHED) {
+                       if (!WIFEXITED(entry->exit_status) || WEXITSTATUS(entry->exit_status) != 0) {
+                               return (1);
+                       }
+               } else {
+                       res = -1;
+               }
+       }
+
+       return (res);
+}
+
+static void
+process_list_move_entry_to_kill_list(struct process_list *plist, struct process_list_entry *entry)
+{
+
+       TAILQ_REMOVE(&plist->active_list, entry, entries);
+       TAILQ_INSERT_TAIL(&plist->to_kill_list, entry, entries);
+}
+
+void
+process_list_move_active_entries_to_kill_list(struct process_list *plist)
+{
+       struct process_list_entry *entry;
+       struct process_list_entry *entry_next;
+
+       entry = TAILQ_FIRST(&plist->active_list);
+
+       while (entry != NULL) {
+               entry_next = TAILQ_NEXT(entry, entries);
+
+               if (entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED ||
+                   entry->state == PROCESS_LIST_ENTRY_STATE_FINISHED) {
+                       TAILQ_REMOVE(&plist->active_list, entry, entries);
+                       process_list_entry_free(entry);
+                       plist->allocated_list_entries--;
+               } else {
+                       process_list_move_entry_to_kill_list(plist, entry);
+               }
+
+               entry = entry_next;
+       }
+}
+
+static int
+process_list_process_kill_list_entry(struct process_list *plist, struct process_list_entry *entry)
+{
+       int sig_to_send;
+       enum process_list_entry_state new_state;
+       int res;
+
+       sig_to_send = 0;
+       new_state = PROCESS_LIST_ENTRY_STATE_INITIALIZED;
+
+       switch (entry->state) {
+       case PROCESS_LIST_ENTRY_STATE_INITIALIZED:
+               /*
+                * This shouldn't happen. If it does, process_list_move_active_entries_to_kill_list
+                * doesn't work as expected or there is some kind of memory corruption.
+                */
+               assert(entry->state != PROCESS_LIST_ENTRY_STATE_INITIALIZED);
+               break;
+       case PROCESS_LIST_ENTRY_STATE_FINISHED:
+               /*
+                * This shouldn't happen. If it does, process_list_waitpid
+                * doesn't work as expected or there is some kind of memory corruption.
+                */
+               assert(entry->state != PROCESS_LIST_ENTRY_STATE_FINISHED);
+               break;
+       case PROCESS_LIST_ENTRY_STATE_RUNNING:
+               sig_to_send = SIGTERM;
+               new_state = PROCESS_LIST_ENTRY_STATE_SIGTERM_SENT;
+               break;
+       case PROCESS_LIST_ENTRY_STATE_SIGTERM_SENT:
+               sig_to_send = SIGKILL;
+               new_state = PROCESS_LIST_ENTRY_STATE_SIGKILL_SENT;
+               break;
+       case PROCESS_LIST_ENTRY_STATE_SIGKILL_SENT:
+               sig_to_send = SIGKILL;
+               new_state = PROCESS_LIST_ENTRY_STATE_SIGKILL_SENT;
+               break;
+       }
+
+       res = 0;
+
+       if (kill(entry->pid, sig_to_send) == -1) {
+               if (errno == EPERM || errno == EINVAL) {
+                       res = -1;
+               }
+       }
+
+       entry->state = new_state;
+
+       return (res);
+}
+
+int
+process_list_process_kill_list(struct process_list *plist)
+{
+       struct process_list_entry *entry;
+
+       if (process_list_waitpid(plist) != 0) {
+               return (-1);
+       }
+
+       TAILQ_FOREACH(entry, &plist->to_kill_list, entries) {
+               if (process_list_process_kill_list_entry(plist, entry) != 0) {
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+size_t
+process_list_get_kill_list_items(struct process_list *plist)
+{
+       struct process_list_entry *entry;
+       size_t res;
+
+       res = 0;
+
+       TAILQ_FOREACH(entry, &plist->to_kill_list, entries) {
+               res++;
+       }
+
+       return (res);
+}
+
+int
+process_list_killall(struct process_list *plist, uint32_t timeout)
+{
+       uint32_t action_timeout;
+       int i;
+
+       process_list_move_active_entries_to_kill_list(plist);
+
+       action_timeout = timeout / 10;
+       if (action_timeout < 1) {
+               action_timeout = 1;
+       }
+
+       for (i = 0; i < 10; i++) {
+               /*
+                * Make sure all process got signal (quick phase)
+                */
+               if (process_list_process_kill_list(plist) != 0) {
+                       return (-1);
+               }
+       }
+
+       for (i = 0; i < 10 && process_list_get_kill_list_items(plist) > 0; i++) {
+               if (process_list_process_kill_list(plist) != 0) {
+                       return (-1);
+               }
+
+               poll(NULL, 0, action_timeout);
+       }
+
+       if (process_list_get_kill_list_items(plist) > 0) {
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/process-list.h b/qdevices/process-list.h
new file mode 100644 (file)
index 0000000..ea2873f
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PROCESS_LIST_H_
+#define _PROCESS_LIST_H_
+
+#include <signal.h>
+#include <sys/queue.h>
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum process_list_entry_state {
+       PROCESS_LIST_ENTRY_STATE_INITIALIZED,
+       PROCESS_LIST_ENTRY_STATE_RUNNING,
+       PROCESS_LIST_ENTRY_STATE_FINISHED,
+       PROCESS_LIST_ENTRY_STATE_SIGTERM_SENT,
+       PROCESS_LIST_ENTRY_STATE_SIGKILL_SENT,
+};
+
+enum process_list_notify_reason {
+       PROCESS_LIST_NOTIFY_REASON_EXECUTED,
+       PROCESS_LIST_NOTIFY_REASON_FINISHED,
+};
+
+struct process_list_entry {
+       char *name;
+       enum process_list_entry_state state;
+       char **exec_argv;
+       size_t exec_argc;
+       pid_t pid;
+       int exit_status;
+
+       TAILQ_ENTRY(process_list_entry) entries;
+};
+
+typedef void (*process_list_notify_fn_t) (enum process_list_notify_reason reason,
+    const struct process_list_entry *entry, void *user_data);
+
+struct process_list {
+       int use_execvp;
+       size_t max_list_entries;
+       size_t allocated_list_entries;
+       process_list_notify_fn_t notify_fn;
+       void *notify_fn_user_data;
+
+       TAILQ_HEAD(, process_list_entry) active_list;
+       TAILQ_HEAD(, process_list_entry) to_kill_list;
+};
+
+
+extern void                             process_list_init(struct process_list *plist,
+    size_t max_list_entries, int use_execvp, process_list_notify_fn_t notify_fn,
+    void *notify_fn_user_data);
+
+extern struct process_list_entry       *process_list_add(struct process_list *plist,
+    const char *name, const char *command);
+
+extern void                             process_list_free(struct process_list *plist);
+
+extern int                              process_list_exec_initialized(struct process_list *plist);
+
+extern int                              process_list_waitpid(struct process_list *plist);
+
+extern size_t                           process_list_get_no_running(struct process_list *plist);
+
+extern int                              process_list_get_summary_result(struct process_list *plist);
+
+extern int                              process_list_get_summary_result_short(
+    struct process_list *plist);
+
+extern void                             process_list_move_active_entries_to_kill_list(
+    struct process_list *plist);
+
+extern int                              process_list_process_kill_list(struct process_list *plist);
+
+extern size_t                           process_list_get_kill_list_items(struct process_list *plist);
+
+extern int                              process_list_killall(struct process_list *plist,
+    uint32_t timeout);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PROCESS_LIST_H_ */
diff --git a/qdevices/qdevice-advanced-settings.c b/qdevices/qdevice-advanced-settings.c
new file mode 100644 (file)
index 0000000..e3d7a9a
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "dynar.h"
+#include "dynar-getopt-lex.h"
+#include "dynar-str.h"
+#include "qdevice-config.h"
+#include "qnet-config.h"
+#include "qdevice-advanced-settings.h"
+#include "utils.h"
+
+int
+qdevice_advanced_settings_init(struct qdevice_advanced_settings *settings)
+{
+
+       memset(settings, 0, sizeof(*settings));
+       if ((settings->lock_file = strdup(QDEVICE_DEFAULT_LOCK_FILE)) == NULL) {
+               return (-1);
+       }
+       if ((settings->local_socket_file = strdup(QDEVICE_DEFAULT_LOCAL_SOCKET_FILE)) == NULL) {
+               return (-1);
+       }
+       settings->local_socket_backlog = QDEVICE_DEFAULT_LOCAL_SOCKET_BACKLOG;
+       settings->max_cs_try_again = QDEVICE_DEFAULT_MAX_CS_TRY_AGAIN;
+       if ((settings->votequorum_device_name = strdup(QDEVICE_DEFAULT_VOTEQUORUM_DEVICE_NAME)) == NULL) {
+               return (-1);
+       }
+       settings->ipc_max_clients = QDEVICE_DEFAULT_IPC_MAX_CLIENTS;
+       settings->ipc_max_receive_size = QDEVICE_DEFAULT_IPC_MAX_RECEIVE_SIZE;
+       settings->ipc_max_send_size = QDEVICE_DEFAULT_IPC_MAX_SEND_SIZE;
+
+       settings->heuristics_ipc_max_send_buffers = QDEVICE_DEFAULT_HEURISTICS_IPC_MAX_SEND_BUFFERS;
+       settings->heuristics_ipc_max_send_receive_size = QDEVICE_DEFAULT_HEURISTICS_IPC_MAX_SEND_RECEIVE_SIZE;
+
+       settings->heuristics_min_timeout = QDEVICE_DEFAULT_HEURISTICS_MIN_TIMEOUT;
+       settings->heuristics_max_timeout = QDEVICE_DEFAULT_HEURISTICS_MAX_TIMEOUT;
+       settings->heuristics_min_interval = QDEVICE_DEFAULT_HEURISTICS_MIN_INTERVAL;
+       settings->heuristics_max_interval = QDEVICE_DEFAULT_HEURISTICS_MAX_INTERVAL;
+
+       settings->heuristics_max_execs = QDEVICE_DEFAULT_HEURISTICS_MAX_EXECS;
+
+       settings->heuristics_use_execvp = QDEVICE_DEFAULT_HEURISTICS_USE_EXECVP;
+       settings->heuristics_max_processes = QDEVICE_DEFAULT_HEURISTICS_MAX_PROCESSES;
+       settings->heuristics_kill_list_interval = QDEVICE_DEFAULT_HEURISTICS_KILL_LIST_INTERVAL;
+
+       if ((settings->net_nss_db_dir = strdup(QDEVICE_NET_DEFAULT_NSS_DB_DIR)) == NULL) {
+               return (-1);
+       }
+       settings->net_initial_msg_receive_size = QDEVICE_NET_DEFAULT_INITIAL_MSG_RECEIVE_SIZE;
+       settings->net_initial_msg_send_size = QDEVICE_NET_DEFAULT_INITIAL_MSG_SEND_SIZE;
+       settings->net_min_msg_send_size = QDEVICE_NET_DEFAULT_MIN_MSG_SEND_SIZE;
+       settings->net_max_msg_receive_size = QDEVICE_NET_DEFAULT_MAX_MSG_RECEIVE_SIZE;
+       settings->net_max_send_buffers = QDEVICE_NET_DEFAULT_MAX_SEND_BUFFERS;
+       if ((settings->net_nss_qnetd_cn = strdup(QDEVICE_NET_DEFAULT_NSS_QNETD_CN)) == NULL) {
+               return (-1);
+       }
+       if ((settings->net_nss_client_cert_nickname =
+           strdup(QDEVICE_NET_DEFAULT_NSS_CLIENT_CERT_NICKNAME)) == NULL) {
+               return (-1);
+       }
+       settings->net_heartbeat_interval_min = QDEVICE_NET_DEFAULT_HEARTBEAT_INTERVAL_MIN;
+       settings->net_heartbeat_interval_max = QDEVICE_NET_DEFAULT_HEARTBEAT_INTERVAL_MAX;
+       settings->net_min_connect_timeout = QDEVICE_NET_DEFAULT_MIN_CONNECT_TIMEOUT;
+       settings->net_max_connect_timeout = QDEVICE_NET_DEFAULT_MAX_CONNECT_TIMEOUT;
+       settings->net_test_algorithm_enabled = QDEVICE_NET_DEFAULT_TEST_ALGORITHM_ENABLED;
+
+       settings->master_wins = QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_MODEL;
+
+       return (0);
+}
+
+void
+qdevice_advanced_settings_destroy(struct qdevice_advanced_settings *settings)
+{
+
+       free(settings->local_socket_file);
+       free(settings->lock_file);
+       free(settings->votequorum_device_name);
+       free(settings->net_nss_db_dir);
+       free(settings->net_nss_qnetd_cn);
+       free(settings->net_nss_client_cert_nickname);
+}
+
+/*
+ * 0 - No error
+ * -1 - Unknown option
+ * -2 - Incorrect value
+ */
+int
+qdevice_advanced_settings_set(struct qdevice_advanced_settings *settings,
+    const char *option, const char *value)
+{
+       long long int tmpll;
+       char *ep;
+
+       if (strcasecmp(option, "lock_file") == 0) {
+               free(settings->lock_file);
+
+               if ((settings->lock_file = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "local_socket_file") == 0) {
+               free(settings->local_socket_file);
+
+               if ((settings->local_socket_file = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "local_socket_backlog") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_LOCAL_SOCKET_BACKLOG || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->local_socket_backlog = (int)tmpll;
+       } else if (strcasecmp(option, "max_cs_try_again") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_MAX_CS_TRY_AGAIN || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->max_cs_try_again = (int)tmpll;
+       } else if (strcasecmp(option, "votequorum_device_name") == 0) {
+               free(settings->votequorum_device_name);
+
+               if ((settings->votequorum_device_name = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "ipc_max_clients") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_IPC_MAX_CLIENTS || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->ipc_max_clients = (size_t)tmpll;
+       } else if (strcasecmp(option, "ipc_max_receive_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_IPC_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->ipc_max_receive_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "ipc_max_send_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_IPC_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->ipc_max_send_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_ipc_max_send_buffers") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_HEURISTICS_IPC_MAX_SEND_BUFFERS || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heuristics_ipc_max_send_buffers = (size_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_ipc_max_send_receive_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_HEURISTICS_IPC_MAX_SEND_RECEIVE_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heuristics_ipc_max_send_receive_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_min_timeout") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_HEURISTICS_TIMEOUT || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heuristics_min_timeout = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_max_timeout") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_HEURISTICS_TIMEOUT || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heuristics_max_timeout = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_min_interval") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_HEURISTICS_INTERVAL || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heuristics_min_interval = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_max_interval") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_HEURISTICS_INTERVAL || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heuristics_max_interval = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_max_execs") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_HEURISTICS_MAX_EXECS || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heuristics_max_execs = (size_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_use_execvp") == 0) {
+               if ((tmpll = utils_parse_bool_str(value)) == -1) {
+                       return (-2);
+               }
+
+               settings->heuristics_use_execvp = (uint8_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_max_processes") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_HEURISTICS_MAX_PROCESSES || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heuristics_max_processes = (size_t)tmpll;
+       } else if (strcasecmp(option, "heuristics_kill_list_interval") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_MIN_HEURISTICS_KILL_LIST_INTERVAL || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heuristics_kill_list_interval = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "net_nss_db_dir") == 0) {
+               free(settings->net_nss_db_dir);
+
+               if ((settings->net_nss_db_dir = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "net_initial_msg_receive_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->net_initial_msg_receive_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "net_initial_msg_send_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->net_initial_msg_send_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "net_min_msg_send_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->net_min_msg_send_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "net_max_msg_receive_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->net_max_msg_receive_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "net_max_send_buffers") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_NET_MIN_MAX_SEND_BUFFERS || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->net_max_send_buffers = (size_t)tmpll;
+       } else if (strcasecmp(option, "net_nss_qnetd_cn") == 0) {
+               free(settings->net_nss_qnetd_cn);
+
+               if ((settings->net_nss_qnetd_cn = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "net_nss_client_cert_nickname") == 0) {
+               free(settings->net_nss_client_cert_nickname);
+
+               if ((settings->net_nss_client_cert_nickname = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "net_heartbeat_interval_min") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_NET_MIN_HEARTBEAT_INTERVAL || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->net_heartbeat_interval_min = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "net_heartbeat_interval_max") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_NET_MIN_HEARTBEAT_INTERVAL || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->net_heartbeat_interval_max = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "net_min_connect_timeout") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_NET_MIN_CONNECT_TIMEOUT || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->net_min_connect_timeout = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "net_max_connect_timeout") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QDEVICE_NET_MIN_CONNECT_TIMEOUT || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->net_max_connect_timeout = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "net_test_algorithm_enabled") == 0) {
+               if ((tmpll = utils_parse_bool_str(value)) == -1) {
+                       return (-2);
+               }
+
+               settings->net_test_algorithm_enabled = (uint8_t)tmpll;
+       } else if (strcasecmp(option, "master_wins") == 0) {
+               tmpll = utils_parse_bool_str(value);
+
+               if (tmpll == 0) {
+                       settings->master_wins = QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_OFF;
+               } else if (tmpll == 1) {
+                       settings->master_wins = QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_ON;
+               } else if (strcasecmp(value, "model") == 0) {
+                       settings->master_wins = QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_MODEL;
+               } else {
+                       return (-2);
+               }
+       } else {
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-advanced-settings.h b/qdevices/qdevice-advanced-settings.h
new file mode 100644 (file)
index 0000000..b8bf604
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_ADVANCED_SETTINGS_H_
+#define _QDEVICE_ADVANCED_SETTINGS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum qdevice_advanced_settings_master_wins {
+       QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_MODEL,
+       QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_ON,
+       QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_OFF,
+};
+
+struct qdevice_advanced_settings {
+       char *lock_file;
+       char *local_socket_file;
+       int local_socket_backlog;
+       int max_cs_try_again;
+       char *votequorum_device_name;
+       size_t ipc_max_clients;
+       size_t ipc_max_send_size;
+       size_t ipc_max_receive_size;
+       enum qdevice_advanced_settings_master_wins master_wins;
+       size_t heuristics_ipc_max_send_buffers;
+       size_t heuristics_ipc_max_send_receive_size;
+       uint32_t heuristics_min_timeout;
+       uint32_t heuristics_max_timeout;
+       uint32_t heuristics_min_interval;
+       uint32_t heuristics_max_interval;
+       size_t heuristics_max_execs;
+       int heuristics_use_execvp;
+       size_t heuristics_max_processes;
+       uint32_t heuristics_kill_list_interval;
+
+       /*
+        * Related to model NET
+        */
+       char *net_nss_db_dir;
+       size_t net_initial_msg_receive_size;
+       size_t net_initial_msg_send_size;
+       size_t net_min_msg_send_size;
+       size_t net_max_msg_receive_size;
+       size_t net_max_send_buffers;
+       char *net_nss_qnetd_cn;
+       char *net_nss_client_cert_nickname;
+       uint32_t net_heartbeat_interval_min;
+       uint32_t net_heartbeat_interval_max;
+       uint32_t net_min_connect_timeout;
+       uint32_t net_max_connect_timeout;
+       uint8_t net_test_algorithm_enabled;
+};
+
+extern int             qdevice_advanced_settings_init(struct qdevice_advanced_settings *settings);
+
+extern int             qdevice_advanced_settings_set(struct qdevice_advanced_settings *settings,
+    const char *option, const char *value);
+
+extern void            qdevice_advanced_settings_destroy(struct qdevice_advanced_settings *settings);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_ADVANCED_SETTINGS_H_ */
diff --git a/qdevices/qdevice-cmap.c b/qdevices/qdevice-cmap.c
new file mode 100644 (file)
index 0000000..e9a99c3
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <netdb.h>
+
+#include "qdevice-config.h"
+#include "qdevice-cmap.h"
+#include "qdevice-log.h"
+#include "qdevice-log-debug.h"
+#include "qdevice-model.h"
+#include "utils.h"
+
+static uint32_t
+qdevice_cmap_autogenerate_node_id(const char *addr, int clear_node_high_byte)
+{
+       struct addrinfo *ainfo;
+       struct addrinfo ahints;
+       int ret, i;
+
+       memset(&ahints, 0, sizeof(ahints));
+       ahints.ai_socktype = SOCK_DGRAM;
+       ahints.ai_protocol = IPPROTO_UDP;
+       /*
+        * Hardcoded AF_INET because autogenerated nodeid is valid only for ipv4
+        */
+       ahints.ai_family = AF_INET;
+
+       ret = getaddrinfo(addr, NULL, &ahints, &ainfo);
+       if (ret != 0)
+               return (0);
+
+       if (ainfo->ai_family != AF_INET) {
+
+               freeaddrinfo(ainfo);
+
+               return (0);
+       }
+
+       memcpy(&i, &((struct sockaddr_in *)ainfo->ai_addr)->sin_addr, sizeof(struct in_addr));
+       freeaddrinfo(ainfo);
+
+       ret = htonl(i);
+
+       if (clear_node_high_byte) {
+               ret &= 0x7FFFFFFF;
+       }
+
+       return (ret);
+}
+
+int
+qdevice_cmap_get_nodelist(cmap_handle_t cmap_handle, struct node_list *list)
+{
+       cs_error_t cs_err;
+       cmap_iter_handle_t iter_handle;
+       char key_name[CMAP_KEYNAME_MAXLEN + 1];
+       char tmp_key[CMAP_KEYNAME_MAXLEN + 1];
+       int res;
+       int ret_value;
+       unsigned int node_pos;
+       uint32_t node_id;
+       uint32_t data_center_id;
+       char *tmp_str;
+       char *addr0_str;
+       int clear_node_high_byte;
+
+       ret_value = 0;
+
+       node_list_init(list);
+
+       cs_err = cmap_iter_init(cmap_handle, "nodelist.node.", &iter_handle);
+       if (cs_err != CS_OK) {
+               return (-1);
+       }
+
+       while ((cs_err = cmap_iter_next(cmap_handle, iter_handle, key_name, NULL, NULL)) == CS_OK) {
+               res = sscanf(key_name, "nodelist.node.%u.%s", &node_pos, tmp_key);
+               if (res != 2) {
+                       continue;
+               }
+
+               if (strcmp(tmp_key, "ring0_addr") != 0) {
+                       continue;
+               }
+
+               snprintf(tmp_key, CMAP_KEYNAME_MAXLEN, "nodelist.node.%u.nodeid", node_pos);
+               cs_err = cmap_get_uint32(cmap_handle, tmp_key, &node_id);
+
+               if (cs_err == CS_ERR_NOT_EXIST) {
+                       /*
+                        * Nodeid doesn't exists -> autogenerate node id
+                        */
+                       clear_node_high_byte = 0;
+
+                       if (cmap_get_string(cmap_handle, "totem.clear_node_high_bit",
+                           &tmp_str) == CS_OK) {
+                               if (strcmp (tmp_str, "yes") == 0) {
+                                       clear_node_high_byte = 1;
+                               }
+
+                               free(tmp_str);
+                       }
+
+                       if (cmap_get_string(cmap_handle, key_name, &addr0_str) != CS_OK) {
+                               return (-1);
+                       }
+
+                       node_id = qdevice_cmap_autogenerate_node_id(addr0_str,
+                           clear_node_high_byte);
+
+                       free(addr0_str);
+               } else if (cs_err != CS_OK) {
+                       ret_value = -1;
+
+                       goto iter_finalize;
+               }
+
+               snprintf(tmp_key, CMAP_KEYNAME_MAXLEN, "nodelist.node.%u.datacenterid", node_pos);
+               if (cmap_get_uint32(cmap_handle, tmp_key, &data_center_id) != CS_OK) {
+                       data_center_id = 0;
+               }
+
+               if (node_list_add(list, node_id, data_center_id, TLV_NODE_STATE_NOT_SET) == NULL) {
+                       ret_value = -1;
+
+                       goto iter_finalize;
+               }
+       }
+
+iter_finalize:
+       cmap_iter_finalize(cmap_handle, iter_handle);
+
+       if (ret_value != 0) {
+               node_list_free(list);
+       }
+
+       return (ret_value);
+}
+
+int
+qdevice_cmap_get_config_version(cmap_handle_t cmap_handle, uint64_t *config_version)
+{
+       int res;
+
+       if (cmap_get_uint64(cmap_handle, "totem.config_version", config_version) == CS_OK) {
+               res = 0;
+       } else {
+               *config_version = 0;
+               res = -1;
+       }
+
+       return (res);
+}
+
+int
+qdevice_cmap_store_config_node_list(struct qdevice_instance *instance)
+{
+       int res;
+
+       node_list_free(&instance->config_node_list);
+
+       if (qdevice_cmap_get_nodelist(instance->cmap_handle, &instance->config_node_list) != 0) {
+               qdevice_log(LOG_ERR, "Can't get configuration node list.");
+
+               return (-1);
+       }
+
+       res = qdevice_cmap_get_config_version(instance->cmap_handle, &instance->config_node_list_version);
+       instance->config_node_list_version_set = (res == 0);
+
+       return (0);
+}
+
+void
+qdevice_cmap_init(struct qdevice_instance *instance)
+{
+       cs_error_t res;
+       int no_retries;
+
+       no_retries = 0;
+
+       while ((res = cmap_initialize(&instance->cmap_handle)) == CS_ERR_TRY_AGAIN &&
+           no_retries++ < instance->advanced_settings->max_cs_try_again) {
+               (void)poll(NULL, 0, 1000);
+       }
+
+       if (res != CS_OK) {
+               errx(1, "Failed to initialize the cmap API. Error %s", cs_strerror(res));
+       }
+
+       if ((res = cmap_context_set(instance->cmap_handle, (void *)instance)) != CS_OK) {
+               errx(1, "Can't set cmap context. Error %s", cs_strerror(res));
+       }
+
+       cmap_fd_get(instance->cmap_handle, &instance->cmap_poll_fd);
+}
+
+static void
+qdevice_cmap_node_list_event(struct qdevice_instance *instance)
+{
+       struct node_list nlist;
+       int config_version_set;
+       uint64_t config_version;
+
+       qdevice_log(LOG_DEBUG, "Node list configuration possibly changed");
+       if (qdevice_cmap_get_nodelist(instance->cmap_handle, &nlist) != 0) {
+               qdevice_log(LOG_ERR, "Can't get configuration node list.");
+
+               if (qdevice_model_get_config_node_list_failed(instance) != 0) {
+                       qdevice_log(LOG_DEBUG, "qdevice_model_get_config_node_list_failed returned error -> exit");
+                       exit(2);
+               }
+
+               return ;
+       }
+
+       config_version_set = (qdevice_cmap_get_config_version(instance->cmap_handle,
+           &config_version) == 0);
+
+       if (node_list_eq(&instance->config_node_list, &nlist)) {
+               return ;
+       }
+
+       qdevice_log(LOG_DEBUG, "Node list changed");
+       if (config_version_set) {
+               qdevice_log(LOG_DEBUG, "  config_version = "UTILS_PRI_CONFIG_VERSION, config_version);
+       }
+       qdevice_log_debug_dump_node_list(&nlist);
+
+       if (qdevice_model_config_node_list_changed(instance, &nlist,
+           config_version_set, config_version) != 0) {
+               qdevice_log(LOG_DEBUG, "qdevice_model_config_node_list_changed returned error -> exit");
+               exit(2);
+       }
+
+       node_list_free(&instance->config_node_list);
+       if (node_list_clone(&instance->config_node_list, &nlist) != 0) {
+               qdevice_log(LOG_ERR, "Can't allocate instance->config_node_list clone");
+
+               node_list_free(&nlist);
+
+               if (qdevice_model_get_config_node_list_failed(instance) != 0) {
+                       qdevice_log(LOG_DEBUG, "qdevice_model_get_config_node_list_failed returned error -> exit");
+                       exit(2);
+               }
+
+               return ;
+       }
+
+       instance->config_node_list_version_set = config_version_set;
+
+       if (config_version_set) {
+               instance->config_node_list_version = config_version;
+       }
+}
+
+static void
+qdevice_cmap_logging_event(struct qdevice_instance *instance)
+{
+
+       qdevice_log(LOG_DEBUG, "Logging configuration possibly changed");
+       qdevice_log_configure(instance);
+}
+
+static void
+qdevice_cmap_heuristics_event(struct qdevice_instance *instance)
+{
+
+       qdevice_log(LOG_DEBUG, "Heuristics configuration possibly changed");
+       if (qdevice_instance_configure_from_cmap_heuristics(instance) != 0) {
+               qdevice_log(LOG_DEBUG, "qdevice_instance_configure_from_cmap_heuristics returned error -> exit");
+               exit(2);
+       }
+}
+
+static void
+qdevice_cmap_reload_cb(cmap_handle_t cmap_handle, cmap_track_handle_t cmap_track_handle,
+    int32_t event, const char *key_name,
+    struct cmap_notify_value new_value, struct cmap_notify_value old_value,
+    void *user_data)
+{
+       cs_error_t cs_res;
+       uint8_t reload;
+       struct qdevice_instance *instance;
+       const char *node_list_prefix_str;
+       const char *logging_prefix_str;
+       const char *heuristics_prefix_str;
+       struct qdevice_cmap_change_events events;
+
+       memset(&events, 0, sizeof(events));
+       node_list_prefix_str = "nodelist.";
+       logging_prefix_str = "logging.";
+       heuristics_prefix_str = "quorum.device.heuristics.";
+
+       if (cmap_context_get(cmap_handle, (const void **)&instance) != CS_OK) {
+               qdevice_log(LOG_ERR, "Fatal error. Can't get cmap context");
+               exit(1);
+       }
+
+       /*
+        * Wait for full reload
+        */
+       if (strcmp(key_name, "config.totemconfig_reload_in_progress") == 0 &&
+           new_value.type == CMAP_VALUETYPE_UINT8 && new_value.len == sizeof(reload)) {
+               reload = 1;
+               if (memcmp(new_value.data, &reload, sizeof(reload)) == 0) {
+                       /*
+                        * Ignore nodelist changes
+                        */
+                       instance->cmap_reload_in_progress = 1;
+                       return ;
+               } else {
+                       instance->cmap_reload_in_progress = 0;
+                       events.node_list = 1;
+                       events.logging = 1;
+                       events.heuristics = 1;
+               }
+       }
+
+       if (instance->cmap_reload_in_progress) {
+               return ;
+       }
+
+       if (((cs_res = cmap_get_uint8(cmap_handle, "config.totemconfig_reload_in_progress",
+           &reload)) == CS_OK) && reload == 1) {
+               return ;
+       }
+
+       if (strncmp(key_name, node_list_prefix_str, strlen(node_list_prefix_str)) == 0) {
+               events.node_list = 1;
+       }
+
+       if (strncmp(key_name, logging_prefix_str, strlen(logging_prefix_str)) == 0) {
+               events.logging = 1;
+       }
+
+       if (strncmp(key_name, heuristics_prefix_str, strlen(heuristics_prefix_str)) == 0) {
+               events.heuristics = 1;
+       }
+
+       if (events.logging) {
+               qdevice_cmap_logging_event(instance);
+       }
+
+       if (events.node_list) {
+               qdevice_cmap_node_list_event(instance);
+       }
+
+       if (events.heuristics) {
+               qdevice_cmap_heuristics_event(instance);
+       }
+
+       /*
+        * Inform model about change
+        */
+       if (qdevice_model_cmap_changed(instance, &events) != 0) {
+               qdevice_log(LOG_DEBUG, "qdevice_model_cmap_changed returned error -> exit");
+               exit(2);
+       }
+}
+
+int
+qdevice_cmap_add_track(struct qdevice_instance *instance)
+{
+       cs_error_t res;
+
+       res = cmap_track_add(instance->cmap_handle, "config.totemconfig_reload_in_progress",
+           CMAP_TRACK_ADD | CMAP_TRACK_MODIFY, qdevice_cmap_reload_cb,
+           NULL, &instance->cmap_reload_track_handle);
+
+       if (res != CS_OK) {
+               qdevice_log(LOG_ERR, "Can't initialize cmap totemconfig_reload_in_progress tracking");
+               return (-1);
+       }
+
+       res = cmap_track_add(instance->cmap_handle, "nodelist.",
+           CMAP_TRACK_ADD | CMAP_TRACK_DELETE | CMAP_TRACK_MODIFY | CMAP_TRACK_PREFIX,
+           qdevice_cmap_reload_cb,
+           NULL, &instance->cmap_nodelist_track_handle);
+
+       if (res != CS_OK) {
+               qdevice_log(LOG_ERR, "Can't initialize cmap nodelist tracking");
+               return (-1);
+       }
+
+       res = cmap_track_add(instance->cmap_handle, "logging.",
+           CMAP_TRACK_ADD | CMAP_TRACK_DELETE | CMAP_TRACK_MODIFY | CMAP_TRACK_PREFIX,
+           qdevice_cmap_reload_cb,
+           NULL, &instance->cmap_logging_track_handle);
+
+       if (res != CS_OK) {
+               qdevice_log(LOG_ERR, "Can't initialize logging tracking");
+               return (-1);
+       }
+
+       res = cmap_track_add(instance->cmap_handle, "quorum.device.heuristics.",
+           CMAP_TRACK_ADD | CMAP_TRACK_DELETE | CMAP_TRACK_MODIFY | CMAP_TRACK_PREFIX,
+           qdevice_cmap_reload_cb,
+           NULL, &instance->cmap_heuristics_track_handle);
+
+       if (res != CS_OK) {
+               qdevice_log(LOG_ERR, "Can't initialize logging tracking");
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_cmap_del_track(struct qdevice_instance *instance)
+{
+       cs_error_t res;
+
+       res = cmap_track_delete(instance->cmap_handle, instance->cmap_reload_track_handle);
+       if (res != CS_OK) {
+               qdevice_log(LOG_WARNING, "Can't delete cmap totemconfig_reload_in_progress tracking");
+       }
+
+       res = cmap_track_delete(instance->cmap_handle, instance->cmap_nodelist_track_handle);
+       if (res != CS_OK) {
+               qdevice_log(LOG_WARNING, "Can't delete cmap nodelist tracking");
+       }
+
+       res = cmap_track_delete(instance->cmap_handle, instance->cmap_logging_track_handle);
+       if (res != CS_OK) {
+               qdevice_log(LOG_WARNING, "Can't delete cmap logging tracking");
+       }
+
+       res = cmap_track_delete(instance->cmap_handle, instance->cmap_heuristics_track_handle);
+       if (res != CS_OK) {
+               qdevice_log(LOG_WARNING, "Can't delete cmap heuristics tracking");
+       }
+
+       return (0);
+}
+
+void
+qdevice_cmap_destroy(struct qdevice_instance *instance)
+{
+       cs_error_t res;
+
+       res = cmap_finalize(instance->cmap_handle);
+
+       if (res != CS_OK) {
+               qdevice_log(LOG_WARNING, "Can't finalize cmap. Error %s", cs_strerror(res));
+       }
+}
+
+int
+qdevice_cmap_dispatch(struct qdevice_instance *instance)
+{
+       cs_error_t res;
+
+       /*
+        * dispatch can block if corosync is during sync phase
+        */
+       if (instance->sync_in_progress) {
+               return (0);
+       }
+
+       res = cmap_dispatch(instance->cmap_handle, CS_DISPATCH_ALL);
+
+       if (res != CS_OK && res != CS_ERR_TRY_AGAIN) {
+               qdevice_log(LOG_ERR, "Can't dispatch cmap messages");
+
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-cmap.h b/qdevices/qdevice-cmap.h
new file mode 100644 (file)
index 0000000..b6d8fcb
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_CMAP_H_
+#define _QDEVICE_CMAP_H_
+
+#include <corosync/cmap.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include "node-list.h"
+#include "qdevice-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qdevice_cmap_change_events {
+       unsigned int logging    : 1;
+       unsigned int node_list  : 1;
+       unsigned int heuristics : 1;
+};
+
+extern int             qdevice_cmap_get_nodelist(cmap_handle_t cmap_handle,
+    struct node_list *list);
+
+extern int             qdevice_cmap_get_config_version(cmap_handle_t cmap_handle,
+    uint64_t *config_version);
+
+extern void            qdevice_cmap_init(struct qdevice_instance *instance);
+
+extern int             qdevice_cmap_add_track(struct qdevice_instance *instance);
+
+extern int             qdevice_cmap_del_track(struct qdevice_instance *instance);
+
+extern void            qdevice_cmap_destroy(struct qdevice_instance *instance);
+
+extern int             qdevice_cmap_dispatch(struct qdevice_instance *instance);
+
+extern int             qdevice_cmap_store_config_node_list(struct qdevice_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_CMAP_H_ */
diff --git a/qdevices/qdevice-config.h b/qdevices/qdevice-config.h
new file mode 100644 (file)
index 0000000..d47e5e5
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_CONFIG_H_
+#define _QDEVICE_CONFIG_H_
+
+#include <config.h>
+
+#include <qb/qbdefs.h>
+#include <qb/qblog.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <string.h>
+#include "qdevice-heuristics-mode.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * There are "hardcoded" defines for qdevice. It's not so good
+ * idea to change them as long as you are not 100% sure what you are doing. Also
+ * most of them can be changed in CLI via advanced_settings (-S).
+ */
+#define QDEVICE_DEFAULT_LOCK_FILE              LOCALSTATEDIR"/run/corosync-qdevice/corosync-qdevice.pid"
+#define QDEVICE_DEFAULT_LOCAL_SOCKET_FILE      LOCALSTATEDIR"/run/corosync-qdevice/corosync-qdevice.sock"
+#define QDEVICE_DEFAULT_LOCAL_SOCKET_BACKLOG   10
+#define QDEVICE_MIN_LOCAL_SOCKET_BACKLOG       1
+
+#define QDEVICE_DEFAULT_MAX_CS_TRY_AGAIN       10
+#define QDEVICE_MIN_MAX_CS_TRY_AGAIN           1
+
+#define QDEVICE_PROGRAM_NAME                   "corosync-qdevice"
+#define QDEVICE_LOG_SUBSYS                     "QDEVICE"
+#define QDEVICE_LOG_DEFAULT_TO_STDERR          1
+#define QDEVICE_LOG_DEFAULT_TO_SYSLOG          1
+#define QDEVICE_LOG_DEFAULT_TO_LOGFILE         0
+#define QDEVICE_LOG_DEFAULT_SYSLOG_FACILITY    LOG_DAEMON
+#define QDEVICE_LOG_DEFAULT_SYSLOG_PRIORITY    LOG_INFO
+#define QDEVICE_LOG_DEFAULT_DEBUG              0
+#define QDEVICE_LOG_DEFAULT_FILELINE           0
+#define QDEVICE_LOG_DEFAULT_TIMESTAMP          0
+#define QDEVICE_LOG_DEFAULT_FUNCTION_NAME      0
+
+#define QDEVICE_DEFAULT_VOTEQUORUM_DEVICE_NAME "Qdevice"
+
+#define QDEVICE_DEFAULT_IPC_MAX_CLIENTS                10
+#define QDEVICE_MIN_IPC_MAX_CLIENTS            0
+#define QDEVICE_DEFAULT_IPC_MAX_RECEIVE_SIZE   (4*1024)
+#define QDEVICE_DEFAULT_IPC_MAX_SEND_SIZE      (64*1024)
+#define QDEVICE_MIN_IPC_RECEIVE_SEND_SIZE      1024
+
+#define QDEVICE_DEFAULT_HEURISTICS_IPC_MAX_SEND_BUFFERS                128
+#define QDEVICE_MIN_HEURISTICS_IPC_MAX_SEND_BUFFERS            10
+#define QDEVICE_DEFAULT_HEURISTICS_IPC_MAX_SEND_RECEIVE_SIZE   (4 * 1024)
+#define QDEVICE_MIN_HEURISTICS_IPC_MAX_SEND_RECEIVE_SIZE       1024
+
+#define QDEVICE_DEFAULT_HEURISTICS_MIN_TIMEOUT                 (1 * 1000)
+#define QDEVICE_DEFAULT_HEURISTICS_MAX_TIMEOUT                 (2 * 60 * 1000)
+#define QDEVICE_MIN_HEURISTICS_TIMEOUT                         250
+#define QDEVICE_DEFAULT_HEURISTICS_MIN_INTERVAL                        QDEVICE_DEFAULT_HEURISTICS_MIN_TIMEOUT
+#define QDEVICE_DEFAULT_HEURISTICS_MAX_INTERVAL                        (60 * 60 * 1000)
+#define QDEVICE_MIN_HEURISTICS_INTERVAL                                QDEVICE_MIN_HEURISTICS_TIMEOUT
+
+#define QDEVICE_DEFAULT_HEURISTICS_MODE                                QDEVICE_HEURISTICS_MODE_DISABLED
+
+#define QDEVICE_DEFAULT_HEURISTICS_MAX_EXECS                   32
+#define QDEVICE_MIN_HEURISTICS_MAX_EXECS                       1
+
+#define QDEVICE_DEFAULT_HEURISTICS_USE_EXECVP                  0
+
+#define QDEVICE_DEFAULT_HEURISTICS_MAX_PROCESSES               (QDEVICE_DEFAULT_HEURISTICS_MAX_EXECS * 5)
+#define QDEVICE_MIN_HEURISTICS_MAX_PROCESSES                   1
+
+#define QDEVICE_DEFAULT_HEURISTICS_KILL_LIST_INTERVAL          (5 * 1000)
+#define QDEVICE_MIN_HEURISTICS_KILL_LIST_INTERVAL              QDEVICE_MIN_HEURISTICS_TIMEOUT
+
+#define QDEVICE_TOOL_PROGRAM_NAME              "corosync-qdevice-tool"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_CONFIG_H_ */
diff --git a/qdevices/qdevice-heuristics-cmd-str.h b/qdevices/qdevice-heuristics-cmd-str.h
new file mode 100644 (file)
index 0000000..13d7ecc
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_CMD_STR_H_
+#define _QDEVICE_HEURISTICS_CMD_STR_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_CLEAR             "exec-list-clear"
+#define QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_ADD               "exec-list-add"
+#define QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_ADD_SPACE         \
+    QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_ADD " "
+#define QDEVICE_HEURISTICS_CMD_STR_EXEC                                "exec"
+#define QDEVICE_HEURISTICS_CMD_STR_EXEC_ADD_SPACE              QDEVICE_HEURISTICS_CMD_STR_EXEC " "
+#define QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT                 "exec-result"
+#define QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT_ADD_SPACE       \
+    QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT " "
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_CMD_STR_H_ */
diff --git a/qdevices/qdevice-heuristics-cmd.c b/qdevices/qdevice-heuristics-cmd.c
new file mode 100644 (file)
index 0000000..1306e94
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "qdevice-heuristics-exec-result.h"
+#include "qdevice-heuristics-cmd.h"
+#include "qdevice-heuristics-cmd-str.h"
+#include "qdevice-heuristics-io.h"
+#include "qdevice-log.h"
+
+static int
+qdevice_heuristics_cmd_process_exec_result(struct qdevice_heuristics_instance *instance,
+    struct dynar *data)
+{
+       uint32_t seq_number;
+       char *str;
+       enum qdevice_heuristics_exec_result exec_result;
+
+       str = dynar_data(data);
+
+       if (sscanf(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT_ADD_SPACE "%"PRIu32" %u", &seq_number,
+           &exec_result) != 2) {
+               qdevice_log(LOG_CRIT, "Can't parse exec result command (sscanf)");
+
+               return (-1);
+       }
+
+       qdevice_log(LOG_DEBUG,
+           "Received heuristics exec result command with seq_no \"%"PRIu32"\" and result \"%s\"", seq_number,
+           qdevice_heuristics_exec_result_to_str(exec_result));
+
+       if (!instance->waiting_for_result) {
+               qdevice_log(LOG_DEBUG, "Received exec result is not expected. Ignoring.");
+
+               return (0);
+       }
+
+       if (seq_number != instance->expected_reply_seq_number) {
+               qdevice_log(LOG_DEBUG, "Received heuristics exec result seq number %"PRIu32
+                   " is not expected one (expected %"PRIu32"). Ignoring.", seq_number,
+                   instance->expected_reply_seq_number);
+
+               return (0);
+       }
+
+       instance->waiting_for_result = 0;
+
+       if (qdevice_heuristics_result_notifier_notify(&instance->exec_result_notifier_list,
+           (void *)instance, seq_number, exec_result) != 0) {
+               qdevice_log(LOG_DEBUG, "qdevice_heuristics_result_notifier_notify returned non-zero result");
+               return (-1);
+       }
+
+       return (0);
+}
+
+/*
+ * 1 - Line processed
+ * 0 - No line to process - everything processed
+ * -1 - Error
+ */
+static int
+qdevice_heuristics_cmd_process_one_line(struct qdevice_heuristics_instance *instance,
+    struct dynar *data)
+{
+       char *str;
+       size_t str_len;
+       size_t nl_pos;
+       size_t zi;
+
+       str = dynar_data(data);
+       str_len = dynar_size(data);
+
+       /*
+        * Find valid line
+        */
+       for (zi = 0; zi < str_len && str[zi] != '\r' && str[zi] != '\n'; zi++) ;
+
+       if (zi >= str_len) {
+               /*
+                * Command is not yet fully readed
+                */
+               return (0);
+       }
+
+       nl_pos = zi;
+
+       str[nl_pos] = '\0';
+
+       if (strncmp(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT_ADD_SPACE,
+           strlen(QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT_ADD_SPACE)) == 0) {
+               if (qdevice_heuristics_cmd_process_exec_result(instance, data) != 0) {
+                       return (-1);
+               }
+       } else {
+               qdevice_log(LOG_CRIT,
+                   "Heuristics worker sent unknown command \"%s\"", str);
+
+                   return (-1);
+       }
+
+       /*
+        * Find place where is begining of new "valid" line
+        */
+       for (zi = nl_pos + 1; zi < str_len && (str[zi] == '\0' || str[zi] == '\n' || str[zi] == '\r'); zi++) ;
+
+       memmove(str, str + zi, str_len - zi);
+       if (dynar_set_size(data, str_len - zi) == -1) {
+               qdevice_log(LOG_CRIT,
+                   "qdevice_heuristics_cmd_process_one_line: Can't set dynar size");
+               return (-1);
+       }
+
+       return (1);
+}
+
+/*
+ * 0 - No error
+ * -1 - Error
+ */
+static int
+qdevice_heuristics_cmd_process(struct qdevice_heuristics_instance *instance)
+{
+       int res;
+
+       while ((res =
+           qdevice_heuristics_cmd_process_one_line(instance, &instance->cmd_in_buffer)) == 1) ;
+
+       return (res);
+}
+
+/*
+ * 0 - No error
+ * 1 - Error
+ */
+int
+qdevice_heuristics_cmd_read_from_pipe(struct qdevice_heuristics_instance *instance)
+{
+       int res;
+       int ret;
+
+       res = qdevice_heuristics_io_read(instance->pipe_cmd_recv, &instance->cmd_in_buffer);
+
+       ret = 0;
+
+       switch (res) {
+       case 0:
+               /*
+                * Partial read
+                */
+               break;
+       case -1:
+               qdevice_log(LOG_ERR, "Lost connection with heuristics worker");
+               ret = -1;
+               break;
+       case -2:
+               qdevice_log(LOG_ERR, "Heuristics worker sent too long cmd.");
+               ret = -1;
+               break;
+       case -3:
+               qdevice_log(LOG_ERR, "Unhandled error when reading from heuristics worker cmd fd");
+               ret = -1;
+               break;
+       case 1:
+               /*
+                * At least one cmd line received
+                */
+               ret = qdevice_heuristics_cmd_process(instance);
+               break;
+       }
+
+       return (ret);
+}
+
+/*
+ * 0 - No error
+ * 1 - Error
+ */
+int
+qdevice_heuristics_cmd_write(struct qdevice_heuristics_instance *instance)
+{
+       struct send_buffer_list_entry *send_buffer;
+       int res;
+
+       send_buffer = send_buffer_list_get_active(&instance->cmd_out_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_CRIT, "send_buffer_list_get_active in qdevice_heuristics_cmd_write returned NULL");
+
+               return (-1);
+       }
+
+       res = qdevice_heuristics_io_write(instance->pipe_cmd_send, &send_buffer->buffer,
+           &send_buffer->msg_already_sent_bytes);
+
+       if (res == 1) {
+               send_buffer_list_delete(&instance->cmd_out_buffer_list, send_buffer);
+       }
+
+       if (res == -1) {
+               qdevice_log(LOG_CRIT, "qdevice_heuristics_io_write returned -1 (write returned 0)");
+
+               return (-1);
+       }
+
+       if (res == -2) {
+               qdevice_log(LOG_CRIT, "Unhandled error in during sending message to heuristics "
+                   "worker (qdevice_heuristics_io_write returned -2)");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+qdevice_heuristics_cmd_remove_newlines(struct dynar *str)
+{
+       size_t len;
+       size_t zi;
+       char *buf;
+
+       len = dynar_size(str);
+       buf = dynar_data(str);
+
+       for (zi = 0; zi < len ; zi++) {
+               if (buf[zi] == '\n' || buf[zi] == '\r') {
+                       buf[zi] = ' ';
+               }
+       }
+
+       return (0);
+}
+
+int
+qdevice_heuristics_cmd_write_exec_list(struct qdevice_heuristics_instance *instance,
+    const struct qdevice_heuristics_exec_list *new_exec_list)
+{
+       struct send_buffer_list_entry *send_buffer;
+       struct qdevice_heuristics_exec_list_entry *entry;
+
+       send_buffer = send_buffer_list_get_new(&instance->cmd_out_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't alloc send list for cmd change exec list");
+
+               return (-1);
+       }
+
+       if (dynar_str_cpy(&send_buffer->buffer, QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_CLEAR) == -1 ||
+           dynar_str_cat(&send_buffer->buffer, "\n") == -1) {
+               qdevice_log(LOG_ERR, "Can't alloc list clear message");
+
+               send_buffer_list_discard_new(&instance->cmd_out_buffer_list, send_buffer);
+
+               return (-1);
+       }
+
+       send_buffer_list_put(&instance->cmd_out_buffer_list, send_buffer);
+
+       if (new_exec_list == NULL) {
+               return (0);
+       }
+
+       /*
+        * new_exec_list is not NULL, send it
+        */
+       TAILQ_FOREACH(entry, new_exec_list, entries) {
+               send_buffer = send_buffer_list_get_new(&instance->cmd_out_buffer_list);
+               if (send_buffer == NULL) {
+                       qdevice_log(LOG_ERR, "Can't alloc send list for cmd change exec list");
+
+                       return (-1);
+               }
+
+               if (dynar_str_cpy(&send_buffer->buffer,
+                   QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_ADD_SPACE) == -1 ||
+                   dynar_str_cat(&send_buffer->buffer, entry->name) == -1 ||
+                   dynar_str_cat(&send_buffer->buffer, " ") == -1 ||
+                   dynar_str_cat(&send_buffer->buffer, entry->command) == -1 ||
+                   qdevice_heuristics_cmd_remove_newlines(&send_buffer->buffer) == -1 ||
+                   dynar_str_cat(&send_buffer->buffer, "\n") == -1) {
+                       qdevice_log(LOG_ERR, "Can't alloc list add message");
+
+                       send_buffer_list_discard_new(&instance->cmd_out_buffer_list, send_buffer);
+
+                       return (-1);
+               }
+
+               send_buffer_list_put(&instance->cmd_out_buffer_list, send_buffer);
+       }
+
+       return (0);
+}
+
+int
+qdevice_heuristics_cmd_write_exec(struct qdevice_heuristics_instance *instance,
+    uint32_t timeout, uint32_t seq_number)
+{
+       struct send_buffer_list_entry *send_buffer;
+
+       send_buffer = send_buffer_list_get_new(&instance->cmd_out_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't alloc send list for cmd change exec list");
+
+               return (-1);
+       }
+
+       if (dynar_str_cpy(&send_buffer->buffer, QDEVICE_HEURISTICS_CMD_STR_EXEC_ADD_SPACE) == -1 ||
+           dynar_str_catf(&send_buffer->buffer, "%"PRIu32" %"PRIu32"\n", timeout, seq_number) == -1) {
+               qdevice_log(LOG_ERR, "Can't alloc exec message");
+
+               send_buffer_list_discard_new(&instance->cmd_out_buffer_list, send_buffer);
+
+               return (-1);
+       }
+
+       send_buffer_list_put(&instance->cmd_out_buffer_list, send_buffer);
+
+       return (0);
+}
diff --git a/qdevices/qdevice-heuristics-cmd.h b/qdevices/qdevice-heuristics-cmd.h
new file mode 100644 (file)
index 0000000..100b051
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_CMD_H_
+#define _QDEVICE_HEURISTICS_CMD_H_
+
+#include "qdevice-heuristics-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qdevice_heuristics_cmd_write(
+    struct qdevice_heuristics_instance *instance);
+
+extern int             qdevice_heuristics_cmd_write_exec_list(
+    struct qdevice_heuristics_instance *instance, const struct qdevice_heuristics_exec_list *new_exec_list);
+
+extern int             qdevice_heuristics_cmd_write_exec(struct qdevice_heuristics_instance *instance,
+    uint32_t timeout, uint32_t seq_number);
+
+extern int             qdevice_heuristics_cmd_read_from_pipe(
+    struct qdevice_heuristics_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_CMD_H_ */
diff --git a/qdevices/qdevice-heuristics-exec-list.c b/qdevices/qdevice-heuristics-exec-list.c
new file mode 100644 (file)
index 0000000..b752400
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "qdevice-heuristics-exec-list.h"
+
+void
+qdevice_heuristics_exec_list_init(struct qdevice_heuristics_exec_list *list)
+{
+
+       TAILQ_INIT(list);
+}
+
+struct qdevice_heuristics_exec_list_entry *
+qdevice_heuristics_exec_list_add(struct qdevice_heuristics_exec_list *list,
+    char *name, char *command)
+{
+       struct qdevice_heuristics_exec_list_entry *entry;
+
+       entry = (struct qdevice_heuristics_exec_list_entry *)malloc(sizeof(*entry));
+       if (entry == NULL) {
+               return (NULL);
+       }
+
+       memset(entry, 0, sizeof(*entry));
+
+       entry->name = strdup(name);
+       if (entry->name == NULL) {
+               free(entry);
+
+               return (NULL);
+       }
+
+       entry->command = strdup(command);
+       if (entry->command == NULL) {
+               free(entry->name);
+               free(entry);
+
+               return (NULL);
+       }
+
+       TAILQ_INSERT_TAIL(list, entry, entries);
+
+       return (entry);
+}
+
+void
+qdevice_heuristics_exec_list_free(struct qdevice_heuristics_exec_list *list)
+{
+       struct qdevice_heuristics_exec_list_entry *entry;
+       struct qdevice_heuristics_exec_list_entry *entry_next;
+
+       entry = TAILQ_FIRST(list);
+
+       while (entry != NULL) {
+               entry_next = TAILQ_NEXT(entry, entries);
+
+               free(entry->name);
+               free(entry->command);
+               free(entry);
+
+               entry = entry_next;
+       }
+
+       TAILQ_INIT(list);
+}
+
+size_t
+qdevice_heuristics_exec_list_size(const struct qdevice_heuristics_exec_list *list)
+{
+       struct qdevice_heuristics_exec_list_entry *entry;
+       size_t res;
+
+       res = 0;
+
+       TAILQ_FOREACH(entry, list, entries) {
+               res++;
+       }
+
+       return (res);
+}
+
+int
+qdevice_heuristics_exec_list_clone(struct qdevice_heuristics_exec_list *dst_list,
+    const struct qdevice_heuristics_exec_list *src_list)
+{
+       struct qdevice_heuristics_exec_list_entry *entry;
+
+       qdevice_heuristics_exec_list_init(dst_list);
+
+       TAILQ_FOREACH(entry, src_list, entries) {
+               if (qdevice_heuristics_exec_list_add(dst_list, entry->name, entry->command) == NULL) {
+                       qdevice_heuristics_exec_list_free(dst_list);
+
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+void
+qdevice_heuristics_exec_list_del(struct qdevice_heuristics_exec_list *list,
+    struct qdevice_heuristics_exec_list_entry *entry)
+{
+
+       TAILQ_REMOVE(list, entry, entries);
+
+       free(entry->name);
+       free(entry->command);
+       free(entry);
+}
+
+int
+qdevice_heuristics_exec_list_is_empty(const struct qdevice_heuristics_exec_list *list)
+{
+
+       return (TAILQ_EMPTY(list));
+}
+
+struct qdevice_heuristics_exec_list_entry *
+qdevice_heuristics_exec_list_find_name(const struct qdevice_heuristics_exec_list *list,
+    const char *name)
+{
+       struct qdevice_heuristics_exec_list_entry *entry;
+
+       TAILQ_FOREACH(entry, list, entries) {
+               if (strcmp(entry->name, name) == 0) {
+                       return (entry);
+               }
+       }
+
+       return (NULL);
+}
+
+int
+qdevice_heuristics_exec_list_eq(const struct qdevice_heuristics_exec_list *list1,
+    const struct qdevice_heuristics_exec_list *list2)
+{
+       struct qdevice_heuristics_exec_list_entry *entry1;
+       struct qdevice_heuristics_exec_list_entry *entry2;
+       struct qdevice_heuristics_exec_list tmp_list;
+       int res;
+
+       res = 1;
+
+       if (qdevice_heuristics_exec_list_clone(&tmp_list, list2) != 0) {
+               return (-1);
+       }
+
+       TAILQ_FOREACH(entry1, list1, entries) {
+               entry2 = qdevice_heuristics_exec_list_find_name(&tmp_list, entry1->name);
+               if (entry2 == NULL) {
+                       res = 0;
+                       goto return_res;
+               }
+
+               if (strcmp(entry1->command, entry2->command) != 0) {
+                       res = 0;
+                       goto return_res;
+               }
+
+               qdevice_heuristics_exec_list_del(&tmp_list, entry2);
+       }
+
+       if (!qdevice_heuristics_exec_list_is_empty(&tmp_list)) {
+               res = 0;
+               goto return_res;
+       }
+
+return_res:
+       qdevice_heuristics_exec_list_free(&tmp_list);
+
+       return (res);
+}
diff --git a/qdevices/qdevice-heuristics-exec-list.h b/qdevices/qdevice-heuristics-exec-list.h
new file mode 100644 (file)
index 0000000..b80067c
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_EXEC_LIST_H_
+#define _QDEVICE_HEURISTICS_EXEC_LIST_H_
+
+#include <sys/types.h>
+
+#include <sys/queue.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qdevice_heuristics_exec_list_entry {
+       char *name;
+       char *command;
+       TAILQ_ENTRY(qdevice_heuristics_exec_list_entry) entries;
+};
+
+TAILQ_HEAD(qdevice_heuristics_exec_list, qdevice_heuristics_exec_list_entry);
+
+extern void                                             qdevice_heuristics_exec_list_init(
+    struct qdevice_heuristics_exec_list *list);
+
+extern struct qdevice_heuristics_exec_list_entry       *qdevice_heuristics_exec_list_add(
+    struct qdevice_heuristics_exec_list *list, char *name, char *command);
+
+extern void                                             qdevice_heuristics_exec_list_free(
+    struct qdevice_heuristics_exec_list *list);
+
+extern size_t                                           qdevice_heuristics_exec_list_size(
+    const struct qdevice_heuristics_exec_list *list);
+
+extern int                                              qdevice_heuristics_exec_list_clone(
+    struct qdevice_heuristics_exec_list *dst_list,
+    const struct qdevice_heuristics_exec_list *src_list);
+
+extern void                                             qdevice_heuristics_exec_list_del(
+    struct qdevice_heuristics_exec_list *list, struct qdevice_heuristics_exec_list_entry *entry);
+
+extern int                                              qdevice_heuristics_exec_list_is_empty(
+    const struct qdevice_heuristics_exec_list *list);
+
+extern struct qdevice_heuristics_exec_list_entry       *qdevice_heuristics_exec_list_find_name(
+    const struct qdevice_heuristics_exec_list *list, const char *name);
+
+extern int                                              qdevice_heuristics_exec_list_eq(
+    const struct qdevice_heuristics_exec_list *list1,
+    const struct qdevice_heuristics_exec_list *list2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_EXEC_LIST_H_ */
diff --git a/qdevices/qdevice-heuristics-exec-result.c b/qdevices/qdevice-heuristics-exec-result.c
new file mode 100644 (file)
index 0000000..8d00ae0
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-heuristics-exec-result.h"
+
+const char *
+qdevice_heuristics_exec_result_to_str(enum qdevice_heuristics_exec_result exec_result)
+{
+
+       switch (exec_result) {
+       case QDEVICE_HEURISTICS_EXEC_RESULT_FAIL: return("Fail"); break;
+       case QDEVICE_HEURISTICS_EXEC_RESULT_PASS: return("Pass"); break;
+       case QDEVICE_HEURISTICS_EXEC_RESULT_DISABLED: return("Disabled"); break;
+       }
+
+       return ("Unknown heuristics exec result value");
+}
diff --git a/qdevices/qdevice-heuristics-exec-result.h b/qdevices/qdevice-heuristics-exec-result.h
new file mode 100644 (file)
index 0000000..12933b3
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_EXEC_RESULT_H_
+#define _QDEVICE_HEURISTICS_EXEC_RESULT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum qdevice_heuristics_exec_result {
+       /*
+        * Heuristics worker received command to exec heuristics but list is empty. This
+        * is happening when heuristics is disabled.
+        */
+       QDEVICE_HEURISTICS_EXEC_RESULT_DISABLED = 0,
+       /*
+        * All executed commands passed
+        */
+       QDEVICE_HEURISTICS_EXEC_RESULT_PASS = 1,
+       /*
+        * One (or more) commands failed or timed-out
+        */
+       QDEVICE_HEURISTICS_EXEC_RESULT_FAIL = 2,
+};
+
+extern const char *    qdevice_heuristics_exec_result_to_str(
+    enum qdevice_heuristics_exec_result exec_result);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_EXEC_RESULT_H_ */
diff --git a/qdevices/qdevice-heuristics-instance.c b/qdevices/qdevice-heuristics-instance.c
new file mode 100644 (file)
index 0000000..2cfce20
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "qdevice-heuristics-instance.h"
+#include "qdevice-heuristics-exec-list.h"
+
+int
+qdevice_heuristics_instance_init(struct qdevice_heuristics_instance *instance)
+{
+
+       memset(instance, 0, sizeof(*instance));
+
+       qdevice_heuristics_exec_list_init(&instance->exec_list);
+       qdevice_heuristics_result_notifier_list_init(&instance->exec_result_notifier_list);
+
+       return (0);
+}
+
+int
+qdevice_heuristics_instance_destroy(struct qdevice_heuristics_instance *instance)
+{
+
+       qdevice_heuristics_result_notifier_list_free(&instance->exec_result_notifier_list);
+       qdevice_heuristics_exec_list_free(&instance->exec_list);
+
+       return (0);
+}
diff --git a/qdevices/qdevice-heuristics-instance.h b/qdevices/qdevice-heuristics-instance.h
new file mode 100644 (file)
index 0000000..51e7ea1
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_INSTANCE_H_
+#define _QDEVICE_HEURISTICS_INSTANCE_H_
+
+#include "dynar.h"
+#include "send-buffer-list.h"
+#include "qdevice-heuristics-mode.h"
+#include "qdevice-heuristics-exec-list.h"
+#include "qdevice-heuristics-exec-result.h"
+#include "qdevice-heuristics-result-notifier.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qdevice_heuristics_instance {
+       int pipe_cmd_send;
+       int pipe_cmd_recv;
+       int pipe_log_recv;
+       pid_t worker_pid;
+       struct send_buffer_list cmd_out_buffer_list;
+       struct dynar log_in_buffer;
+       struct dynar cmd_in_buffer;
+
+       uint32_t timeout;
+       uint32_t sync_timeout;
+       uint32_t interval;
+
+       enum qdevice_heuristics_mode mode;
+
+       int waiting_for_result;
+       uint32_t expected_reply_seq_number;
+
+       struct qdevice_heuristics_exec_list exec_list;
+
+       struct qdevice_instance *qdevice_instance_ptr;
+
+       struct qdevice_heuristics_result_notifier_list exec_result_notifier_list;
+};
+
+extern int     qdevice_heuristics_instance_init(struct qdevice_heuristics_instance *instance);
+
+extern int     qdevice_heuristics_instance_destroy(struct qdevice_heuristics_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_INSTANCE_H_ */
diff --git a/qdevices/qdevice-heuristics-io.c b/qdevices/qdevice-heuristics-io.c
new file mode 100644 (file)
index 0000000..9485e06
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <limits.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "qdevice-heuristics-io.h"
+
+#define QDEVICE_HEURISTICS_IO_BUFFER_SIZE      256
+
+ssize_t
+qdevice_heuristics_io_blocking_write(int fd, const void *buf, size_t count)
+{
+       ssize_t bytes_written;
+       ssize_t tmp_bytes_written;
+
+       bytes_written = 0;
+
+       do {
+               tmp_bytes_written = write(fd, (const char *)buf + bytes_written,
+                   (count - bytes_written > SSIZE_MAX) ? SSIZE_MAX : count - bytes_written);
+               if (tmp_bytes_written == -1) {
+                       if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) {
+                               return (-1);
+                       }
+               } else {
+                       bytes_written += tmp_bytes_written;
+               }
+       } while ((size_t)bytes_written != count);
+
+       return (bytes_written);
+}
+
+/*
+ *  1 Full line readed (at least one \n found)
+ *  0 Partial read (no error)
+ * -1 End of connection
+ * -2 Buffer too long
+ * -3 Unhandled error
+ */
+int
+qdevice_heuristics_io_read(int fd, struct dynar *dest)
+{
+       char buf[QDEVICE_HEURISTICS_IO_BUFFER_SIZE];
+       ssize_t readed;
+       int res;
+       size_t zi;
+
+       res = 0;
+       readed = read(fd, buf, sizeof(buf));
+       if (readed > 0) {
+               if (dynar_cat(dest, buf, readed) == -1) {
+                       res = -2;
+                       goto exit_err;
+               }
+
+               for (zi = 0; zi < (size_t)readed; zi++) {
+                       if (buf[zi] == '\n') {
+                               res = 1;
+                       }
+               }
+       }
+
+       if (readed == 0) {
+               res = -1;
+       }
+
+       if (readed < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
+               res = -3;
+       }
+
+exit_err:
+       return (res);
+}
+
+/*
+ * 1 All data succesfully sent
+ *  0 Partial send (no error)
+ * -1 send returned 0,
+ * -2 Unhandled error
+ */
+int
+qdevice_heuristics_io_write(int fd, const struct dynar *msg, size_t *already_sent_bytes)
+{
+       ssize_t sent;
+       size_t to_send;
+       int res;
+
+       res = 0;
+
+       to_send = dynar_size(msg) - *already_sent_bytes;
+       if (to_send > QDEVICE_HEURISTICS_IO_BUFFER_SIZE) {
+               to_send = QDEVICE_HEURISTICS_IO_BUFFER_SIZE;
+       }
+
+       sent = write(fd, dynar_data(msg) + *already_sent_bytes,
+           to_send);
+
+       if (sent > 0) {
+               *already_sent_bytes += sent;
+
+               if (*already_sent_bytes == dynar_size(msg)) {
+                       return (1);
+               }
+       }
+
+       if (sent == 0) {
+               res = -1;
+       }
+
+       if (sent < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
+               res = -2;
+       }
+
+       return (res);
+}
diff --git a/qdevices/qdevice-heuristics-io.h b/qdevices/qdevice-heuristics-io.h
new file mode 100644 (file)
index 0000000..b85d147
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_IO_H_
+#define _QDEVICE_HEURISTICS_IO_H_
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern ssize_t          qdevice_heuristics_io_blocking_write(int fd, const void *buf,
+    size_t count);
+
+extern int             qdevice_heuristics_io_read(int fd, struct dynar *dest);
+
+extern int             qdevice_heuristics_io_write(int fd, const struct dynar *msg,
+    size_t *already_sent_bytes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_IO_H_ */
diff --git a/qdevices/qdevice-heuristics-log.c b/qdevices/qdevice-heuristics-log.c
new file mode 100644 (file)
index 0000000..319da81
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-heuristics-io.h"
+#include "qdevice-heuristics-log.h"
+#include "qdevice-log.h"
+
+/*
+ * 1 - Line logged
+ * 0 - No line to log - everything processed
+ * -1 - Error
+ */
+static int
+qdevice_heuristics_log_process_one_line(struct dynar *data)
+{
+       char *str;
+       char *log_str_start;
+       size_t str_len;
+       size_t nl_pos;
+       size_t zi;
+       int status;
+       unsigned int log_priority;
+
+       str = dynar_data(data);
+       str_len = dynar_size(data);
+       log_str_start = str;
+
+       status = 0;
+       log_priority = 0;
+       /*
+        * Find start of log message and end of line
+        */
+       for (zi = 0; zi < str_len && status != -1; zi++) {
+               switch (status) {
+               case 0:
+                       if (str[zi] >= '0' && str[zi] <= '9') {
+                               log_priority = log_priority * 10 + (str[zi] - '0');
+                       } else if (str[zi] == ' ') {
+                               status = 1;
+                       } else {
+                               qdevice_log(LOG_ERR, "Parsing of heuristics log line failed. "
+                                   "Unexpected char '%c'", str[zi]);
+                               return (-1);
+                       }
+                       break;
+               case 1:
+                       if (str[zi] != ' ') {
+                               status = 2;
+                               log_str_start = str + zi;
+                       }
+                       break;
+               case 2:
+                       if (str[zi] == '\n' || str[zi] == '\r') {
+                               str[zi] = '\0';
+                               nl_pos = zi;
+                               status = -1;
+                       }
+                       break;
+               }
+       }
+
+       if (status != -1) {
+               return (0);
+       }
+
+       /*
+        * Do actual logging
+        */
+       qb_log_from_external_source(__func__, __FILE__, "worker: %s", log_priority, __LINE__, 0, log_str_start);
+
+       /*
+        * Find place where is begining of new "valid" line
+        */
+       for (zi = nl_pos + 1; zi < str_len && (str[zi] == '\0' || str[zi] == '\n' || str[zi] == '\r'); zi++) ;
+
+       memmove(str, str + zi, str_len - zi);
+       if (dynar_set_size(data, str_len - zi) == -1) {
+               qdevice_log(LOG_ERR, "qdevice_heuristics_log_process_one_line: Can't set dynar size");
+               return (-1);
+       }
+
+       return (1);
+
+}
+
+
+/*
+ * 0 - No error
+ * -1 - Error
+ */
+static int
+qdevice_heuristics_log_process(struct qdevice_heuristics_instance *instance)
+{
+       int res;
+
+       while ((res = qdevice_heuristics_log_process_one_line(&instance->log_in_buffer)) == 1) ;
+
+       return (res);
+}
+
+/*
+ * 0 - No error
+ * 1 - Error
+ */
+int
+qdevice_heuristics_log_read_from_pipe(struct qdevice_heuristics_instance *instance)
+{
+       int res;
+       int ret;
+
+       res = qdevice_heuristics_io_read(instance->pipe_log_recv, &instance->log_in_buffer);
+
+       ret = 0;
+
+       switch (res) {
+       case 0:
+               /*
+                * Partial read
+                */
+               break;
+       case -1:
+               qdevice_log(LOG_ERR, "Lost connection with heuristics worker");
+               ret = -1;
+               break;
+       case -2:
+               qdevice_log(LOG_ERR, "Heuristics worker sent too long log. Ignoring line");
+               dynar_clean(&instance->log_in_buffer);
+               break;
+       case -3:
+               qdevice_log(LOG_ERR, "Unhandled error when reading from heuristics worker log fd");
+               ret = -1;
+               break;
+       case 1:
+               /*
+                * At least one log line received
+                */
+               ret = qdevice_heuristics_log_process(instance);
+               break;
+       }
+
+       return (ret);
+}
diff --git a/qdevices/qdevice-heuristics-log.h b/qdevices/qdevice-heuristics-log.h
new file mode 100644 (file)
index 0000000..4fa8628
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_LOG_H_
+#define _QDEVICE_HEURISTICS_LOG_H_
+
+#include "qdevice-heuristics-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qdevice_heuristics_log_read_from_pipe(
+    struct qdevice_heuristics_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_LOG_H_ */
diff --git a/qdevices/qdevice-heuristics-mode.c b/qdevices/qdevice-heuristics-mode.c
new file mode 100644 (file)
index 0000000..f7e4425
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "qdevice-heuristics-mode.h"
+
+const char*
+qdevice_heuristics_mode_to_str(enum qdevice_heuristics_mode mode)
+{
+       switch (mode) {
+       case QDEVICE_HEURISTICS_MODE_DISABLED: return ("Disabled"); break;
+       case QDEVICE_HEURISTICS_MODE_ENABLED: return ("Enabled"); break;
+       case QDEVICE_HEURISTICS_MODE_SYNC: return ("Enabled only on sync"); break;
+       }
+
+       return ("Undefined");
+}
diff --git a/qdevices/qdevice-heuristics-mode.h b/qdevices/qdevice-heuristics-mode.h
new file mode 100644 (file)
index 0000000..bf6236c
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_MODE_H_
+#define _QDEVICE_HEURISTICS_MODE_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum qdevice_heuristics_mode {
+       QDEVICE_HEURISTICS_MODE_DISABLED = 0,
+       QDEVICE_HEURISTICS_MODE_ENABLED = 1,
+       QDEVICE_HEURISTICS_MODE_SYNC = 2,
+};
+
+extern const char       *qdevice_heuristics_mode_to_str(enum qdevice_heuristics_mode mode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_MODE_H_ */
diff --git a/qdevices/qdevice-heuristics-result-notifier.c b/qdevices/qdevice-heuristics-result-notifier.c
new file mode 100644 (file)
index 0000000..a7adf1e
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "qdevice-heuristics-result-notifier.h"
+
+void
+qdevice_heuristics_result_notifier_list_init(struct qdevice_heuristics_result_notifier_list *notifier_list)
+{
+
+        TAILQ_INIT(notifier_list);
+}
+
+struct qdevice_heuristics_result_notifier_item *
+qdevice_heuristics_result_notifier_list_get(struct qdevice_heuristics_result_notifier_list *notifier_list,
+    qdevice_heuristics_result_notifier_callback callback)
+{
+       struct qdevice_heuristics_result_notifier_item *item;
+
+       TAILQ_FOREACH(item, notifier_list, entries) {
+               if (item->callback == callback) {
+                       return (item);
+               }
+       }
+
+       return (NULL);
+}
+
+struct qdevice_heuristics_result_notifier_item *
+qdevice_heuristics_result_notifier_list_add(struct qdevice_heuristics_result_notifier_list *notifier_list,
+    qdevice_heuristics_result_notifier_callback callback)
+{
+       struct qdevice_heuristics_result_notifier_item *item;
+
+       item = qdevice_heuristics_result_notifier_list_get(notifier_list, callback);
+       if (item != NULL) {
+               return (item);
+       }
+
+       item = (struct qdevice_heuristics_result_notifier_item *)malloc(sizeof(*item));
+       if (item == NULL) {
+               return (NULL);
+       }
+       memset(item, 0, sizeof(*item));
+       item->callback = callback;
+       item->active = 0;
+
+       TAILQ_INSERT_TAIL(notifier_list, item, entries);
+
+       return (item);
+}
+
+int
+qdevice_heuristics_result_notifier_list_set_active(struct qdevice_heuristics_result_notifier_list *notifier_list,
+    qdevice_heuristics_result_notifier_callback callback, int active)
+{
+       struct qdevice_heuristics_result_notifier_item *item;
+
+       item = qdevice_heuristics_result_notifier_list_get(notifier_list, callback);
+       if (item == NULL) {
+               return (-1);
+       }
+
+       item->active = active;
+
+       return (0);
+}
+
+void
+qdevice_heuristics_result_notifier_list_free(struct qdevice_heuristics_result_notifier_list *notifier_list)
+{
+       struct qdevice_heuristics_result_notifier_item *item;
+       struct qdevice_heuristics_result_notifier_item *item_next;
+
+       item = TAILQ_FIRST(notifier_list);
+       while (item != NULL) {
+               item_next = TAILQ_NEXT(item, entries);
+
+               free(item);
+               item = item_next;
+       }
+}
+
+int
+qdevice_heuristics_result_notifier_notify(struct qdevice_heuristics_result_notifier_list *notifier_list,
+    void *heuristics_instance, uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result)
+{
+       struct qdevice_heuristics_result_notifier_item *item;
+
+       TAILQ_FOREACH(item, notifier_list, entries) {
+               if (!item->active) {
+                       continue ;
+               }
+
+               if (item->callback(heuristics_instance, seq_number, exec_result) != 0) {
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-heuristics-result-notifier.h b/qdevices/qdevice-heuristics-result-notifier.h
new file mode 100644 (file)
index 0000000..d32d787
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_RESULT_NOTIFIER_H_
+#define _QDEVICE_HEURISTICS_RESULT_NOTIFIER_H_
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "qdevice-heuristics-exec-result.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int (*qdevice_heuristics_result_notifier_callback)(void *heuristics_instance, uint32_t seq_number,
+    enum qdevice_heuristics_exec_result exec_result);
+
+struct qdevice_heuristics_result_notifier_item {
+       qdevice_heuristics_result_notifier_callback callback;
+       int active;
+       TAILQ_ENTRY(qdevice_heuristics_result_notifier_item) entries;
+};
+
+TAILQ_HEAD(qdevice_heuristics_result_notifier_list, qdevice_heuristics_result_notifier_item);
+
+extern void                                             qdevice_heuristics_result_notifier_list_init(
+    struct qdevice_heuristics_result_notifier_list *notifier_list);
+
+extern struct qdevice_heuristics_result_notifier_item  *qdevice_heuristics_result_notifier_list_add(
+    struct qdevice_heuristics_result_notifier_list *notifier_list,
+    qdevice_heuristics_result_notifier_callback callback);
+
+extern struct qdevice_heuristics_result_notifier_item  *qdevice_heuristics_result_notifier_list_get(
+    struct qdevice_heuristics_result_notifier_list *notifier_list,
+    qdevice_heuristics_result_notifier_callback callback);
+
+extern int                                              qdevice_heuristics_result_notifier_list_set_active(
+    struct qdevice_heuristics_result_notifier_list *notifier_list,
+    qdevice_heuristics_result_notifier_callback callback, int active);
+
+extern void                                             qdevice_heuristics_result_notifier_list_free(
+    struct qdevice_heuristics_result_notifier_list *notifier_list);
+
+extern int                                              qdevice_heuristics_result_notifier_notify(
+    struct qdevice_heuristics_result_notifier_list *notifier_list,
+    void *heuristics_instance, uint32_t seq_number,
+    enum qdevice_heuristics_exec_result exec_result);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_RESULT_NOTIFIER_H_ */
diff --git a/qdevices/qdevice-heuristics-worker-cmd.c b/qdevices/qdevice-heuristics-worker-cmd.c
new file mode 100644 (file)
index 0000000..3745faf
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "qdevice-heuristics-io.h"
+#include "qdevice-heuristics-worker.h"
+#include "qdevice-heuristics-worker-cmd.h"
+#include "qdevice-heuristics-cmd-str.h"
+#include "qdevice-heuristics-worker-log.h"
+
+static int
+qdevice_heuristics_worker_cmd_process_exec_list_add(struct qdevice_heuristics_worker_instance *instance,
+    struct dynar *data)
+{
+       size_t zi;
+       char *exec_name;
+       char *exec_command;
+       char *str;
+
+       str = dynar_data(data);
+
+       /*
+        * Skip to first space
+        */
+       for (zi = 0; str[zi] != ' ' && str[zi] != '\0'; zi++) ;
+
+       if (str[zi] == '\0') {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't find first space");
+               return (-1);
+       }
+
+       /*
+        * Skip to the end of spaces
+        */
+       for (; str[zi] == ' '; zi++) ;
+
+       if (str[zi] == '\0') {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't find start of exec name");
+               return (-1);
+       }
+
+       /*
+        * Found exec name
+        */
+       exec_name = str + zi;
+
+       /*
+        * Skip to the next spaces
+        */
+       for (; str[zi] != ' ' && str[zi] != '\0'; zi++) ;
+
+       if (str[zi] == '\0') {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't find second space");
+               return (-1);
+       }
+
+       /*
+        * Put trailing \0 into exec_name
+        */
+       str[zi] = '\0';
+       zi++;
+
+       /*
+        * Skip to the end of next spaces
+        */
+       for (; str[zi] == ' '; zi++) ;
+
+       if (str[zi] == '\0') {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't find start of exec command");
+               return (-1);
+       }
+
+       /*
+        * Found exec_command
+        */
+       exec_command = str + zi;
+
+       qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG,
+           "qdevice_heuristics_worker_cmd_process_one_line: Received exec-list-add command "
+           "with name \"%s\" and command \"%s\"", exec_name, exec_command);
+
+       if (qdevice_heuristics_exec_list_add(&instance->exec_list, exec_name, exec_command) == NULL) {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't alloc exec list entry");
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+qdevice_heuristics_worker_cmd_process_exec(struct qdevice_heuristics_worker_instance *instance,
+    struct dynar *data)
+{
+       uint32_t timeout;
+       uint32_t seq_number;
+       char *str;
+       struct qdevice_heuristics_exec_list_entry *exec_list_entry;
+       struct process_list_entry *plist_entry;
+
+       str = dynar_data(data);
+
+       if (sscanf(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_ADD_SPACE "%"PRIu32" %"PRIu32, &timeout,
+           &seq_number) != 2) {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_cmd_process_exec: Can't parse command (sscanf)");
+               return (-1);
+       }
+
+       qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG,
+           "qdevice_heuristics_worker_cmd_process_exec: Received exec command "
+           "with seq_no \"%"PRIu32"\" and timeout \"%"PRIu32"\"", seq_number, timeout);
+
+       if (instance->exec_timeout_timer != NULL) {
+               process_list_move_active_entries_to_kill_list(&instance->main_process_list);
+
+               timer_list_delete(&instance->main_timer_list, instance->exec_timeout_timer);
+               instance->exec_timeout_timer = NULL;
+       }
+
+       instance->last_exec_seq_number = seq_number;
+
+       if (qdevice_heuristics_exec_list_is_empty(&instance->exec_list)) {
+               if (qdevice_heuristics_worker_cmd_write_exec_result(instance,
+                   instance->last_exec_seq_number,
+                   QDEVICE_HEURISTICS_EXEC_RESULT_DISABLED) != 0) {
+                       return (-1);
+               }
+       } else {
+               /*
+                * Initialize process list (from exec list)
+                */
+               TAILQ_FOREACH(exec_list_entry, &instance->exec_list, entries) {
+                       plist_entry = process_list_add(&instance->main_process_list,
+                           exec_list_entry->name, exec_list_entry->command);
+
+                       if (plist_entry == NULL) {
+                               qdevice_heuristics_worker_log_printf(instance, LOG_ERR,
+                                   "qdevice_heuristics_worker_cmd_process_exec: Can't allocate "
+                                   "process list entry");
+
+                               process_list_move_active_entries_to_kill_list(
+                                   &instance->main_process_list);
+
+                               if (qdevice_heuristics_worker_cmd_write_exec_result(instance,
+                                   instance->last_exec_seq_number,
+                                   QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) {
+                                       return (-1);
+                               }
+
+                               return (0);
+                       }
+               }
+
+               if (process_list_exec_initialized(&instance->main_process_list) != 0) {
+                       qdevice_heuristics_worker_log_printf(instance, LOG_ERR,
+                           "qdevice_heuristics_worker_cmd_process_exec: Can't execute "
+                           "process list");
+
+                       process_list_move_active_entries_to_kill_list(&instance->main_process_list);
+
+                       if (qdevice_heuristics_worker_cmd_write_exec_result(instance,
+                           instance->last_exec_seq_number,
+                           QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) {
+                               return (-1);
+                       }
+
+                       return (0);
+               }
+
+               instance->exec_timeout_timer = timer_list_add(&instance->main_timer_list,
+                   timeout, qdevice_heuristics_worker_exec_timeout_timer_callback,
+                   (void *)instance, NULL);
+               if (instance->exec_timeout_timer == NULL) {
+                       qdevice_heuristics_worker_log_printf(instance, LOG_ERR,
+                           "qdevice_heuristics_worker_cmd_process_exec: Can't add exec timeout "
+                           "timer to timer list");
+
+                       process_list_move_active_entries_to_kill_list(&instance->main_process_list);
+
+                       if (qdevice_heuristics_worker_cmd_write_exec_result(instance,
+                           instance->last_exec_seq_number,
+                           QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) {
+                               return (-1);
+                       }
+
+                       return (0);
+               }
+       }
+
+       return (0);
+}
+
+/*
+ * 1 - Line processed
+ * 0 - No line to process - everything processed
+ * -1 - Error
+ */
+static int
+qdevice_heuristics_worker_cmd_process_one_line(struct qdevice_heuristics_worker_instance *instance,
+    struct dynar *data)
+{
+       char *str;
+       size_t str_len;
+       size_t nl_pos;
+       size_t zi;
+
+       str = dynar_data(data);
+       str_len = dynar_size(data);
+
+       /*
+        * Find valid line
+        */
+       for (zi = 0; zi < str_len && str[zi] != '\r' && str[zi] != '\n'; zi++) ;
+
+       if (zi >= str_len) {
+               /*
+                * Command is not yet fully readed
+                */
+               return (0);
+       }
+
+       nl_pos = zi;
+
+       str[nl_pos] = '\0';
+
+       if (strcmp(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_CLEAR) == 0) {
+               qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG,
+                   "qdevice_heuristics_worker_cmd_process_one_line: Received exec-list-clear command");
+
+               qdevice_heuristics_exec_list_free(&instance->exec_list);
+       } else if (strncmp(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_ADD_SPACE,
+           strlen(QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_ADD)) == 0) {
+               if (qdevice_heuristics_worker_cmd_process_exec_list_add(instance, data) != 0) {
+                       return (-1);
+               }
+       } else if (strncmp(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_ADD_SPACE,
+           strlen(QDEVICE_HEURISTICS_CMD_STR_EXEC_ADD_SPACE)) == 0) {
+               if (qdevice_heuristics_worker_cmd_process_exec(instance, data) != 0) {
+                       return (-1);
+               }
+       } else {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_cmd_process_one_line: Unknown command \"%s\" "
+                   "received from main qdevice process", str);
+
+                   return (-1);
+       }
+
+       /*
+        * Find place where is begining of new "valid" line
+        */
+       for (zi = nl_pos + 1; zi < str_len && (str[zi] == '\0' || str[zi] == '\n' || str[zi] == '\r'); zi++) ;
+
+       memmove(str, str + zi, str_len - zi);
+       if (dynar_set_size(data, str_len - zi) == -1) {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_cmd_process_one_line: Can't set dynar size");
+               return (-1);
+       }
+
+       return (1);
+}
+
+/*
+ * 0 - No error
+ * -1 - Error
+ */
+static int
+qdevice_heuristics_worker_cmd_process(struct qdevice_heuristics_worker_instance *instance)
+{
+       int res;
+
+       while ((res =
+           qdevice_heuristics_worker_cmd_process_one_line(instance, &instance->cmd_in_buffer)) == 1) ;
+
+       return (res);
+}
+
+/*
+ * 0 - No error
+ * 1 - Error
+ */
+int
+qdevice_heuristics_worker_cmd_read_from_pipe(struct qdevice_heuristics_worker_instance *instance)
+{
+       int res;
+       int ret;
+
+       res = qdevice_heuristics_io_read(QDEVICE_HEURISTICS_WORKER_CMD_IN_FD, &instance->cmd_in_buffer);
+
+       ret = 0;
+
+       switch (res) {
+       case 0:
+               /*
+                * Partial read
+                */
+               break;
+       case -1:
+               qdevice_heuristics_worker_log_printf(instance, LOG_ERR,
+                   "Lost connection with main qdevice process");
+               ret = -1;
+               break;
+       case -2:
+               qdevice_heuristics_worker_log_printf(instance, LOG_ERR,
+                   "Heuristics sent too long command line");
+               ret = -1;
+               break;
+       case -3:
+               qdevice_heuristics_worker_log_printf(instance, LOG_ERR,
+                   "Unhandled error when reading from heuristics command in fd");
+               ret = -1;
+               break;
+       case 1:
+               /*
+                * At least one log line received
+                */
+               ret = qdevice_heuristics_worker_cmd_process(instance);
+               break;
+       }
+
+       return (ret);
+}
+
+int
+qdevice_heuristics_worker_cmd_write_exec_result(struct qdevice_heuristics_worker_instance *instance,
+    uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result)
+{
+       if (dynar_str_cpy(&instance->cmd_out_buffer,
+           QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT_ADD_SPACE) != -1 &&
+           dynar_str_catf(&instance->cmd_out_buffer, "%"PRIu32" %u\n", seq_number,
+           (int)exec_result) != -1) {
+               (void)qdevice_heuristics_io_blocking_write(QDEVICE_HEURISTICS_WORKER_CMD_OUT_FD,
+                   dynar_data(&instance->cmd_out_buffer), dynar_size(&instance->cmd_out_buffer));
+       } else {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "Can't alloc memory for exec result");
+
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-heuristics-worker-cmd.h b/qdevices/qdevice-heuristics-worker-cmd.h
new file mode 100644 (file)
index 0000000..0e05bd8
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_WORKER_CMD_H_
+#define _QDEVICE_HEURISTICS_WORKER_CMD_H_
+
+#include "qdevice-heuristics-worker-instance.h"
+#include "qdevice-heuristics-exec-result.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qdevice_heuristics_worker_cmd_read_from_pipe(
+    struct qdevice_heuristics_worker_instance *instance);
+
+extern int             qdevice_heuristics_worker_cmd_write_exec_result(
+    struct qdevice_heuristics_worker_instance *instance, uint32_t seq_number,
+    enum qdevice_heuristics_exec_result exec_result);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_WORKER_CMD_H_ */
diff --git a/qdevices/qdevice-heuristics-worker-instance.h b/qdevices/qdevice-heuristics-worker-instance.h
new file mode 100644 (file)
index 0000000..7949264
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_WORKER_INSTANCE_H_
+#define _QDEVICE_HEURISTICS_WORKER_INSTANCE_H_
+
+#include "dynar.h"
+
+#include "qdevice-heuristics-exec-list.h"
+#include "process-list.h"
+#include "timer-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qdevice_heuristics_worker_instance {
+       struct dynar cmd_in_buffer;
+       struct dynar cmd_out_buffer;
+       struct dynar log_out_buffer;
+
+       struct qdevice_heuristics_exec_list exec_list;
+       struct process_list main_process_list;
+       struct timer_list main_timer_list;
+
+       struct timer_list_entry *kill_list_timer;
+       struct timer_list_entry *exec_timeout_timer;
+
+       uint32_t last_exec_seq_number;
+
+       int schedule_exit;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_WORKER_INSTANCE_H_ */
diff --git a/qdevices/qdevice-heuristics-worker-log.c b/qdevices/qdevice-heuristics-worker-log.c
new file mode 100644 (file)
index 0000000..997ae97
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "qdevice-heuristics-io.h"
+#include "qdevice-heuristics-worker.h"
+#include "qdevice-heuristics-worker-log.h"
+
+static int
+qdevice_heuristics_worker_log_remove_newlines(struct dynar *str)
+{
+       size_t len;
+       size_t zi;
+       char *buf;
+
+       len = dynar_size(str);
+       buf = dynar_data(str);
+
+       for (zi = 0; zi < len ; zi++) {
+               if (buf[zi] == '\n' || buf[zi] == '\r') {
+                       buf[zi] = ' ';
+               }
+       }
+
+       return (0);
+}
+
+void
+qdevice_heuristics_worker_log_printf(struct qdevice_heuristics_worker_instance *instance,
+    int priority, const char *format, ...)
+{
+       va_list ap;
+       va_list ap_copy;
+
+       va_start(ap, format);
+
+       if (dynar_str_cpy(&instance->log_out_buffer, "") != -1 &&
+           dynar_str_catf(&instance->log_out_buffer, "%u ", priority) != -1 &&
+           dynar_str_vcatf(&instance->log_out_buffer, format, ap) != -1 &&
+           qdevice_heuristics_worker_log_remove_newlines(&instance->log_out_buffer) != -1 &&
+           dynar_str_cat(&instance->log_out_buffer, "\n") != -1) {
+               /*
+                * It was possible to log everything
+                */
+               (void)qdevice_heuristics_io_blocking_write(QDEVICE_HEURISTICS_WORKER_LOG_OUT_FD,
+                   dynar_data(&instance->log_out_buffer), dynar_size(&instance->log_out_buffer));
+       } else {
+               /*
+                * As a fallback try to log to syslog
+                */
+               va_copy(ap_copy, ap);
+               openlog("qdevice_heuristics_worker", LOG_PID, LOG_DAEMON);
+               syslog(LOG_ERR, "Log entry sent to syslog instead of parent process");
+               vsyslog(priority, format, ap_copy);
+               closelog();
+               va_end(ap_copy);
+       }
+
+       va_end(ap);
+}
diff --git a/qdevices/qdevice-heuristics-worker-log.h b/qdevices/qdevice-heuristics-worker-log.h
new file mode 100644 (file)
index 0000000..ab76b54
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_WORKER_LOG_H_
+#define _QDEVICE_HEURISTICS_WORKER_LOG_H_
+
+#include <syslog.h>
+
+#include "qdevice-heuristics-worker-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void            qdevice_heuristics_worker_log_printf(
+    struct qdevice_heuristics_worker_instance *instance,
+    int priority, const char *format, ...) __attribute__((__format__(__printf__, 3, 0)));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_WORKER_LOG_H_ */
diff --git a/qdevices/qdevice-heuristics-worker.c b/qdevices/qdevice-heuristics-worker.c
new file mode 100644 (file)
index 0000000..15f6943
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <limits.h>
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dynar-str.h"
+#include "qdevice-config.h"
+#include "qdevice-heuristics-io.h"
+#include "qdevice-heuristics-worker.h"
+#include "qdevice-heuristics-worker-instance.h"
+#include "qdevice-heuristics-worker-log.h"
+#include "qdevice-heuristics-worker-cmd.h"
+
+/*
+ * Declarations
+ */
+static int             qdevice_heuristics_worker_kill_list_timer_callback(void *data1,
+    void *data2);
+
+static void            qdevice_heuristics_worker_process_list_notify(
+    enum process_list_notify_reason reason, const struct process_list_entry *entry,
+    void *user_data);
+
+static void            qdevice_heuristics_worker_signal_handlers_register(void);
+
+
+/*
+ * Definitions
+ */
+static void
+qdevice_heuristics_worker_process_list_notify(enum process_list_notify_reason reason,
+    const struct process_list_entry *entry, void *user_data)
+{
+       struct qdevice_heuristics_worker_instance *instance;
+
+       instance = (struct qdevice_heuristics_worker_instance *)user_data;
+
+       switch (reason) {
+       case PROCESS_LIST_NOTIFY_REASON_EXECUTED:
+               qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG,
+                   "process %s executed", entry->name);
+               break;
+       case PROCESS_LIST_NOTIFY_REASON_FINISHED:
+               if (!WIFEXITED(entry->exit_status) || WEXITSTATUS(entry->exit_status) != 0) {
+                       if (WIFEXITED(entry->exit_status)) {
+                               qdevice_heuristics_worker_log_printf(instance, LOG_WARNING,
+                                   "process %s finished with status %d", entry->name,
+                                   WEXITSTATUS(entry->exit_status));
+                       } else if (WIFSIGNALED(entry->exit_status)) {
+                               qdevice_heuristics_worker_log_printf(instance, LOG_WARNING,
+                                   "process %s killed by signal %d", entry->name,
+                                   WTERMSIG(entry->exit_status));
+                       } else {
+                               qdevice_heuristics_worker_log_printf(instance, LOG_WARNING,
+                                   "process %s finished with non zero status", entry->name);
+                       }
+               } else {
+                       qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG,
+                           "process %s sucesfully finished", entry->name);
+               }
+               break;
+       }
+}
+
+static void
+qdevice_heuristics_worker_signal_handlers_register(void)
+{
+       struct sigaction act;
+
+       act.sa_handler = SIG_DFL;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGCHLD, &act, NULL);
+
+       act.sa_handler = SIG_IGN;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGPIPE, &act, NULL);
+
+       act.sa_handler = SIG_IGN;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGINT, &act, NULL);
+}
+
+static int
+qdevice_heuristics_worker_kill_list_timer_callback(void *data1, void *data2)
+{
+       struct qdevice_heuristics_worker_instance *instance;
+       size_t kill_list_size;
+
+       instance = (struct qdevice_heuristics_worker_instance *)data1;
+
+       if (process_list_process_kill_list(&instance->main_process_list) != 0) {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_kill_list_timer_callback: process kill list failed. "
+                   "Shutting down worker");
+
+               instance->schedule_exit = 1;
+               return (0);
+       }
+
+       kill_list_size = process_list_get_kill_list_items(&instance->main_process_list);
+
+       if (kill_list_size > 0) {
+               qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG,
+                   "Still waiting for %zu processes exit", kill_list_size);
+       }
+
+       /*
+        * Schedule this timer again
+        */
+       return (-1);
+}
+
+int
+qdevice_heuristics_worker_exec_timeout_timer_callback(void *data1, void *data2)
+{
+       struct qdevice_heuristics_worker_instance *instance;
+
+       instance = (struct qdevice_heuristics_worker_instance *)data1;
+
+       qdevice_heuristics_worker_log_printf(instance, LOG_WARNING,
+           "Not all heuristics execs finished on time");
+
+       process_list_move_active_entries_to_kill_list(&instance->main_process_list);
+
+       instance->exec_timeout_timer = NULL;
+
+       if (qdevice_heuristics_worker_cmd_write_exec_result(instance, instance->last_exec_seq_number,
+           QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) {
+               instance->schedule_exit = 1;
+
+               return (0);
+       }
+
+       return (0);
+}
+
+static int
+qdevice_heuristics_worker_poll(struct qdevice_heuristics_worker_instance *instance)
+{
+       int poll_res;
+       struct pollfd poll_input_fd;
+       uint32_t timeout;
+       int plist_summary;
+
+       /*
+        * Poll command input
+        */
+       poll_input_fd.fd = QDEVICE_HEURISTICS_WORKER_CMD_IN_FD;
+       poll_input_fd.events = POLLIN;
+       poll_input_fd.revents = 0;
+
+       timeout = timer_list_time_to_expire_ms(&instance->main_timer_list);
+       if (timeout > QDEVICE_MIN_HEURISTICS_TIMEOUT) {
+               timeout = QDEVICE_MIN_HEURISTICS_TIMEOUT;
+       }
+
+       if ((poll_res = poll(&poll_input_fd, 1, timeout)) >= 0) {
+               if (poll_input_fd.revents & POLLIN) {
+                       /*
+                        * POLLIN
+                        */
+                       if (qdevice_heuristics_worker_cmd_read_from_pipe(instance) != 0) {
+                               return (-1);
+                       }
+               }
+
+               if (poll_input_fd.revents & POLLOUT) {
+                       /*
+                        * Pollout shouldn't happen (critical error)
+                        */
+                       qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                           "qdevice_heuristics_worker_poll: POLLOUT set. Shutting down worker");
+
+                       return (-1);
+               }
+
+               if (poll_input_fd.revents & (POLLERR|POLLHUP|POLLNVAL) &&
+                   !(poll_input_fd.revents & (POLLIN|POLLOUT))) {
+                       /*
+                        * Qdevice closed pipe
+                        */
+
+                       return (-1);
+               }
+       }
+
+       if (process_list_waitpid(&instance->main_process_list) != 0) {
+               qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                   "qdevice_heuristics_worker_poll: Waitpid failed. Shutting down worker");
+
+               return (-1);
+       }
+
+       if (instance->exec_timeout_timer != NULL) {
+               plist_summary = process_list_get_summary_result_short(&instance->main_process_list);
+
+               switch (plist_summary) {
+               case -1:
+                       /*
+                        * Processes not finished -> continue
+                        */
+                       break;
+               case 0:
+                       /*
+                        * All processes finished sucesfully
+                        */
+                       if (qdevice_heuristics_worker_cmd_write_exec_result(instance,
+                           instance->last_exec_seq_number, QDEVICE_HEURISTICS_EXEC_RESULT_PASS) != 0) {
+                               return (-1);
+                       }
+
+                       process_list_move_active_entries_to_kill_list(&instance->main_process_list);
+
+                       timer_list_delete(&instance->main_timer_list, instance->exec_timeout_timer);
+                       instance->exec_timeout_timer = NULL;
+
+                       break;
+               case 1:
+                       /*
+                        * Some processes failed
+                        */
+                       if (qdevice_heuristics_worker_cmd_write_exec_result(instance,
+                           instance->last_exec_seq_number, QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) {
+                               return (-1);
+                       }
+
+                       process_list_move_active_entries_to_kill_list(&instance->main_process_list);
+
+                       timer_list_delete(&instance->main_timer_list, instance->exec_timeout_timer);
+                       instance->exec_timeout_timer = NULL;
+                       break;
+               default:
+                       qdevice_heuristics_worker_log_printf(instance, LOG_CRIT,
+                           "qdevice_heuristics_worker_poll: Unhandled "
+                           "process_list_get_summary_result. Shutting down worker");
+
+                       return (-1);
+                       break;
+               }
+       }
+
+       timer_list_expire(&instance->main_timer_list);
+
+       if (instance->schedule_exit) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+void
+qdevice_heuristics_worker_start(size_t ipc_max_send_receive_size, int use_execvp,
+    size_t max_processes, uint32_t kill_list_interval)
+{
+       struct qdevice_heuristics_worker_instance instance;
+
+       memset(&instance, 0, sizeof(instance));
+
+       instance.schedule_exit = 0;
+
+       dynar_init(&instance.cmd_in_buffer, ipc_max_send_receive_size);
+       dynar_init(&instance.cmd_out_buffer, ipc_max_send_receive_size);
+       dynar_init(&instance.log_out_buffer, ipc_max_send_receive_size);
+
+       process_list_init(&instance.main_process_list, max_processes, use_execvp,
+           qdevice_heuristics_worker_process_list_notify, (void *)&instance);
+
+       timer_list_init(&instance.main_timer_list);
+       instance.kill_list_timer = timer_list_add(&instance.main_timer_list,
+           kill_list_interval, qdevice_heuristics_worker_kill_list_timer_callback,
+           (void *)&instance, NULL);
+
+       if (instance.kill_list_timer == NULL) {
+               qdevice_heuristics_worker_log_printf(&instance, LOG_CRIT,
+                   "Can't create kill list timer");
+               return ;
+       }
+
+       instance.exec_timeout_timer = NULL;
+
+       qdevice_heuristics_exec_list_init(&instance.exec_list);
+
+       qdevice_heuristics_worker_signal_handlers_register();
+
+       qdevice_heuristics_worker_log_printf(&instance, LOG_DEBUG, "Heuristic worker initialized");
+
+       while (qdevice_heuristics_worker_poll(&instance) == 0) {
+       }
+
+       qdevice_heuristics_worker_log_printf(&instance, LOG_DEBUG, "Heuristic worker shutdown "
+           "requested");
+
+       qdevice_heuristics_exec_list_free(&instance.exec_list);
+
+       timer_list_free(&instance.main_timer_list);
+
+       qdevice_heuristics_worker_log_printf(&instance, LOG_DEBUG,
+           "Waiting for all processes to exit");
+
+       if (process_list_killall(&instance.main_process_list, kill_list_interval) != 0) {
+               qdevice_heuristics_worker_log_printf(&instance, LOG_WARNING,
+                   "Not all process exited");
+       }
+
+       process_list_free(&instance.main_process_list);
+
+       dynar_destroy(&instance.cmd_in_buffer);
+       dynar_destroy(&instance.cmd_out_buffer);
+       dynar_destroy(&instance.log_out_buffer);
+}
diff --git a/qdevices/qdevice-heuristics-worker.h b/qdevices/qdevice-heuristics-worker.h
new file mode 100644 (file)
index 0000000..100bb97
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_WORKER_H_
+#define _QDEVICE_HEURISTICS_WORKER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define QDEVICE_HEURISTICS_WORKER_CMD_IN_FD            0
+#define QDEVICE_HEURISTICS_WORKER_CMD_OUT_FD           1
+#define QDEVICE_HEURISTICS_WORKER_LOG_OUT_FD           2
+
+void           qdevice_heuristics_worker_start(size_t ipc_max_send_receive_size,
+    int use_execvp, size_t max_processes, uint32_t kill_list_interval);
+
+int            qdevice_heuristics_worker_exec_timeout_timer_callback(void *data1, void *data2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_WORKER_H_ */
diff --git a/qdevices/qdevice-heuristics.c b/qdevices/qdevice-heuristics.c
new file mode 100644 (file)
index 0000000..6960329
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "qdevice-log.h"
+#include "qdevice-heuristics.h"
+#include "qdevice-heuristics-cmd.h"
+#include "qdevice-heuristics-worker.h"
+#include "qdevice-heuristics-io.h"
+#include "qdevice-votequorum.h"
+#include "utils.h"
+
+#define QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS       5
+
+void
+qdevice_heuristics_init(struct qdevice_heuristics_instance *instance,
+    struct qdevice_advanced_settings *advanced_settings)
+{
+       int pipe_cmd_in[2], pipe_cmd_out[2], pipe_log_out[2];
+       pid_t pid;
+
+       if (pipe(pipe_cmd_in) != 0) {
+               err(1, "Can't create command input pipe");
+       }
+
+       if (pipe(pipe_cmd_out) != 0) {
+               err(1, "Can't create command output pipe");
+       }
+
+       if (pipe(pipe_log_out) != 0) {
+               err(1, "Can't create logging output pipe");
+       }
+
+       pid = fork();
+       if (pid == -1) {
+               err(1, "Can't create child process");
+       } else if (pid == 0) {
+               /*
+                * Child
+                */
+               (void)setsid();
+               if (dup2(pipe_cmd_in[0], 0) == -1) {
+                       err(1, "Can't dup2 command input pipe");
+               }
+               close(pipe_cmd_in[1]);
+               close(pipe_cmd_in[0]);
+               if (utils_fd_set_non_blocking(0) == -1) {
+                       err(1, "Can't set non blocking flag on command input pipe");
+               }
+
+               if (dup2(pipe_cmd_out[1], 1) == -1) {
+                       err(1, "Can't dup2 command output pipe");
+               }
+               close(pipe_cmd_out[0]);
+               close(pipe_cmd_out[1]);
+
+               if (dup2(pipe_log_out[1], 2) == -1) {
+                       err(1, "Can't dup2 logging output pipe");
+               }
+               close(pipe_log_out[0]);
+               close(pipe_log_out[1]);
+
+               qdevice_heuristics_worker_start(advanced_settings->heuristics_ipc_max_send_receive_size,
+                   advanced_settings->heuristics_use_execvp, advanced_settings->heuristics_max_processes,
+                   advanced_settings->heuristics_kill_list_interval);
+
+               qdevice_advanced_settings_destroy(advanced_settings);
+
+               exit(0);
+       } else {
+               close(pipe_cmd_in[0]);
+               close(pipe_cmd_out[1]);
+               close(pipe_log_out[1]);
+
+               qdevice_heuristics_instance_init(instance);
+
+               instance->pipe_cmd_send = pipe_cmd_in[1];
+               if (utils_fd_set_non_blocking(instance->pipe_cmd_send) == -1) {
+                       err(1, "Can't set non blocking flag on command input pipe");
+               }
+               instance->pipe_cmd_recv = pipe_cmd_out[0];
+               if (utils_fd_set_non_blocking(instance->pipe_cmd_recv) == -1) {
+                       err(1, "Can't set non blocking flag on command output pipe");
+               }
+               instance->pipe_log_recv = pipe_log_out[0];
+               if (utils_fd_set_non_blocking(instance->pipe_cmd_recv) == -1) {
+                       err(1, "Can't set non blocking flag on logging output pipe");
+               }
+               instance->worker_pid = pid;
+
+               send_buffer_list_init(&instance->cmd_out_buffer_list,
+                   advanced_settings->heuristics_ipc_max_send_buffers,
+                   advanced_settings->heuristics_ipc_max_send_receive_size);
+               dynar_init(&instance->log_in_buffer,
+                   advanced_settings->heuristics_ipc_max_send_receive_size);
+               dynar_init(&instance->cmd_in_buffer,
+                   advanced_settings->heuristics_ipc_max_send_receive_size);
+       }
+}
+
+void
+qdevice_heuristics_destroy(struct qdevice_heuristics_instance *instance)
+{
+       int status;
+
+       /*
+        * Close of pipe_cmd_send result is correct and almost instant exit of worker
+        */
+       close(instance->pipe_cmd_send);
+
+       qdevice_log(LOG_DEBUG, "Waiting for heuristics worker to finish");
+       if (waitpid(instance->worker_pid, &status, 0) == -1) {
+               qdevice_log_err(LOG_ERR, "Heuristics worker waitpid failed");
+       } else {
+               /*
+                * Log what left in worker log buffer. Errors can be ignored
+                */
+               (void)qdevice_heuristics_log_read_from_pipe(instance);
+       }
+
+       close(instance->pipe_cmd_recv);
+       close(instance->pipe_log_recv);
+
+       dynar_destroy(&instance->log_in_buffer);
+       dynar_destroy(&instance->cmd_in_buffer);
+       send_buffer_list_free(&instance->cmd_out_buffer_list);
+
+       qdevice_heuristics_instance_destroy(instance);
+}
+
+int
+qdevice_heuristics_exec(struct qdevice_heuristics_instance *instance, int sync_in_progress)
+{
+       uint32_t timeout;
+
+       instance->expected_reply_seq_number++;
+       instance->waiting_for_result = 1;
+
+       if (sync_in_progress) {
+               timeout = instance->sync_timeout;
+       } else {
+               timeout = instance->timeout;
+       }
+
+       return (qdevice_heuristics_cmd_write_exec(instance, timeout,
+           instance->expected_reply_seq_number));
+}
+
+int
+qdevice_heuristics_waiting_for_result(const struct qdevice_heuristics_instance *instance)
+{
+
+       return (instance->waiting_for_result);
+}
+
+int
+qdevice_heuristics_change_exec_list(struct qdevice_heuristics_instance *instance,
+    const struct qdevice_heuristics_exec_list *new_exec_list, int sync_in_progress)
+{
+
+       if (qdevice_heuristics_cmd_write_exec_list(instance, new_exec_list) != 0) {
+               return (-1);
+       }
+
+       qdevice_heuristics_exec_list_free(&instance->exec_list);
+
+       if (new_exec_list != NULL) {
+               if (qdevice_heuristics_exec_list_clone(&instance->exec_list, new_exec_list) != 0) {
+                       qdevice_log(LOG_ERR, "Can't clone exec list");
+
+                       return (-1);
+               }
+       }
+
+       if (qdevice_heuristics_waiting_for_result(instance)) {
+               if (qdevice_heuristics_exec(instance, sync_in_progress) != 0) {
+                       qdevice_log(LOG_ERR, "Can't execute heuristics");
+
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+
+int
+qdevice_heuristics_wait_for_initial_exec_result(struct qdevice_heuristics_instance *instance)
+{
+       struct pollfd pfds[QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS];
+       int no_pfds;
+       int poll_res;
+       int timeout;
+       int i;
+       int case_processed;
+       int res;
+
+       while (!instance->qdevice_instance_ptr->vq_node_list_initial_heuristics_finished) {
+               no_pfds = 0;
+
+               assert(no_pfds < QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS);
+               pfds[no_pfds].fd = instance->pipe_log_recv;
+               pfds[no_pfds].events = POLLIN;
+               pfds[no_pfds].revents = 0;
+               no_pfds++;
+
+               assert(no_pfds < QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS);
+               pfds[no_pfds].fd = instance->pipe_cmd_recv;
+               pfds[no_pfds].events = POLLIN;
+               pfds[no_pfds].revents = 0;
+               no_pfds++;
+
+               assert(no_pfds < QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS);
+               pfds[no_pfds].fd = instance->qdevice_instance_ptr->votequorum_poll_fd;
+               pfds[no_pfds].events = POLLIN;
+               pfds[no_pfds].revents = 0;
+               no_pfds++;
+
+               if (!send_buffer_list_empty(&instance->cmd_out_buffer_list)) {
+                       assert(no_pfds < QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS);
+                       pfds[no_pfds].fd = instance->pipe_cmd_send;
+                       pfds[no_pfds].events = POLLOUT;
+                       pfds[no_pfds].revents = 0;
+                       no_pfds++;
+               }
+
+               /*
+                * We know this is never larger than QDEVICE_DEFAULT_HEURISTICS_MAX_TIMEOUT * 2
+                */
+               timeout = (int)instance->sync_timeout * 2;
+
+               poll_res = poll(pfds, no_pfds, timeout);
+               if (poll_res > 0) {
+                       for (i = 0; i < no_pfds; i++) {
+                               if (pfds[i].revents & POLLIN) {
+                                       case_processed = 0;
+                                       switch (i) {
+                                       case 0:
+                                               case_processed = 1;
+
+                                               res = qdevice_heuristics_log_read_from_pipe(instance);
+                                               if (res == -1) {
+                                                       return (-1);
+                                               }
+                                               break;
+                                       case 1:
+                                               case_processed = 1;
+                                               res = qdevice_heuristics_cmd_read_from_pipe(instance);
+                                               if (res == -1) {
+                                                       return (-1);
+                                               }
+                                               break;
+                                       case 2:
+                                               case_processed = 1;
+                                               res = qdevice_votequorum_dispatch(instance->qdevice_instance_ptr);
+                                               if (res == -1) {
+                                                       return (-1);
+                                               }
+                                       case 3:
+                                               /*
+                                                * Read on heuristics cmd send fs shouldn't happen
+                                                */
+                                                break;
+                                       }
+
+                                       if (!case_processed) {
+                                               qdevice_log(LOG_CRIT, "Unhandled read on poll descriptor %u", i);
+                                               exit(1);
+                                       }
+                               }
+
+                               if (pfds[i].revents & POLLOUT) {
+                                       case_processed = 0;
+                                       switch (i) {
+                                       case 0:
+                                       case 1:
+                                       case 2:
+                                               /*
+                                                * Write on heuristics log, cmd recv or vq shouldn't happen
+                                                */
+                                               break;
+                                       case 3:
+                                               case_processed = 1;
+                                               res = qdevice_heuristics_cmd_write(instance);
+                                               if (res == -1) {
+                                                       return (-1);
+                                               }
+                                               break;
+                                       }
+
+                                       if (!case_processed) {
+                                               qdevice_log(LOG_CRIT, "Unhandled write on poll descriptor %u", i);
+                                               exit(1);
+                                       }
+                               }
+
+                               if ((pfds[i].revents & (POLLERR|POLLHUP|POLLNVAL)) &&
+                                   !(pfds[i].revents & (POLLIN|POLLOUT))) {
+                                       switch (i) {
+                                       case 0:
+                                       case 1:
+                                       case 3:
+                                               /*
+                                                *  Closed pipe doesn't mean return of POLLIN. To display
+                                                * better log message, we call read log as if POLLIN would
+                                                * be set.
+                                                */
+                                               res = qdevice_heuristics_log_read_from_pipe(instance);
+                                               if (res == -1) {
+                                                       return (-1);
+                                               }
+
+                                               qdevice_log(LOG_ERR, "POLLERR (%u) on heuristics pipe. Exiting");
+                                               return (-1);
+                                               break;
+                                       case 2:
+                                               qdevice_log(LOG_ERR, "POLLERR (%u) on corosync socket. Exiting");
+                                               return (-1);
+                                               break;
+                                       }
+                               }
+                       }
+               } else if (poll_res == 0) {
+                       qdevice_log(LOG_ERR, "Timeout waiting for initial heuristics exec result");
+                       return (-1);
+               } else {
+                       qdevice_log_err(LOG_ERR, "Initial heuristics exec result poll failed");
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-heuristics.h b/qdevices/qdevice-heuristics.h
new file mode 100644 (file)
index 0000000..80e3d63
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_HEURISTICS_H_
+#define _QDEVICE_HEURISTICS_H_
+
+#include "dynar.h"
+#include "qdevice-advanced-settings.h"
+#include "send-buffer-list.h"
+#include "qdevice-heuristics-instance.h"
+#include "qdevice-heuristics-log.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void            qdevice_heuristics_init(struct qdevice_heuristics_instance *instance,
+    struct qdevice_advanced_settings *advanced_settings);
+
+extern void            qdevice_heuristics_destroy(struct qdevice_heuristics_instance *instance);
+
+extern int             qdevice_heuristics_exec(struct qdevice_heuristics_instance *instance,
+    int sync_in_progress);
+
+extern int             qdevice_heuristics_waiting_for_result(
+    const struct qdevice_heuristics_instance *instance);
+
+extern int             qdevice_heuristics_change_exec_list(
+    struct qdevice_heuristics_instance *instance,
+    const struct qdevice_heuristics_exec_list *new_exec_list, int sync_in_progress);
+
+extern int             qdevice_heuristics_wait_for_initial_exec_result(
+    struct qdevice_heuristics_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_HEURISTICS_H_ */
diff --git a/qdevices/qdevice-instance.c b/qdevices/qdevice-instance.c
new file mode 100644 (file)
index 0000000..5259328
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-config.h"
+#include "qdevice-instance.h"
+#include "qdevice-heuristics-exec-list.h"
+#include "qdevice-log.h"
+#include "qdevice-model.h"
+#include "utils.h"
+
+int
+qdevice_instance_init(struct qdevice_instance *instance,
+    const struct qdevice_advanced_settings *advanced_settings)
+{
+
+       memset(instance, 0, sizeof(*instance));
+
+       node_list_init(&instance->config_node_list);
+
+       instance->vq_last_poll = ((time_t) -1);
+       instance->advanced_settings = advanced_settings;
+
+       return (0);
+}
+
+int
+qdevice_instance_destroy(struct qdevice_instance *instance)
+{
+
+       node_list_free(&instance->config_node_list);
+
+       return (0);
+}
+
+int
+qdevice_instance_configure_from_cmap_heuristics(struct qdevice_instance *instance)
+{
+       char *str;
+       long int li;
+       char *ep;
+       int i;
+       int res;
+       cs_error_t cs_err;
+       cmap_iter_handle_t iter_handle;
+       char key_name[CMAP_KEYNAME_MAXLEN + 1];
+       size_t value_len;
+       cmap_value_types_t type;
+       struct qdevice_heuristics_exec_list tmp_exec_list;
+       struct qdevice_heuristics_exec_list *exec_list;
+       char *command;
+       char exec_name[CMAP_KEYNAME_MAXLEN + 1];
+       char tmp_key[CMAP_KEYNAME_MAXLEN + 1];
+       size_t no_execs;
+       int send_exec_list;
+
+       instance->heuristics_instance.timeout = instance->heartbeat_interval / 2;
+       if (cmap_get_string(instance->cmap_handle,
+           "quorum.device.heuristics.timeout", &str) == CS_OK) {
+               li = strtol(str, &ep, 10);
+               if (li < instance->advanced_settings->heuristics_min_timeout ||
+                   li > instance->advanced_settings->heuristics_max_timeout || *ep != '\0') {
+                       qdevice_log(LOG_ERR, "heuristics.timeout must be valid number in "
+                           "range <%"PRIu32",%"PRIu32">",
+                           instance->advanced_settings->heuristics_min_timeout,
+                           instance->advanced_settings->heuristics_max_timeout);
+
+                       free(str);
+                       return (-1);
+               } else {
+                       instance->heuristics_instance.timeout = li;
+               }
+
+               free(str);
+       }
+
+       instance->heuristics_instance.sync_timeout = instance->sync_heartbeat_interval / 2;
+       if (cmap_get_string(instance->cmap_handle,
+           "quorum.device.heuristics.sync_timeout", &str) == CS_OK) {
+               li = strtol(str, &ep, 10);
+               if (li < instance->advanced_settings->heuristics_min_timeout ||
+                   li > instance->advanced_settings->heuristics_max_timeout || *ep != '\0') {
+                       qdevice_log(LOG_ERR, "heuristics.sync_timeout must be valid number in "
+                           "range <%"PRIu32",%"PRIu32">",
+                           instance->advanced_settings->heuristics_min_timeout,
+                           instance->advanced_settings->heuristics_max_timeout);
+
+                       free(str);
+                       return (-1);
+               } else {
+                       instance->heuristics_instance.sync_timeout = li;
+               }
+
+               free(str);
+       }
+
+       instance->heuristics_instance.interval = instance->heartbeat_interval * 3;
+       if (cmap_get_string(instance->cmap_handle,
+           "quorum.device.heuristics.interval", &str) == CS_OK) {
+               li = strtol(str, &ep, 10);
+               if (li < instance->advanced_settings->heuristics_min_interval ||
+                   li > instance->advanced_settings->heuristics_max_interval || *ep != '\0') {
+                       qdevice_log(LOG_ERR, "heuristics.interval must be valid number in "
+                           "range <%"PRIu32",%"PRIu32">",
+                           instance->advanced_settings->heuristics_min_interval,
+                           instance->advanced_settings->heuristics_max_interval);
+
+                       free(str);
+                       return (-1);
+               } else {
+                       instance->heuristics_instance.interval = li;
+               }
+
+               free(str);
+       }
+
+       instance->heuristics_instance.mode = QDEVICE_DEFAULT_HEURISTICS_MODE;
+
+       if (cmap_get_string(instance->cmap_handle, "quorum.device.heuristics.mode", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       if (strcasecmp(str, "sync") != 0) {
+                               qdevice_log(LOG_ERR, "quorum.device.heuristics.mode value is not valid.");
+
+                               free(str);
+                               return (-1);
+                       } else {
+                               instance->heuristics_instance.mode = QDEVICE_HEURISTICS_MODE_SYNC;
+                       }
+               } else {
+                       if (i == 1) {
+                               instance->heuristics_instance.mode = QDEVICE_HEURISTICS_MODE_ENABLED;
+                       } else {
+                               instance->heuristics_instance.mode = QDEVICE_HEURISTICS_MODE_DISABLED;
+                       }
+               }
+
+               free(str);
+       }
+
+       send_exec_list = 0;
+       exec_list = NULL;
+       qdevice_heuristics_exec_list_init(&tmp_exec_list);
+
+       if (instance->heuristics_instance.mode == QDEVICE_HEURISTICS_MODE_DISABLED) {
+               exec_list = NULL;
+               send_exec_list = 1;
+       } else if (instance->heuristics_instance.mode == QDEVICE_HEURISTICS_MODE_ENABLED ||
+           instance->heuristics_instance.mode == QDEVICE_HEURISTICS_MODE_SYNC) {
+               /*
+                * Walk thru list of commands to exec
+                */
+               cs_err = cmap_iter_init(instance->cmap_handle, "quorum.device.heuristics.exec_", &iter_handle);
+               if (cs_err != CS_OK) {
+                       qdevice_log(LOG_ERR, "Can't iterate quorum.device.heuristics.exec_ keys. "
+                           "Error %s", cs_strerror(cs_err));
+
+                       return (-1);
+               }
+
+               while ((cs_err = cmap_iter_next(instance->cmap_handle, iter_handle, key_name,
+                   &value_len, &type)) == CS_OK) {
+                       if (type != CMAP_VALUETYPE_STRING) {
+                               qdevice_log(LOG_WARNING, "%s key is not of string type. Ignoring");
+                               continue ;
+                       }
+
+                       res = sscanf(key_name, "quorum.device.heuristics.exec_%[^.]%s", exec_name, tmp_key);
+                       if (res != 1) {
+                               qdevice_log(LOG_WARNING, "%s key is not correct heuristics exec name. Ignoring");
+                               continue ;
+                       }
+
+                       cs_err = cmap_get_string(instance->cmap_handle, key_name, &command);
+                       if (cs_err != CS_OK) {
+                               qdevice_log(LOG_WARNING, "Can't get value of %s key. Ignoring");
+                               continue ;
+                       }
+
+                       if (qdevice_heuristics_exec_list_add(&tmp_exec_list, exec_name, command) == NULL) {
+                               qdevice_log(LOG_WARNING, "Can't store value of %s key into list. Ignoring");
+                       }
+
+                       free(command);
+               }
+
+               no_execs = qdevice_heuristics_exec_list_size(&tmp_exec_list);
+
+               if (no_execs == 0) {
+                       qdevice_log(LOG_INFO, "No valid heuristics execs defined. Disabling heuristics.");
+                       instance->heuristics_instance.mode = QDEVICE_HEURISTICS_MODE_DISABLED;
+                       exec_list = NULL;
+                       send_exec_list = 1;
+               } else if (no_execs > instance->advanced_settings->heuristics_max_execs) {
+                       qdevice_log(LOG_ERR, "Too much (%zu) heuristics execs defined (max is %zu)."
+                           " Disabling heuristics.", no_execs,
+                           instance->advanced_settings->heuristics_max_execs);
+                       instance->heuristics_instance.mode = QDEVICE_HEURISTICS_MODE_DISABLED;
+                       exec_list = NULL;
+                       send_exec_list = 1;
+               } else if (qdevice_heuristics_exec_list_eq(&tmp_exec_list,
+                   &instance->heuristics_instance.exec_list) == 1) {
+                       qdevice_log(LOG_DEBUG, "Heuristics list is unchanged");
+                       send_exec_list = 0;
+               } else {
+                       qdevice_log(LOG_DEBUG, "Heuristics list changed");
+                       exec_list = &tmp_exec_list;
+                       send_exec_list = 1;
+               }
+
+       } else {
+               qdevice_log(LOG_CRIT, "Undefined heuristics mode");
+               exit(1);
+       }
+
+       if (send_exec_list) {
+               if (qdevice_heuristics_change_exec_list(&instance->heuristics_instance,
+                   exec_list, instance->sync_in_progress) != 0) {
+                       return (-1);
+               }
+       }
+
+       qdevice_heuristics_exec_list_free(&tmp_exec_list);
+
+       return (0);
+}
+
+int
+qdevice_instance_configure_from_cmap(struct qdevice_instance *instance)
+{
+       char *str;
+
+       if (cmap_get_string(instance->cmap_handle, "quorum.device.model", &str) != CS_OK) {
+               qdevice_log(LOG_ERR, "Can't read quorum.device.model cmap key.");
+
+               return (-1);
+       }
+
+       if (qdevice_model_str_to_type(str, &instance->model_type) != 0) {
+               qdevice_log(LOG_ERR, "Configured device model %s is not supported.", str);
+               free(str);
+
+               return (-1);
+       }
+       free(str);
+
+       if (cmap_get_uint32(instance->cmap_handle, "runtime.votequorum.this_node_id",
+           &instance->node_id) != CS_OK) {
+               qdevice_log(LOG_ERR, "Unable to retrieve this node nodeid.");
+
+               return (-1);
+       }
+
+       if (cmap_get_uint32(instance->cmap_handle, "quorum.device.timeout", &instance->heartbeat_interval) != CS_OK) {
+               instance->heartbeat_interval = VOTEQUORUM_QDEVICE_DEFAULT_TIMEOUT;
+       }
+
+       if (cmap_get_uint32(instance->cmap_handle, "quorum.device.sync_timeout",
+           &instance->sync_heartbeat_interval) != CS_OK) {
+               instance->sync_heartbeat_interval = VOTEQUORUM_QDEVICE_DEFAULT_SYNC_TIMEOUT;
+       }
+
+       if (qdevice_instance_configure_from_cmap_heuristics(instance) != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-instance.h b/qdevices/qdevice-instance.h
new file mode 100644 (file)
index 0000000..d137e52
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_INSTANCE_H_
+#define _QDEVICE_INSTANCE_H_
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <corosync/cmap.h>
+#include <corosync/votequorum.h>
+
+#include "qdevice-advanced-settings.h"
+#include "qdevice-heuristics.h"
+#include "qdevice-model-type.h"
+#include "node-list.h"
+#include "unix-socket-ipc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qdevice_instance {
+       cmap_handle_t cmap_handle;
+       int cmap_poll_fd;
+       int cmap_reload_in_progress;
+       cmap_track_handle_t cmap_reload_track_handle;
+       cmap_track_handle_t cmap_nodelist_track_handle;
+       cmap_track_handle_t cmap_logging_track_handle;
+       cmap_track_handle_t cmap_heuristics_track_handle;
+
+       votequorum_handle_t votequorum_handle;
+       int votequorum_poll_fd;
+
+       struct unix_socket_ipc local_ipc;
+
+       enum qdevice_model_type model_type;
+
+       uint32_t node_id;
+       uint32_t heartbeat_interval;            /* Heartbeat interval during normal operation */
+       uint32_t sync_heartbeat_interval;       /* Heartbeat interval during corosync sync */
+
+       struct node_list config_node_list;
+       int config_node_list_version_set;
+       uint64_t config_node_list_version;
+
+       /*
+        * Copy of votequorum_quorum_notify_fn callback paramters.
+        * Set after model callback is called.
+        */
+       uint32_t vq_quorum_quorate;
+       uint32_t vq_quorum_node_list_entries;
+       votequorum_node_t *vq_quorum_node_list;
+
+       /*
+        * Copy of current votequorum_nodelist_notify_fn callback parameters.
+        * Set after model callback qdevice_votequorum_node_list_notify_callback is called.
+        */
+       uint8_t vq_node_list_initial_ring_id_set;
+       votequorum_ring_id_t vq_node_list_ring_id;
+       uint32_t vq_node_list_entries;
+       uint32_t *vq_node_list;
+       uint8_t vq_node_list_initial_heuristics_finished;
+       enum qdevice_heuristics_exec_result vq_node_list_heuristics_result;
+
+       /*
+        * Copy of current votequorum_nodelist_notify_fn callback ring id
+        * It's set before any callback is called and used for qdevice_votequorum_poll
+        */
+       votequorum_ring_id_t vq_poll_ring_id;
+
+       /*
+        * Copy of votequorum_expectedvotes_notify_fn callback parameters.
+        * Set after model callback is called.
+        */
+       uint32_t vq_expected_votes;
+
+       time_t vq_last_poll;
+       int vq_last_poll_cast_vote;
+
+       void *model_data;
+
+       const struct qdevice_advanced_settings *advanced_settings;
+
+       int sync_in_progress;
+
+       struct qdevice_heuristics_instance heuristics_instance;
+};
+
+extern int     qdevice_instance_init(struct qdevice_instance *instance,
+    const struct qdevice_advanced_settings *advanced_settings);
+
+extern int     qdevice_instance_destroy(struct qdevice_instance *instance);
+
+extern int     qdevice_instance_configure_from_cmap(struct qdevice_instance *instance);
+
+extern int     qdevice_instance_configure_from_cmap_heuristics(struct qdevice_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_INSTANCE_H_ */
diff --git a/qdevices/qdevice-ipc-cmd.c b/qdevices/qdevice-ipc-cmd.c
new file mode 100644 (file)
index 0000000..aa66157
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-ipc-cmd.h"
+#include "qdevice-log.h"
+#include "qdevice-model.h"
+#include "dynar-str.h"
+#include "utils.h"
+
+static int
+qdevice_ipc_cmd_status_add_header(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+
+       return ((dynar_str_catf(outbuf, "Qdevice information\n") != -1) &&
+           (dynar_str_catf(outbuf, "-------------------\n") != -1));
+}
+
+static int
+qdevice_ipc_cmd_status_add_model(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+
+       return (dynar_str_catf(outbuf, "Model:\t\t\t%s\n",
+               qdevice_model_type_to_str(instance->model_type)) != -1);
+}
+
+static int
+qdevice_ipc_cmd_status_add_nodeid(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+
+       return (dynar_str_catf(outbuf, "Node ID:\t\t"UTILS_PRI_NODE_ID"\n",
+           instance->node_id) != -1);
+}
+
+static int
+qdevice_ipc_cmd_status_add_intervals(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+
+       if (!verbose) {
+               return (1);
+       }
+
+       return ((dynar_str_catf(outbuf, "HB interval:\t\t%"PRIu32"ms\n",
+           instance->heartbeat_interval) != -1) &&
+           (dynar_str_catf(outbuf, "Sync HB interval:\t%"PRIu32"ms\n",
+           instance->sync_heartbeat_interval) != -1));
+}
+
+static int
+qdevice_ipc_cmd_status_add_config_node_list(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+       struct node_list_entry *node_info;
+       size_t zi;
+
+       if (instance->config_node_list_version_set) {
+               if (dynar_str_catf(outbuf, "Configuration version:\t"UTILS_PRI_CONFIG_VERSION"\n",
+                   instance->config_node_list_version) == -1) {
+                       return (0);
+               }
+       }
+
+       if (dynar_str_catf(outbuf, "Configured node list:\n") == -1) {
+               return (0);
+       }
+
+       zi = 0;
+
+       TAILQ_FOREACH(node_info, &instance->config_node_list, entries) {
+               if ((dynar_str_catf(outbuf, "    %zu\tNode ID = "UTILS_PRI_NODE_ID, zi,
+                   node_info->node_id) == -1) ||
+                   (node_info->data_center_id != 0 && dynar_str_catf(outbuf, ", Data center ID = "
+                   UTILS_PRI_DATACENTER_ID, node_info->data_center_id) == -1) ||
+                   (dynar_str_catf(outbuf, "\n") == -1)) {
+                       return (0);
+               }
+
+               zi++;
+       }
+
+       return (1);
+}
+
+static int
+qdevice_ipc_cmd_status_add_membership_node_list(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+       uint32_t u32;
+
+       if (verbose && dynar_str_catf(outbuf, "Ring ID:\t\t"UTILS_PRI_RING_ID"\n",
+           instance->vq_node_list_ring_id.nodeid, instance->vq_node_list_ring_id.seq) == -1) {
+               return (0);
+       }
+
+       if (dynar_str_catf(outbuf, "Membership node list:\t") == -1) {
+               return (0);
+       }
+
+       for (u32 = 0; u32 < instance->vq_node_list_entries; u32++) {
+               if (u32 != 0) {
+                       if (dynar_str_catf(outbuf, ", ") == -1) {
+                               return (0);
+                       }
+               }
+
+               if (dynar_str_catf(outbuf, UTILS_PRI_NODE_ID, instance->vq_node_list[u32]) == -1) {
+                       return (0);
+               }
+       }
+
+       if (dynar_str_catf(outbuf, "\n") == -1) {
+               return (0);
+       }
+
+       return (1);
+}
+
+static const char *
+qdevice_ipc_cmd_vq_nodestate_to_str(uint32_t state)
+{
+
+       switch (state) {
+       case VOTEQUORUM_NODESTATE_MEMBER: return ("member"); break;
+       case VOTEQUORUM_NODESTATE_DEAD: return ("dead"); break;
+       case VOTEQUORUM_NODESTATE_LEAVING: return ("leaving"); break;
+       default:
+               qdevice_log(LOG_ERR, "qdevice_ipc_cmd_vq_nodestate_to_str: Unhandled votequorum "
+                   "node state %"PRIu32, state);
+               exit(1);
+               break;
+       }
+
+       return ("Unhandled votequorum node state");
+}
+
+static int
+qdevice_ipc_cmd_status_add_quorum_node_list(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+       uint32_t u32;
+       votequorum_node_t *node;
+
+       if (!verbose) {
+               return (1);
+       }
+
+       if (dynar_str_catf(outbuf, "Quorate:\t\t%s\n",
+           (instance->vq_quorum_quorate ? "Yes" : "No")) == -1) {
+               return (0);
+       }
+
+       if (dynar_str_catf(outbuf, "Quorum node list:\n") == -1) {
+               return (0);
+       }
+
+       for (u32 = 0; u32 < instance->vq_quorum_node_list_entries; u32++) {
+               node = &instance->vq_quorum_node_list[u32];
+
+               if (node->nodeid == 0) {
+                       continue;
+               }
+
+               if (dynar_str_catf(outbuf, "    %"PRIu32"\tNode ID = "UTILS_PRI_NODE_ID
+                   ", State = %s\n", u32, node->nodeid,
+                   qdevice_ipc_cmd_vq_nodestate_to_str(node->state)) == -1) {
+                       return (0);
+               }
+       }
+
+       return (1);
+}
+
+static int
+qdevice_ipc_cmd_status_add_expected_votes(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+
+       if (!verbose) {
+               return (1);
+       }
+
+       return (dynar_str_catf(outbuf, "Expected votes:\t\t"UTILS_PRI_EXPECTED_VOTES"\n",
+           instance->vq_expected_votes) != -1);
+}
+
+static int
+qdevice_ipc_cmd_status_add_last_poll(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+       struct tm tm_res;
+
+       if (!verbose) {
+               return (1);
+       }
+
+       if (instance->vq_last_poll == ((time_t) -1)) {
+               return (dynar_str_catf(outbuf, "Last poll call:\t\tNever\n") != -1);
+       }
+
+       localtime_r(&instance->vq_last_poll, &tm_res);
+
+       if (dynar_str_catf(outbuf, "Last poll call:\t\t%04d-%02d-%02dT%02d:%02d:%02d%s\n",
+           tm_res.tm_year + 1900, tm_res.tm_mon + 1, tm_res.tm_mday,
+           tm_res.tm_hour, tm_res.tm_min, tm_res.tm_sec,
+           (instance->vq_last_poll_cast_vote ? " (cast vote)" : "")) == -1) {
+               return (0);
+       }
+
+       return (1);
+}
+
+static int
+qdevice_ipc_cmd_status_add_heuristics(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose)
+{
+
+       if (!verbose) {
+               return (1);
+       }
+
+       return (dynar_str_catf(outbuf, "Heuristics:\t\t%s\n",
+           qdevice_heuristics_mode_to_str(instance->heuristics_instance.mode)) != 0);
+}
+
+int
+qdevice_ipc_cmd_status(struct qdevice_instance *instance, struct dynar *outbuf, int verbose)
+{
+
+       if (qdevice_ipc_cmd_status_add_header(instance, outbuf, verbose) &&
+           qdevice_ipc_cmd_status_add_model(instance, outbuf, verbose) &&
+           qdevice_ipc_cmd_status_add_nodeid(instance, outbuf, verbose) &&
+           qdevice_ipc_cmd_status_add_intervals(instance, outbuf, verbose) &&
+           qdevice_ipc_cmd_status_add_config_node_list(instance, outbuf, verbose) &&
+           qdevice_ipc_cmd_status_add_heuristics(instance, outbuf, verbose) &&
+           qdevice_ipc_cmd_status_add_membership_node_list(instance, outbuf, verbose) &&
+           qdevice_ipc_cmd_status_add_quorum_node_list(instance, outbuf, verbose) &&
+           qdevice_ipc_cmd_status_add_expected_votes(instance, outbuf, verbose) &&
+           qdevice_ipc_cmd_status_add_last_poll(instance, outbuf, verbose) &&
+           dynar_str_catf(outbuf, "\n") != -1 &&
+           qdevice_model_ipc_cmd_status(instance, outbuf, verbose) != -1) {
+               return (0);
+       }
+
+       return (-1);
+}
diff --git a/qdevices/qdevice-ipc-cmd.h b/qdevices/qdevice-ipc-cmd.h
new file mode 100644 (file)
index 0000000..cfda6e7
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_IPC_CMD_H_
+#define _QDEVICE_IPC_CMD_H_
+
+#include "dynar.h"
+#include "qdevice-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_ipc_cmd_status(struct qdevice_instance *instance, struct dynar *outbuf,
+    int verbose);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_IPC_CMD_H_ */
diff --git a/qdevices/qdevice-ipc.c b/qdevices/qdevice-ipc.c
new file mode 100644 (file)
index 0000000..73aba3d
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-config.h"
+#include "qdevice-ipc.h"
+#include "qdevice-log.h"
+#include "unix-socket-ipc.h"
+#include "dynar-simple-lex.h"
+#include "dynar-str.h"
+#include "qdevice-ipc-cmd.h"
+
+int
+qdevice_ipc_init(struct qdevice_instance *instance)
+{
+       if (unix_socket_ipc_init(&instance->local_ipc,
+           instance->advanced_settings->local_socket_file,
+           instance->advanced_settings->local_socket_backlog,
+           instance->advanced_settings->ipc_max_clients,
+           instance->advanced_settings->ipc_max_receive_size,
+           instance->advanced_settings->ipc_max_send_size) != 0) {
+               qdevice_log_err(LOG_ERR, "Can't create unix socket");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_ipc_close(struct qdevice_instance *instance)
+{
+       int res;
+
+       res = unix_socket_ipc_close(&instance->local_ipc);
+       if (res != 0) {
+               qdevice_log_err(LOG_WARNING, "Can't close local IPC");
+       }
+
+       return (res);
+}
+
+int
+qdevice_ipc_is_closed(struct qdevice_instance *instance)
+{
+
+       return (unix_socket_ipc_is_closed(&instance->local_ipc));
+}
+
+int
+qdevice_ipc_destroy(struct qdevice_instance *instance)
+{
+       int res;
+       struct unix_socket_client *client;
+       const struct unix_socket_client_list *ipc_client_list;
+
+       ipc_client_list = &instance->local_ipc.clients;
+
+       TAILQ_FOREACH(client, ipc_client_list, entries) {
+               free(client->user_data);
+       }
+
+       res = unix_socket_ipc_destroy(&instance->local_ipc);
+       if (res != 0) {
+               qdevice_log_err(LOG_WARNING, "Can't destroy local IPC");
+       }
+
+       return (res);
+}
+
+int
+qdevice_ipc_accept(struct qdevice_instance *instance, struct unix_socket_client **res_client)
+{
+       int res;
+       int accept_res;
+
+       accept_res = unix_socket_ipc_accept(&instance->local_ipc, res_client);
+
+       switch (accept_res) {
+       case -1:
+               qdevice_log_err(LOG_ERR, "Can't accept local IPC connection");
+               res = -1;
+               goto return_res;
+               break;
+       case -2:
+               qdevice_log(LOG_ERR, "Maximum IPC clients reached. Not accepting connection");
+               res = -1;
+               goto return_res;
+               break;
+       case -3:
+               qdevice_log(LOG_ERR, "Can't add client to list");
+               res = -1;
+               goto return_res;
+               break;
+       default:
+               unix_socket_client_read_line(*res_client, 1);
+               res = 0;
+               break;
+       }
+
+       (*res_client)->user_data = malloc(sizeof(struct qdevice_ipc_user_data));
+       if ((*res_client)->user_data == NULL) {
+               qdevice_log(LOG_ERR, "Can't alloc IPC client user data");
+               res = -1;
+               qdevice_ipc_client_disconnect(instance, *res_client);
+       } else {
+               memset((*res_client)->user_data, 0, sizeof(struct qdevice_ipc_user_data));
+       }
+
+return_res:
+       return (res);
+}
+
+void
+qdevice_ipc_client_disconnect(struct qdevice_instance *instance, struct unix_socket_client *client)
+{
+
+       free(client->user_data);
+       unix_socket_ipc_client_disconnect(&instance->local_ipc, client);
+}
+
+int
+qdevice_ipc_send_error(struct qdevice_instance *instance, struct unix_socket_client *client,
+    const char *error_fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, error_fmt);
+       res = ((dynar_str_cpy(&client->send_buffer, "Error\n") == 0) &&
+           (dynar_str_vcatf(&client->send_buffer, error_fmt, ap) > 0) &&
+           (dynar_str_cat(&client->send_buffer, "\n") == 0));
+
+       va_end(ap);
+
+       if (res) {
+               unix_socket_client_write_buffer(client, 1);
+       } else {
+               qdevice_log(LOG_ERR, "Can't send ipc error to client (buffer too small)");
+       }
+
+       return (res ? 0 : -1);
+}
+
+int
+qdevice_ipc_send_buffer(struct qdevice_instance *instance, struct unix_socket_client *client)
+{
+
+       if (dynar_str_prepend(&client->send_buffer, "OK\n") != 0) {
+               qdevice_log(LOG_ERR, "Can't send ipc message to client (buffer too small)");
+
+               if (qdevice_ipc_send_error(instance, client, "Internal IPC buffer too small") != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       unix_socket_client_write_buffer(client, 1);
+
+       return (0);
+}
+
+static void
+qdevice_ipc_parse_line(struct qdevice_instance *instance, struct unix_socket_client *client)
+{
+       struct dynar_simple_lex lex;
+       struct dynar *token;
+       char *str;
+       struct qdevice_ipc_user_data *ipc_user_data;
+       int verbose;
+
+       ipc_user_data = (struct qdevice_ipc_user_data *)client->user_data;
+
+       dynar_simple_lex_init(&lex, &client->receive_buffer, DYNAR_SIMPLE_LEX_TYPE_PLAIN);
+       token = dynar_simple_lex_token_next(&lex);
+
+       verbose = 0;
+
+       if (token == NULL) {
+               qdevice_log(LOG_ERR, "Can't alloc memory for simple lex");
+
+               if (qdevice_ipc_send_error(instance, client, "Command too long") != 0) {
+                       client->schedule_disconnect = 1;
+               }
+
+               return;
+       }
+
+       str = dynar_data(token);
+       if (strcasecmp(str, "") == 0) {
+               qdevice_log(LOG_DEBUG, "IPC client doesn't send command");
+               if (qdevice_ipc_send_error(instance, client, "No command specified") != 0) {
+                       client->schedule_disconnect = 1;
+               }
+       } else if (strcasecmp(str, "shutdown") == 0) {
+               qdevice_log(LOG_DEBUG, "IPC client requested shutdown");
+
+               ipc_user_data->shutdown_requested = 1;
+
+               if (qdevice_ipc_send_buffer(instance, client) != 0) {
+                       client->schedule_disconnect = 1;
+               }
+       } else if (strcasecmp(str, "status") == 0) {
+               token = dynar_simple_lex_token_next(&lex);
+
+               if (token != NULL && (str = dynar_data(token), strcmp(str, "")) != 0) {
+                       if (strcasecmp(str, "verbose") == 0) {
+                               verbose = 1;
+                       }
+               }
+
+               if (qdevice_ipc_cmd_status(instance, &client->send_buffer, verbose) != 0) {
+                       if (qdevice_ipc_send_error(instance, client, "Can't get QDevice status") != 0) {
+                               client->schedule_disconnect = 1;
+                       }
+               } else {
+                       if (qdevice_ipc_send_buffer(instance, client) != 0) {
+                               client->schedule_disconnect = 1;
+                       }
+               }
+       } else {
+               qdevice_log(LOG_DEBUG, "IPC client sent unknown command");
+               if (qdevice_ipc_send_error(instance, client, "Unknown command '%s'", str) != 0) {
+                       client->schedule_disconnect = 1;
+               }
+       }
+
+       dynar_simple_lex_destroy(&lex);
+}
+
+void
+qdevice_ipc_io_read(struct qdevice_instance *instance, struct unix_socket_client *client)
+{
+       int res;
+
+       res = unix_socket_client_io_read(client);
+
+       switch (res) {
+       case 0:
+               /*
+                * Partial read
+                */
+               break;
+       case -1:
+               qdevice_log(LOG_DEBUG, "IPC client closed connection");
+               client->schedule_disconnect = 1;
+               break;
+       case -2:
+               qdevice_log(LOG_ERR, "Can't store message from IPC client. Disconnecting client.");
+               client->schedule_disconnect = 1;
+               break;
+       case -3:
+               qdevice_log_err(LOG_ERR, "Can't receive message from IPC client. Disconnecting client.");
+               client->schedule_disconnect = 1;
+               break;
+       case 1:
+               /*
+                * Full message received
+                */
+               unix_socket_client_read_line(client, 0);
+
+               qdevice_ipc_parse_line(instance, client);
+               break;
+       }
+}
+
+void
+qdevice_ipc_io_write(struct qdevice_instance *instance, struct unix_socket_client *client)
+{
+       int res;
+       struct qdevice_ipc_user_data *ipc_user_data;
+
+       ipc_user_data = (struct qdevice_ipc_user_data *)client->user_data;
+
+       res = unix_socket_client_io_write(client);
+
+       switch (res) {
+       case 0:
+               /*
+                * Partial send
+                */
+               break;
+       case -1:
+               qdevice_log(LOG_DEBUG, "IPC client closed connection");
+               client->schedule_disconnect = 1;
+               break;
+       case -2:
+               qdevice_log_err(LOG_ERR, "Can't send message to IPC client. Disconnecting client");
+               client->schedule_disconnect = 1;
+               break;
+       case 1:
+               /*
+                * Full message sent
+                */
+               unix_socket_client_write_buffer(client, 0);
+               client->schedule_disconnect = 1;
+
+               if (ipc_user_data->shutdown_requested) {
+                       qdevice_ipc_close(instance);
+               }
+
+               break;
+       }
+}
diff --git a/qdevices/qdevice-ipc.h b/qdevices/qdevice-ipc.h
new file mode 100644 (file)
index 0000000..8185799
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_IPC_H_
+#define _QDEVICE_IPC_H_
+
+#include "qdevice-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qdevice_ipc_user_data {
+       void *model_data;
+       int shutdown_requested;
+};
+
+extern int             qdevice_ipc_init(struct qdevice_instance *instance);
+
+extern int             qdevice_ipc_close(struct qdevice_instance *instance);
+
+extern int             qdevice_ipc_destroy(struct qdevice_instance *instance);
+
+extern int             qdevice_ipc_accept(struct qdevice_instance *instance,
+    struct unix_socket_client **res_client);
+
+extern void            qdevice_ipc_client_disconnect(struct qdevice_instance *instance,
+    struct unix_socket_client *client);
+
+extern void            qdevice_ipc_io_read(struct qdevice_instance *instance,
+    struct unix_socket_client *client);
+
+extern void            qdevice_ipc_io_write(struct qdevice_instance *instance,
+    struct unix_socket_client *client);
+
+extern int             qdevice_ipc_send_error(struct qdevice_instance *instance,
+    struct unix_socket_client *client, const char *error_fmt, ...)
+    __attribute__((__format__(__printf__, 3, 4)));
+
+extern int             qdevice_ipc_send_buffer(struct qdevice_instance *instance,
+    struct unix_socket_client *client);
+
+extern int             qdevice_ipc_is_closed(struct qdevice_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_IPC_H_ */
diff --git a/qdevices/qdevice-log-debug.c b/qdevices/qdevice-log-debug.c
new file mode 100644 (file)
index 0000000..4886ca3
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-log-debug.h"
+#include "qdevice-log.h"
+#include "utils.h"
+
+void
+qdevice_log_debug_dump_node_list(const struct node_list *nlist)
+{
+       struct node_list_entry *node_info;
+       size_t zi;
+
+       qdevice_log(LOG_DEBUG, "  Node list:");
+
+       zi = 0;
+
+       TAILQ_FOREACH(node_info, nlist, entries) {
+               qdevice_log(LOG_DEBUG, "    %zu node_id = "UTILS_PRI_NODE_ID", "
+                   "data_center_id = "UTILS_PRI_DATACENTER_ID", node_state = %s",
+                   zi, node_info->node_id, node_info->data_center_id,
+                   tlv_node_state_to_str(node_info->node_state));
+               zi++;
+       }
+}
diff --git a/qdevices/qdevice-log-debug.h b/qdevices/qdevice-log-debug.h
new file mode 100644 (file)
index 0000000..23469ba
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_LOG_DEBUG_H_
+#define _QDEVICE_LOG_DEBUG_H_
+
+#include "node-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void            qdevice_log_debug_dump_node_list(const struct node_list *nlist);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_LOG_DEBUG_H_ */
diff --git a/qdevices/qdevice-log.c b/qdevices/qdevice-log.c
new file mode 100644 (file)
index 0000000..44fbe58
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-log.h"
+#include "qdevice-config.h"
+#include "utils.h"
+
+static int qdevice_log_global_force_debug;
+
+struct qdevice_log_syslog_names {
+       const char *prio_name;
+       int priority;
+};
+
+static struct qdevice_log_syslog_names qdevice_log_priority_names[] = {
+       { "alert", LOG_ALERT },
+       { "crit", LOG_CRIT },
+       { "debug", LOG_DEBUG },
+       { "emerg", LOG_EMERG },
+       { "err", LOG_ERR },
+       { "error", LOG_ERR },
+       { "info", LOG_INFO },
+       { "notice", LOG_NOTICE },
+       { "warning", LOG_WARNING },
+       { NULL, -1 }};
+
+static int
+qdevice_log_priority_str_to_int(const char *priority_str)
+{
+       unsigned int i;
+
+       for (i = 0; qdevice_log_priority_names[i].prio_name != NULL; i++) {
+               if (strcasecmp(priority_str, qdevice_log_priority_names[i].prio_name) == 0) {
+                       return (qdevice_log_priority_names[i].priority);
+               }
+       }
+
+       return (-1);
+}
+
+void
+qdevice_log_configure(struct qdevice_instance *instance)
+{
+       int to_stderr;
+       int to_syslog;
+       int syslog_facility;
+       int syslog_priority;
+       int logfile_priority;
+       int debug;
+       char *str;
+       int i;
+       int fileline;
+       int timestamp;
+       int function_name;
+       char log_format_syslog[64];
+       char log_format_stderr[64];
+
+       to_stderr = QDEVICE_LOG_DEFAULT_TO_STDERR;
+       if (cmap_get_string(instance->cmap_handle, "logging.to_stderr", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       qdevice_log(LOG_WARNING, "logging.to_stderr value is not valid");
+               } else {
+                       to_stderr = i;
+               }
+               free(str);
+       }
+
+       if (cmap_get_string(instance->cmap_handle,
+           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".to_stderr", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       qdevice_log(LOG_WARNING,
+                           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".to_stderr value is not valid.");
+               } else {
+                       to_stderr = i;
+               }
+               free(str);
+       }
+
+       to_syslog = QDEVICE_LOG_DEFAULT_TO_SYSLOG;
+       if (cmap_get_string(instance->cmap_handle, "logging.to_syslog", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       qdevice_log(LOG_WARNING, "logging.to_syslog value is not valid");
+               } else {
+                       to_syslog = i;
+               }
+               free(str);
+       }
+
+       if (cmap_get_string(instance->cmap_handle,
+           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".to_syslog", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       qdevice_log(LOG_WARNING,
+                           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".to_syslog value is not valid.");
+               } else {
+                       to_syslog = i;
+               }
+               free(str);
+       }
+
+       syslog_facility = QDEVICE_LOG_DEFAULT_SYSLOG_FACILITY;
+       if (cmap_get_string(instance->cmap_handle, "logging.syslog_facility", &str) == CS_OK) {
+               if ((i = qb_log_facility2int(str)) < 0) {
+                       qdevice_log(LOG_WARNING, "logging.syslog_facility value is not valid");
+               } else {
+                       syslog_facility = i;
+               }
+
+               free(str);
+       }
+
+       if (cmap_get_string(instance->cmap_handle,
+           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".syslog_facility", &str) == CS_OK) {
+               if ((i = qb_log_facility2int(str)) < 0) {
+                       qdevice_log(LOG_WARNING,
+                           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".syslog_facility value is not valid.");
+               } else {
+                       syslog_facility = i;
+               }
+               free(str);
+       }
+
+       syslog_priority = QDEVICE_LOG_DEFAULT_SYSLOG_PRIORITY;
+       if (cmap_get_string(instance->cmap_handle, "logging.syslog_priority", &str) == CS_OK) {
+               if ((i = qdevice_log_priority_str_to_int(str)) < 0) {
+                       qdevice_log(LOG_WARNING, "logging.syslog_priority value is not valid");
+               } else {
+                       syslog_priority = i;
+               }
+
+               free(str);
+       }
+
+       if (cmap_get_string(instance->cmap_handle,
+           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".syslog_priority", &str) == CS_OK) {
+               if ((i = qdevice_log_priority_str_to_int(str)) < 0) {
+                       qdevice_log(LOG_WARNING,
+                           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".syslog_priority value is not valid.");
+               } else {
+                       syslog_priority = i;
+               }
+               free(str);
+       }
+
+       logfile_priority = QDEVICE_LOG_DEFAULT_SYSLOG_PRIORITY;
+       if (cmap_get_string(instance->cmap_handle, "logging.logfile_priority", &str) == CS_OK) {
+               if ((i = qdevice_log_priority_str_to_int(str)) < 0) {
+                       qdevice_log(LOG_WARNING, "logging.logfile_priority value is not valid");
+               } else {
+                       logfile_priority = i;
+               }
+
+               free(str);
+       }
+
+       if (cmap_get_string(instance->cmap_handle,
+           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".logfile_priority", &str) == CS_OK) {
+               if ((i = qdevice_log_priority_str_to_int(str)) < 0) {
+                       qdevice_log(LOG_WARNING,
+                           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".logfile_priority value is not valid.");
+               } else {
+                       logfile_priority = i;
+               }
+               free(str);
+       }
+
+       debug = QDEVICE_LOG_DEFAULT_DEBUG;
+       if (cmap_get_string(instance->cmap_handle, "logging.debug", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       if (strcasecmp(str, "trace") == 0) {
+                               debug = 1;
+                       } else {
+                               qdevice_log(LOG_WARNING, "logging.debug value is not valid");
+                       }
+               } else {
+                       debug = i;
+               }
+               free(str);
+       }
+
+       if (cmap_get_string(instance->cmap_handle,
+           "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".debug", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       if (strcasecmp(str, "trace") == 0) {
+                               debug = 1;
+                       } else {
+                               qdevice_log(LOG_WARNING,
+                                   "logging.logger_subsys." QDEVICE_LOG_SUBSYS ".debug value is not valid.");
+                       }
+               } else {
+                       debug = i;
+               }
+               free(str);
+       }
+
+       fileline = QDEVICE_LOG_DEFAULT_FILELINE;
+       if (cmap_get_string(instance->cmap_handle, "logging.fileline", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       qdevice_log(LOG_WARNING, "logging.fileline value is not valid");
+               } else {
+                       fileline = i;
+               }
+               free(str);
+       }
+
+       timestamp = QDEVICE_LOG_DEFAULT_TIMESTAMP;
+       if (cmap_get_string(instance->cmap_handle, "logging.timestamp", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       qdevice_log(LOG_WARNING, "logging.timestamp value is not valid");
+               } else {
+                       timestamp = i;
+               }
+               free(str);
+       }
+
+       function_name = QDEVICE_LOG_DEFAULT_FUNCTION_NAME;
+       if (cmap_get_string(instance->cmap_handle, "logging.function_name", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       qdevice_log(LOG_WARNING, "logging.function_name value is not valid");
+               } else {
+                       function_name = i;
+               }
+               free(str);
+       }
+
+       strcpy(log_format_syslog, "");
+
+       if (fileline) {
+               strcat(log_format_syslog, "%f:");
+
+               if (function_name) {
+                       strcat(log_format_syslog, "%n:");
+               }
+
+               strcat(log_format_syslog, "%l ");
+       }
+
+       strcat(log_format_syslog, "%b");
+
+       strcpy(log_format_stderr, "");
+       if (timestamp) {
+               strcpy(log_format_stderr, "%t %7p ");
+       }
+       strcat(log_format_stderr, log_format_syslog);
+
+       if (qdevice_log_global_force_debug) {
+               debug = 1;
+       }
+
+       /*
+        * Finally reconfigure log system
+        */
+       qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, to_stderr);
+       qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, to_syslog);
+       qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, syslog_facility);
+       qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_CLEAR_ALL, QB_LOG_FILTER_FILE, "*", LOG_TRACE);
+       qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*",
+           (debug ? LOG_DEBUG : syslog_priority));
+       qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_CLEAR_ALL, QB_LOG_FILTER_FILE, "*", LOG_TRACE);
+       qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*",
+           (debug ? LOG_DEBUG : logfile_priority));
+
+       qb_log_format_set(QB_LOG_STDERR, log_format_stderr);
+       qb_log_format_set(QB_LOG_SYSLOG, log_format_syslog);
+}
+
+void
+qdevice_log_init(struct qdevice_instance *instance, int force_debug)
+{
+       qdevice_log_global_force_debug = force_debug;
+
+       qb_log_init(QDEVICE_PROGRAM_NAME, QDEVICE_LOG_DEFAULT_SYSLOG_FACILITY,
+           QDEVICE_LOG_DEFAULT_SYSLOG_PRIORITY);
+
+       qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
+       qb_log_ctl(QB_LOG_STDOUT, QB_LOG_CONF_ENABLED, QB_FALSE);
+       qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
+       qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
+
+       qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_INFO);
+       qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_INFO);
+       qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_PRIORITY_BUMP, LOG_INFO - LOG_DEBUG);
+       qb_log_format_set(QB_LOG_STDERR, "%t %7p %b");
+
+       qdevice_log_configure(instance);
+}
+
+void
+qdevice_log_close(struct qdevice_instance *instance)
+{
+
+       qb_log_fini();
+}
diff --git a/qdevices/qdevice-log.h b/qdevices/qdevice-log.h
new file mode 100644 (file)
index 0000000..f167424
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_LOG_H_
+#define _QDEVICE_LOG_H_
+
+#include <qb/qbdefs.h>
+#include <qb/qblog.h>
+#include <prerror.h>
+
+#include "qdevice-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define qdevice_log    qb_log
+#define qdevice_log_nss(priority, str) qdevice_log(priority, "%s (%d): %s", \
+    str, PR_GetError(), PR_ErrorToString(PR_GetError(), PR_LANGUAGE_I_DEFAULT));
+
+#define qdevice_log_err(priority, str) qdevice_log(priority, "%s (%d): %s", \
+    str, errno, strerror(errno));
+
+extern void            qdevice_log_init(struct qdevice_instance *instance, int force_debug);
+
+extern void            qdevice_log_configure(struct qdevice_instance *instance);
+
+extern void            qdevice_log_close(struct qdevice_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_LOG_H_ */
diff --git a/qdevices/qdevice-model-net.c b/qdevices/qdevice-model-net.c
new file mode 100644 (file)
index 0000000..0bfdb94
--- /dev/null
@@ -0,0 +1,686 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <poll.h>
+
+#include "qdevice-model.h"
+#include "qdevice-model-net.h"
+#include "qdevice-log.h"
+#include "qdevice-net-cast-vote-timer.h"
+#include "qdevice-net-instance.h"
+#include "qdevice-net-ipc-cmd.h"
+#include "qdevice-net-algorithm.h"
+#include "qdevice-net-heuristics.h"
+#include "qdevice-net-poll.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-votequorum.h"
+#include "qnet-config.h"
+#include "nss-sock.h"
+
+int
+qdevice_model_net_init(struct qdevice_instance *instance)
+{
+
+       struct qdevice_net_instance *net_instance;
+
+       qdevice_log(LOG_DEBUG, "Initializing qdevice_net_instance");
+       if (qdevice_net_instance_init_from_cmap(instance) != 0) {
+               return (-1);
+       }
+
+       net_instance = instance->model_data;
+
+       qdevice_log(LOG_DEBUG, "Registering algorithms");
+       if (qdevice_net_algorithm_register_all() != 0) {
+               return (-1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Initializing NSS");
+       if (nss_sock_init_nss((net_instance->tls_supported != TLV_TLS_UNSUPPORTED ?
+           instance->advanced_settings->net_nss_db_dir : NULL)) != 0) {
+               qdevice_log_nss(LOG_ERR, "Can't init nss");
+               return (-1);
+       }
+
+       if (qdevice_net_cast_vote_timer_update(net_instance, TLV_VOTE_ASK_LATER) != 0) {
+               qdevice_log(LOG_ERR, "Can't update cast vote timer");
+               return (-1);
+       }
+
+       if (qdevice_net_algorithm_init(net_instance) != 0) {
+               qdevice_log(LOG_ERR, "Algorithm init failed");
+               return (-1);
+       }
+
+       if (qdevice_net_heuristics_init(net_instance) != 0) {
+               qdevice_log(LOG_ERR, "Can't initialize net heuristics");
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_model_net_destroy(struct qdevice_instance *instance)
+{
+       struct qdevice_net_instance *net_instance;
+
+       net_instance = instance->model_data;
+
+       qdevice_log(LOG_DEBUG, "Destroying algorithm");
+       qdevice_net_algorithm_destroy(net_instance);
+
+       qdevice_log(LOG_DEBUG, "Destroying qdevice_net_instance");
+       qdevice_net_instance_destroy(net_instance);
+
+       qdevice_log(LOG_DEBUG, "Shutting down NSS");
+       SSL_ClearSessionCache();
+
+       if (NSS_Shutdown() != SECSuccess) {
+               qdevice_log_nss(LOG_WARNING, "Can't shutdown NSS");
+       }
+
+       if (PR_Cleanup() != PR_SUCCESS) {
+               qdevice_log_nss(LOG_WARNING, "Can't shutdown NSPR");
+       }
+
+       free(net_instance);
+
+       return (0);
+}
+
+static int
+qdevice_model_net_timer_connect_timeout(void *data1, void *data2)
+{
+       struct qdevice_net_instance *instance;
+
+       instance = (struct qdevice_net_instance *)data1;
+
+       qdevice_log(LOG_ERR, "Connect timeout");
+
+       instance->schedule_disconnect = 1;
+
+       instance->connect_timer = NULL;
+       instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_CONNECT_TO_THE_SERVER;
+
+       return (0);
+}
+
+static PRIntn
+qdevice_model_net_get_af(const struct qdevice_net_instance *instance)
+{
+       PRIntn af;
+
+       af = PR_AF_UNSPEC;
+       if (instance->force_ip_version == 4) {
+               af = PR_AF_INET;
+       }
+
+       if (instance->force_ip_version == 6) {
+               af = PR_AF_INET6;
+       }
+
+       return (af);
+}
+
+int
+qdevice_model_net_run(struct qdevice_instance *instance)
+{
+       struct qdevice_net_instance *net_instance;
+       int try_connect;
+       int res;
+       enum tlv_vote vote;
+       int delay_before_reconnect;
+
+       net_instance = instance->model_data;
+
+       qdevice_log(LOG_DEBUG, "Executing qdevice-net");
+
+       try_connect = 1;
+       while (try_connect) {
+               net_instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT;
+               net_instance->socket = NULL;
+
+               net_instance->connect_timer = timer_list_add(&net_instance->main_timer_list,
+                       net_instance->connect_timeout, qdevice_model_net_timer_connect_timeout,
+                       (void *)net_instance, NULL);
+
+               if (net_instance->connect_timer == NULL) {
+                       qdevice_log(LOG_CRIT, "Can't schedule connect timer");
+
+                       try_connect = 0;
+                       break;
+               }
+
+               qdevice_log(LOG_DEBUG, "Trying connect to qnetd server %s:%u (timeout = %ums)",
+                   net_instance->host_addr, net_instance->host_port, net_instance->connect_timeout);
+
+               res = nss_sock_non_blocking_client_init(net_instance->host_addr,
+                   net_instance->host_port, qdevice_model_net_get_af(net_instance),
+                   &net_instance->non_blocking_client);
+               if (res == -1) {
+                       qdevice_log_nss(LOG_ERR, "Can't initialize non blocking client connection");
+               }
+
+               res = nss_sock_non_blocking_client_try_next(&net_instance->non_blocking_client);
+               if (res == -1) {
+                       qdevice_log_nss(LOG_ERR, "Can't connect to qnetd host");
+                       nss_sock_non_blocking_client_destroy(&net_instance->non_blocking_client);
+               }
+
+               while (qdevice_net_poll(net_instance) == 0) {
+               };
+
+               if (net_instance->connect_timer != NULL) {
+                       timer_list_delete(&net_instance->main_timer_list, net_instance->connect_timer);
+                       net_instance->connect_timer = NULL;
+               }
+
+               if (net_instance->echo_request_timer != NULL) {
+                       timer_list_delete(&net_instance->main_timer_list, net_instance->echo_request_timer);
+                       net_instance->echo_request_timer = NULL;
+               }
+
+               try_connect = qdevice_net_disconnect_reason_try_reconnect(net_instance->disconnect_reason);
+
+               /*
+                * Unpause cast vote timer, because if it is paused we cannot remove tracking
+                */
+               qdevice_net_cast_vote_timer_set_paused(net_instance, 0);
+
+               vote = TLV_VOTE_NO_CHANGE;
+
+               if (qdevice_net_algorithm_disconnected(net_instance,
+                   net_instance->disconnect_reason, &try_connect, &vote) != 0) {
+                       qdevice_log(LOG_ERR, "Algorithm returned error, force exit");
+                       return (-1);
+               } else {
+                       qdevice_log(LOG_DEBUG, "Algorithm result vote is %s",
+                           tlv_vote_to_str(vote));
+               }
+
+               if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
+                       qdevice_log(LOG_ERR, "qdevice_model_net_run fatal error. "
+                           " Can't update cast vote timer vote");
+               }
+
+               if (qdevice_net_disconnect_reason_force_disconnect(net_instance->disconnect_reason)) {
+                       try_connect = 0;
+               }
+
+               if (net_instance->socket != NULL) {
+                       if (PR_Close(net_instance->socket) != PR_SUCCESS) {
+                               qdevice_log_nss(LOG_WARNING, "Unable to close connection");
+                       }
+                       net_instance->socket = NULL;
+               }
+
+               if (!net_instance->non_blocking_client.destroyed) {
+                       nss_sock_non_blocking_client_destroy(&net_instance->non_blocking_client);
+               }
+
+               if (net_instance->non_blocking_client.socket != NULL) {
+                       if (PR_Close(net_instance->non_blocking_client.socket) != PR_SUCCESS) {
+                               qdevice_log_nss(LOG_WARNING, "Unable to close non-blocking client connection");
+                       }
+                       net_instance->non_blocking_client.socket = NULL;
+               }
+
+               if (try_connect &&
+                   net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) {
+                       /*
+                        * Give qnetd server a little time before reconnect
+                        */
+                       delay_before_reconnect = random() %
+                           (int)(net_instance->cast_vote_timer_interval * 0.9);
+
+                       qdevice_log(LOG_DEBUG, "Sleeping for %u ms before reconnect",
+                           delay_before_reconnect);
+                       (void)poll(NULL, 0, delay_before_reconnect);
+               }
+
+               qdevice_net_instance_clean(net_instance);
+       }
+
+       return (0);
+}
+
+/*
+ * Called when cmap reload (or nodelist) was requested.
+ *
+ * nlist is node list
+ * config_version is valid only if config_version_set != 0
+ *
+ * Should return 0 if processing should continue or -1 to call exit
+ */
+int
+qdevice_model_net_config_node_list_changed(struct qdevice_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version)
+{
+       struct qdevice_net_instance *net_instance;
+       int send_node_list;
+       enum tlv_vote vote;
+
+       net_instance = instance->model_data;
+
+       if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               /*
+                * Nodelist changed, but connection to qnetd not initiated yet.
+                */
+               send_node_list = 0;
+
+               if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
+                       vote = TLV_VOTE_NACK;
+               } else {
+                       vote = TLV_VOTE_NO_CHANGE;
+               }
+       } else {
+               send_node_list = 1;
+               vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       if (qdevice_net_algorithm_config_node_list_changed(net_instance, nlist, config_version_set,
+           config_version, &send_node_list, &vote) != 0) {
+               qdevice_log(LOG_ERR, "Algorithm returned error, Disconnecting");
+
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_CONFIG_NODE_LIST_CHANGED_ERR;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm decided to %s node list and result vote is %s",
+                   (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote));
+       }
+
+       if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
+               qdevice_log(LOG_CRIT, "qdevice_model_net_config_node_list_changed fatal error. "
+                               " Can't update cast vote timer vote");
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       }
+
+       if (send_node_list) {
+               if (qdevice_net_send_config_node_list(net_instance, nlist, config_version_set,
+                   config_version, 0) != 0) {
+                       net_instance->schedule_disconnect = 1;
+                       net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+
+                       return (0);
+               }
+       }
+
+       return (0);
+}
+
+/*
+ * Called when cmap reload (or nodelist) was requested, but it was not possible to
+ * get node list.
+ *
+ * Should return 0 if processing should continue or -1 to call exit
+ */
+int
+qdevice_model_net_get_config_node_list_failed(struct qdevice_instance *instance)
+{
+       struct qdevice_net_instance *net_instance;
+
+       net_instance = instance->model_data;
+
+       net_instance->schedule_disconnect = 1;
+       net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+
+       return (0);
+}
+
+int
+qdevice_model_net_votequorum_quorum_notify(struct qdevice_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[])
+{
+       struct qdevice_net_instance *net_instance;
+       int send_node_list;
+       enum tlv_vote vote;
+
+       net_instance = instance->model_data;
+
+       if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               /*
+                * Nodelist changed, but connection to qnetd not initiated yet.
+                */
+               send_node_list = 0;
+
+               if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
+                       vote = TLV_VOTE_NACK;
+               } else {
+                       vote = TLV_VOTE_NO_CHANGE;
+               }
+       } else {
+               send_node_list = 1;
+               vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       if (qdevice_net_algorithm_votequorum_quorum_notify(net_instance, quorate,
+           node_list_entries, node_list, &send_node_list, &vote) != 0) {
+               qdevice_log(LOG_ERR, "Algorithm returned error. Disconnecting.");
+
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_QUORUM_NOTIFY_ERR;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm decided to %s list and result vote is %s",
+                   (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote));
+       }
+
+       if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
+               qdevice_log(LOG_CRIT, "qdevice_model_net_votequorum_quorum_notify fatal error. "
+                               " Can't update cast vote timer vote");
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       }
+
+       if (send_node_list) {
+               if (qdevice_net_send_quorum_node_list(net_instance,
+                   (quorate ? TLV_QUORATE_QUORATE : TLV_QUORATE_INQUORATE),
+                   node_list_entries, node_list) != 0) {
+                       /*
+                        * Fatal error -> schedule disconnect
+                        */
+                       net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+                       net_instance->schedule_disconnect = 1;
+
+                       return (0);
+               }
+       }
+
+       return (0);
+}
+
+int
+qdevice_model_net_votequorum_node_list_heuristics_notify(struct qdevice_instance *instance,
+    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    enum qdevice_heuristics_exec_result heuristics_exec_result)
+{
+       struct qdevice_net_instance *net_instance;
+       struct tlv_ring_id tlv_rid;
+       enum tlv_vote vote;
+       enum tlv_heuristics heuristics;
+       int send_node_list;
+
+       net_instance = instance->model_data;
+
+       qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, &votequorum_ring_id);
+       heuristics = qdevice_net_heuristics_exec_result_to_tlv(heuristics_exec_result);
+
+       if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               /*
+                * Nodelist changed, but connection to qnetd not initiated yet.
+                */
+               send_node_list = 0;
+
+               if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
+                       vote = TLV_VOTE_NACK;
+               } else {
+                       vote = TLV_VOTE_NO_CHANGE;
+               }
+       } else {
+               send_node_list = 1;
+               vote = TLV_VOTE_WAIT_FOR_REPLY;
+       }
+
+       if (qdevice_net_algorithm_votequorum_node_list_heuristics_notify(net_instance, &tlv_rid,
+           node_list_entries, node_list, &send_node_list, &vote, &heuristics) != 0) {
+               qdevice_log(LOG_ERR, "Algorithm returned error. Disconnecting.");
+
+               net_instance->disconnect_reason =
+                   QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_HEURISTICS_NOTIFY_ERR;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm decided to %s list, result vote is %s and heuristics is %s",
+                   (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote),
+                   tlv_heuristics_to_str(heuristics));
+       }
+
+       if (send_node_list) {
+               if (qdevice_net_send_membership_node_list(net_instance, &tlv_rid,
+                   node_list_entries, node_list, heuristics) != 0) {
+                       /*
+                        * Fatal error -> schedule disconnect
+                        */
+                       net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+                       net_instance->schedule_disconnect = 1;
+
+                       return (0);
+               }
+       }
+
+       /*
+        * Unpause cast vote timer
+        */
+       qdevice_net_cast_vote_timer_set_paused(net_instance, 0);
+
+       if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
+               qdevice_log(LOG_CRIT, "qdevice_model_net_votequorum_node_list_notify fatal error "
+                   "Can't update cast vote timer");
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       }
+
+       net_instance->latest_vq_heuristics_result = heuristics;
+       net_instance->latest_heuristics_result = heuristics;
+
+       if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) {
+               return (0);
+       }
+
+       return (0);
+}
+
+int
+qdevice_model_net_votequorum_node_list_notify(struct qdevice_instance *instance,
+    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[])
+{
+       struct qdevice_net_instance *net_instance;
+       struct tlv_ring_id tlv_rid;
+       enum tlv_vote vote;
+       int pause_cast_vote_timer;
+
+       net_instance = instance->model_data;
+
+       /*
+        * Stop regular heuristics till qdevice_model_net_votequorum_node_list_heuristics_notify
+        * is called
+        */
+       if (qdevice_net_heuristics_stop_timer(net_instance) != 0) {
+               return (0);
+       }
+
+       pause_cast_vote_timer = 1;
+       vote = TLV_VOTE_NO_CHANGE;
+
+       if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS &&
+           net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
+               /*
+                * Nodelist changed and vote timer still votes ACK. It's needed to start voting
+                * NACK.
+                */
+               if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
+                       vote = TLV_VOTE_NACK;
+               }
+       }
+
+       qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, &votequorum_ring_id);
+
+       if (qdevice_net_algorithm_votequorum_node_list_notify(net_instance, &tlv_rid,
+           node_list_entries, node_list, &pause_cast_vote_timer, &vote) != 0) {
+               qdevice_log(LOG_ERR, "Algorithm returned error. Disconnecting.");
+
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_NOTIFY_ERR;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm decided to %s cast vote timer and result vote is %s ",
+                   (pause_cast_vote_timer ? "pause" : "not pause"), tlv_vote_to_str(vote));
+       }
+
+       if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
+               qdevice_log(LOG_CRIT, "qdevice_model_net_votequorum_node_list_notify fatal error "
+                   "Can't update cast vote timer");
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       }
+
+       qdevice_net_cast_vote_timer_set_paused(net_instance, pause_cast_vote_timer);
+
+       return (0);
+}
+
+int
+qdevice_model_net_votequorum_expected_votes_notify(struct qdevice_instance *instance,
+    uint32_t expected_votes)
+{
+       struct qdevice_net_instance *net_instance;
+       enum tlv_vote vote;
+
+       net_instance = instance->model_data;
+
+       qdevice_log(LOG_DEBUG, "qdevice_model_net_votequorum_expected_votes_notify"
+           " (expected votes old=%"PRIu32" / new=%"PRIu32")",
+           net_instance->qdevice_instance_ptr->vq_expected_votes, expected_votes);
+
+       vote = TLV_VOTE_NO_CHANGE;
+
+       if (qdevice_net_algorithm_votequorum_expected_votes_notify(net_instance, expected_votes,
+           &vote) != 0) {
+               qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting.");
+
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_EXPECTED_VOTES_NOTIFY_ERR;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(vote));
+       }
+
+       if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
+               qdevice_log(LOG_CRIT, "qdevice_model_net_votequorum_expected_votes_notify fatal error. "
+                               " Can't update cast vote timer vote");
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       }
+
+       return (0);
+}
+
+int
+qdevice_model_net_cmap_changed(struct qdevice_instance *instance,
+    const struct qdevice_cmap_change_events *events)
+{
+       struct qdevice_net_instance *net_instance;
+       enum qdevice_heuristics_mode active_heuristics_mode;
+       int heuristics_enabled;
+
+       net_instance = instance->model_data;
+
+       if (events->heuristics) {
+               active_heuristics_mode = instance->heuristics_instance.mode;
+               heuristics_enabled = (active_heuristics_mode == QDEVICE_HEURISTICS_MODE_ENABLED ||
+                   active_heuristics_mode == QDEVICE_HEURISTICS_MODE_SYNC);
+
+               if (net_instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS &&
+                   !net_instance->server_supports_heuristics && heuristics_enabled) {
+                       qdevice_log(LOG_ERR, "Heuristics are enabled but not supported by the server");
+
+                       net_instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT;
+
+                       net_instance->schedule_disconnect = 1;
+
+                       return (0);
+               }
+
+               if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) {
+                       return (0);
+               }
+       }
+
+       return (0);
+}
+
+int
+qdevice_model_net_ipc_cmd_status(struct qdevice_instance *instance,
+    struct dynar *outbuf, int verbose)
+{
+       struct qdevice_net_instance *net_instance;
+
+       net_instance = instance->model_data;
+
+       if (!qdevice_net_ipc_cmd_status(net_instance, outbuf, verbose)) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+static struct qdevice_model qdevice_model_net = {
+       .name                                   = "net",
+       .init                                   = qdevice_model_net_init,
+       .destroy                                = qdevice_model_net_destroy,
+       .run                                    = qdevice_model_net_run,
+       .get_config_node_list_failed            = qdevice_model_net_get_config_node_list_failed,
+       .config_node_list_changed               = qdevice_model_net_config_node_list_changed,
+       .votequorum_quorum_notify               = qdevice_model_net_votequorum_quorum_notify,
+       .votequorum_node_list_notify            = qdevice_model_net_votequorum_node_list_notify,
+       .votequorum_node_list_heuristics_notify = qdevice_model_net_votequorum_node_list_heuristics_notify,
+       .votequorum_expected_votes_notify       = qdevice_model_net_votequorum_expected_votes_notify,
+       .cmap_changed                           = qdevice_model_net_cmap_changed,
+       .ipc_cmd_status                         = qdevice_model_net_ipc_cmd_status,
+};
+
+int
+qdevice_model_net_register(void)
+{
+       return (qdevice_model_register(QDEVICE_MODEL_TYPE_NET, &qdevice_model_net));
+}
diff --git a/qdevices/qdevice-model-net.h b/qdevices/qdevice-model-net.h
new file mode 100644 (file)
index 0000000..199205c
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_MODEL_NET_H_
+#define _QDEVICE_MODEL_NET_H_
+
+#include "qdevice-instance.h"
+#include "qdevice-model.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_model_net_init(struct qdevice_instance *instance);
+
+extern int     qdevice_model_net_destroy(struct qdevice_instance *instance);
+
+extern int     qdevice_model_net_run(struct qdevice_instance *instance);
+
+extern int     qdevice_model_net_get_config_node_list_failed(struct qdevice_instance *instance);
+
+extern int     qdevice_model_net_config_node_list_changed(struct qdevice_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version);
+
+extern int     qdevice_model_net_votequorum_quorum_notify(struct qdevice_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[]);
+
+extern int     qdevice_model_net_votequorum_node_list_notify(struct qdevice_instance *instance,
+    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[]);
+
+extern int     qdevice_model_net_votequorum_node_list_heuristics_notify(
+    struct qdevice_instance *instance,
+    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    enum qdevice_heuristics_exec_result heuristics_exec_result);
+
+extern int     qdevice_model_net_votequorum_expected_votes_notify(struct qdevice_instance *instance,
+    uint32_t expected_votes);
+
+extern int      qdevice_model_net_cmap_changed(struct qdevice_instance *instance,
+    const struct qdevice_cmap_change_events *events);
+
+extern int     qdevice_model_net_ipc_cmd_status(struct qdevice_instance *instance,
+    struct dynar *outbuf, int verbose);
+
+extern int     qdevice_model_net_register(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_MODEL_NET_H_ */
diff --git a/qdevices/qdevice-model-type.h b/qdevices/qdevice-model-type.h
new file mode 100644 (file)
index 0000000..f133ebd
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_MODEL_TYPE_H_
+#define _QDEVICE_MODEL_TYPE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum qdevice_model_type {
+       QDEVICE_MODEL_TYPE_NET = 0,
+       QDEVICE_MODEL_TYPE_ARRAY_SIZE,
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_MODEL_TYPE_H_ */
diff --git a/qdevices/qdevice-model.c b/qdevices/qdevice-model.c
new file mode 100644 (file)
index 0000000..1af887f
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-log.h"
+#include "qdevice-model.h"
+#include "qdevice-model-net.h"
+
+static struct qdevice_model *qdevice_model_array[QDEVICE_MODEL_TYPE_ARRAY_SIZE];
+
+int
+qdevice_model_init(struct qdevice_instance *instance)
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_init unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->init(instance));
+}
+
+int
+qdevice_model_destroy(struct qdevice_instance *instance)
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_destroy unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->destroy(instance));
+}
+
+int
+qdevice_model_run(struct qdevice_instance *instance)
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_run unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->run(instance));
+}
+
+int
+qdevice_model_get_config_node_list_failed(struct qdevice_instance *instance)
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_run unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->get_config_node_list_failed(instance));
+}
+
+int
+qdevice_model_config_node_list_changed(struct qdevice_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version)
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_run unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->
+           config_node_list_changed(instance, nlist, config_version_set, config_version));
+}
+
+int
+qdevice_model_votequorum_quorum_notify(struct qdevice_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[])
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_votequorum_quorum_notify unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->
+           votequorum_quorum_notify(instance, quorate, node_list_entries, node_list));
+}
+
+int
+qdevice_model_votequorum_node_list_notify(struct qdevice_instance *instance,
+    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[])
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_votequorum_node_list_notify unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->
+           votequorum_node_list_notify(instance, votequorum_ring_id, node_list_entries, node_list));
+}
+
+int
+qdevice_model_votequorum_node_list_heuristics_notify(struct qdevice_instance *instance,
+    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    enum qdevice_heuristics_exec_result heuristics_exec_result)
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_votequorum_node_list_heuristics_notify unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->
+           votequorum_node_list_heuristics_notify(instance, votequorum_ring_id, node_list_entries,
+               node_list, heuristics_exec_result));
+}
+
+int
+qdevice_model_votequorum_expected_votes_notify(struct qdevice_instance *instance,
+    uint32_t expected_votes)
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_votequorum_expected_votes_notify unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->
+           votequorum_expected_votes_notify(instance, expected_votes));
+}
+
+int
+qdevice_model_ipc_cmd_status(struct qdevice_instance *instance, struct dynar *outbuf, int verbose)
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_ipc_cmd_status unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->
+           ipc_cmd_status(instance, outbuf, verbose));
+}
+
+int
+qdevice_model_cmap_changed(struct qdevice_instance *instance,
+    const struct qdevice_cmap_change_events *events)
+{
+
+       if (instance->model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE ||
+           qdevice_model_array[instance->model_type] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_model_cmap_chaged unhandled model");
+               exit(1);
+       }
+
+       return (qdevice_model_array[instance->model_type]->
+           cmap_changed(instance, events));
+}
+
+int
+qdevice_model_register(enum qdevice_model_type model_type,
+    struct qdevice_model *model)
+{
+
+       if (model_type >= QDEVICE_MODEL_TYPE_ARRAY_SIZE) {
+               return (-1);
+       }
+
+       if (qdevice_model_array[model_type] != NULL) {
+               return (-1);
+       }
+
+       qdevice_model_array[model_type] = model;
+
+       return (0);
+}
+
+void
+qdevice_model_register_all(void)
+{
+
+       if (qdevice_model_net_register() != 0) {
+               qdevice_log(LOG_CRIT, "Failed to register model 'net' ");
+               exit(1);
+       }
+}
+
+int
+qdevice_model_str_to_type(const char *str, enum qdevice_model_type *model_type)
+{
+       int i;
+
+       for (i = 0; i < QDEVICE_MODEL_TYPE_ARRAY_SIZE; i++) {
+               if (qdevice_model_array[i] != NULL &&
+                   strcmp(qdevice_model_array[i]->name, str) == 0) {
+                       *model_type = i;
+
+                       return (0);
+               }
+       }
+
+       return (-1);
+}
+
+const char *
+qdevice_model_type_to_str(enum qdevice_model_type model_type)
+{
+
+       switch (model_type) {
+       case QDEVICE_MODEL_TYPE_NET: return ("Net"); break;
+       case QDEVICE_MODEL_TYPE_ARRAY_SIZE: return ("Unknown model"); break;
+       /*
+        * Default is not defined intentionally. Compiler shows warning when new model is added
+        */
+       }
+
+       return ("Unknown model");
+}
diff --git a/qdevices/qdevice-model.h b/qdevices/qdevice-model.h
new file mode 100644 (file)
index 0000000..6afa5f9
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_MODEL_H_
+#define _QDEVICE_MODEL_H_
+
+#include "qdevice-cmap.h"
+#include "qdevice-instance.h"
+#include "qdevice-model-type.h"
+#include "qdevice-heuristics-exec-result.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_model_init(struct qdevice_instance *instance);
+
+extern int     qdevice_model_destroy(struct qdevice_instance *instance);
+
+extern int     qdevice_model_run(struct qdevice_instance *instance);
+
+extern int     qdevice_model_get_config_node_list_failed(struct qdevice_instance *instance);
+
+extern int     qdevice_model_config_node_list_changed(struct qdevice_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version);
+
+extern int     qdevice_model_votequorum_quorum_notify(struct qdevice_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[]);
+
+extern int     qdevice_model_votequorum_node_list_notify(struct qdevice_instance *instance,
+    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[]);
+
+extern int     qdevice_model_votequorum_node_list_heuristics_notify(struct qdevice_instance *instance,
+    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    enum qdevice_heuristics_exec_result heuristics_exec_result);
+
+extern int     qdevice_model_votequorum_expected_votes_notify(struct qdevice_instance *instance,
+    uint32_t expected_votes);
+
+extern int     qdevice_model_ipc_cmd_status(struct qdevice_instance *instance,
+    struct dynar *outbuf, int verbose);
+
+extern int     qdevice_model_cmap_changed(struct qdevice_instance *instance,
+    const struct qdevice_cmap_change_events *events);
+
+struct qdevice_model {
+       const char *name;
+       int (*init)(struct qdevice_instance *instance);
+       int (*destroy)(struct qdevice_instance *instance);
+       int (*run)(struct qdevice_instance *instance);
+       int (*get_config_node_list_failed)(struct qdevice_instance *instance);
+       int (*config_node_list_changed)(struct qdevice_instance *instance,
+           const struct node_list *nlist, int config_version_set, uint64_t config_version);
+       int (*votequorum_quorum_notify)(struct qdevice_instance *instance,
+           uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[]);
+       int (*votequorum_node_list_notify)(struct qdevice_instance *instance,
+           votequorum_ring_id_t votequorum_ring_id,uint32_t node_list_entries,
+           uint32_t node_list[]);
+       int (*votequorum_node_list_heuristics_notify)(struct qdevice_instance *instance,
+           votequorum_ring_id_t votequorum_ring_id,uint32_t node_list_entries,
+           uint32_t node_list[], enum qdevice_heuristics_exec_result heuristics_exec_result);
+       int (*votequorum_expected_votes_notify)(struct qdevice_instance *instance,
+           uint32_t expected_votes);
+       int (*ipc_cmd_status)(struct qdevice_instance *instance, struct dynar *outbuf, int verbose);
+       int (*cmap_changed)(struct qdevice_instance *instance,
+           const struct qdevice_cmap_change_events *events);
+};
+
+extern int              qdevice_model_register(
+    enum qdevice_model_type model_type, struct qdevice_model *model);
+
+extern void qdevice_model_register_all(void);
+
+extern int              qdevice_model_str_to_type(const char *str,
+    enum qdevice_model_type *model_type);
+
+extern const char      *qdevice_model_type_to_str(enum qdevice_model_type model_type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_MODEL_H_ */
diff --git a/qdevices/qdevice-net-algo-2nodelms.c b/qdevices/qdevice-net-algo-2nodelms.c
new file mode 100644 (file)
index 0000000..e64a702
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "qdevice-net-algo-2nodelms.h"
+#include "qdevice-log.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-cast-vote-timer.h"
+
+int
+qdevice_net_algo_2nodelms_init(struct qdevice_net_instance *instance)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_connected(struct qdevice_net_instance *instance, enum tlv_heuristics *heuristics,
+    int *send_config_node_list, int *send_membership_node_list, int *send_quorum_node_list, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_config_node_list_changed(struct qdevice_net_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version,
+    int *send_node_list, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_votequorum_node_list_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *pause_cast_vote_timer, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_votequorum_quorum_notify(struct qdevice_net_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list,
+    enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_votequorum_expected_votes_notify(struct qdevice_net_instance *instance,
+    uint32_t expected_votes, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_config_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_membership_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_quorum_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_ask_for_vote_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_echo_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int is_expected_seq_number)
+{
+
+       return (is_expected_seq_number ? 0 : -1);
+}
+
+int
+qdevice_net_algo_2nodelms_echo_reply_not_received(struct qdevice_net_instance *instance)
+{
+
+       return (-1);
+}
+
+int
+qdevice_net_algo_2nodelms_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_heuristics heuristics, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_2nodelms_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+void
+qdevice_net_algo_2nodelms_destroy(struct qdevice_net_instance *instance)
+{
+
+}
+
+static struct qdevice_net_algorithm qdevice_net_algo_2nodelms = {
+       .init                                   = qdevice_net_algo_2nodelms_init,
+       .connected                              = qdevice_net_algo_2nodelms_connected,
+       .config_node_list_changed               = qdevice_net_algo_2nodelms_config_node_list_changed,
+       .votequorum_node_list_notify            = qdevice_net_algo_2nodelms_votequorum_node_list_notify,
+       .votequorum_node_list_heuristics_notify = qdevice_net_algo_2nodelms_votequorum_node_list_heuristics_notify,
+       .votequorum_quorum_notify               = qdevice_net_algo_2nodelms_votequorum_quorum_notify,
+       .votequorum_expected_votes_notify       = qdevice_net_algo_2nodelms_votequorum_expected_votes_notify,
+       .config_node_list_reply_received        = qdevice_net_algo_2nodelms_config_node_list_reply_received,
+       .membership_node_list_reply_received    = qdevice_net_algo_2nodelms_membership_node_list_reply_received,
+       .quorum_node_list_reply_received        = qdevice_net_algo_2nodelms_quorum_node_list_reply_received,
+       .ask_for_vote_reply_received            = qdevice_net_algo_2nodelms_ask_for_vote_reply_received,
+       .vote_info_received                     = qdevice_net_algo_2nodelms_vote_info_received,
+       .echo_reply_received                    = qdevice_net_algo_2nodelms_echo_reply_received,
+       .echo_reply_not_received                = qdevice_net_algo_2nodelms_echo_reply_not_received,
+       .heuristics_change                      = qdevice_net_algo_2nodelms_heuristics_change,
+       .heuristics_change_reply_received       = qdevice_net_algo_2nodelms_heuristics_change_reply_received,
+       .disconnected                           = qdevice_net_algo_2nodelms_disconnected,
+       .destroy                                = qdevice_net_algo_2nodelms_destroy,
+};
+
+int
+qdevice_net_algo_2nodelms_register(void)
+{
+       return (qdevice_net_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_2NODELMS, &qdevice_net_algo_2nodelms));
+}
diff --git a/qdevices/qdevice-net-algo-2nodelms.h b/qdevices/qdevice-net-algo-2nodelms.h
new file mode 100644 (file)
index 0000000..c76e606
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_ALGO_2NODELMS_H_
+#define _QDEVICE_NET_ALGO_2NODELMS_H_
+
+#include "qdevice-net-algorithm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_net_algo_2nodelms_init(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_2nodelms_connected(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list,
+    int *send_quorum_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_config_node_list_changed(
+    struct qdevice_net_instance *instance, const struct node_list *nlist,
+    int config_version_set, uint64_t config_version, int *send_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_votequorum_node_list_notify(
+    struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], int *pause_cast_vote_timer,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_votequorum_node_list_heuristics_notify(
+    struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], int *send_node_list, enum tlv_vote *vote,
+    enum tlv_heuristics *heuristics);
+
+extern int     qdevice_net_algo_2nodelms_votequorum_quorum_notify(
+    struct qdevice_net_instance *instance, uint32_t quorate, uint32_t node_list_entries,
+    votequorum_node_t node_list[], int *send_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_votequorum_expected_votes_notify(
+    struct qdevice_net_instance *instance, uint32_t expected_votes, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_config_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, int initial,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_membership_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, const struct tlv_ring_id *ring_id,
+    int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_quorum_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_ask_for_vote_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_echo_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, int is_expected_seq_number);
+
+extern int     qdevice_net_algo_2nodelms_echo_reply_not_received(
+    struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_2nodelms_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_2nodelms_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote);
+
+extern void    qdevice_net_algo_2nodelms_destroy(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_2nodelms_register(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_ALGO_2NODELMS_H_ */
diff --git a/qdevices/qdevice-net-algo-ffsplit.c b/qdevices/qdevice-net-algo-ffsplit.c
new file mode 100644 (file)
index 0000000..cc16194
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "qdevice-net-algo-ffsplit.h"
+#include "qdevice-log.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-cast-vote-timer.h"
+#include "qdevice-votequorum.h"
+#include "utils.h"
+
+static int
+check_vqinfo_validity(struct qdevice_net_instance *instance)
+{
+       struct qdevice_instance *qdev_instance;
+       struct votequorum_info vq_info;
+       cs_error_t cs_res;
+       struct node_list_entry *node;
+       uint32_t node_id;
+
+       qdev_instance = instance->qdevice_instance_ptr;
+
+       TAILQ_FOREACH(node, &qdev_instance->config_node_list, entries) {
+               node_id = node->node_id;
+
+               cs_res = votequorum_getinfo(qdev_instance->votequorum_handle, node_id, &vq_info);
+
+               if (cs_res == CS_ERR_NOT_EXIST) {
+                       continue;
+               } else if (cs_res != CS_OK) {
+                       qdevice_log(LOG_CRIT, "Can't get votequorum information for node "
+                           UTILS_PRI_NODE_ID ". Error %s", node_id, cs_strerror(cs_res));
+
+                       return (-1);
+               }
+
+               if (vq_info.node_votes != 1) {
+                       qdevice_log(LOG_CRIT, "50:50 split algorithm works only if all nodes have "
+                           "exactly 1 vote. Node " UTILS_PRI_NODE_ID " has %u votes!",
+                           node_id, vq_info.node_votes);
+
+                       return (-1);
+               }
+
+               if (vq_info.qdevice_votes != 1) {
+                       qdevice_log(LOG_CRIT, "50:50 split algorithm works only if qdevice has "
+                           "exactly 1 vote. Node "UTILS_PRI_NODE_ID" has %u votes!",
+                           node_id, vq_info.qdevice_votes);
+
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+static int
+check_cmap_validity(struct qdevice_net_instance *instance)
+{
+       struct qdevice_instance *qdev_instance;
+       uint32_t qdevice_votes;
+
+       qdev_instance = instance->qdevice_instance_ptr;
+
+       if (cmap_get_uint32(qdev_instance->cmap_handle, "quorum.device.votes", &qdevice_votes) != CS_OK ||
+           qdevice_votes != 1) {
+               qdevice_log(LOG_CRIT, "50:50 split algorithm works only if quorum.device.votes"
+                   " configuration key is set to 1!");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_init(struct qdevice_net_instance *instance)
+{
+
+       if (check_cmap_validity(instance) != 0 ||
+           check_vqinfo_validity(instance) != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_connected(struct qdevice_net_instance *instance, enum tlv_heuristics *heuristics,
+    int *send_config_node_list, int *send_membership_node_list, int *send_quorum_node_list, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_config_node_list_changed(struct qdevice_net_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version,
+    int *send_node_list, enum tlv_vote *vote)
+{
+
+       if (check_vqinfo_validity(instance) != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_votequorum_node_list_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *pause_cast_vote_timer, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_votequorum_quorum_notify(struct qdevice_net_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list,
+    enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_votequorum_expected_votes_notify(struct qdevice_net_instance *instance,
+    uint32_t expected_votes, enum tlv_vote *vote)
+{
+
+       if (check_vqinfo_validity(instance) != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_config_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_membership_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_quorum_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_ask_for_vote_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_echo_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int is_expected_seq_number)
+{
+
+       return (is_expected_seq_number ? 0 : -1);
+}
+
+int
+qdevice_net_algo_ffsplit_echo_reply_not_received(struct qdevice_net_instance *instance)
+{
+
+       return (-1);
+}
+
+int
+qdevice_net_algo_ffsplit_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_heuristics heuristics, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_ffsplit_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote)
+{
+
+       /*
+        * We cannot depend on default behavior (until there is no change -> use old vote).
+        * This could create two quorate clusters (2:2 -> first half get ACK -> first half
+        * disconnects from qnetd -> second half get ACK -> two quorate clusters)
+        */
+       *vote = TLV_VOTE_NACK;
+
+       return (0);
+}
+
+void
+qdevice_net_algo_ffsplit_destroy(struct qdevice_net_instance *instance)
+{
+
+}
+
+static struct qdevice_net_algorithm qdevice_net_algo_ffsplit = {
+       .init                                   = qdevice_net_algo_ffsplit_init,
+       .connected                              = qdevice_net_algo_ffsplit_connected,
+       .config_node_list_changed               = qdevice_net_algo_ffsplit_config_node_list_changed,
+       .votequorum_node_list_notify            = qdevice_net_algo_ffsplit_votequorum_node_list_notify,
+       .votequorum_node_list_heuristics_notify = qdevice_net_algo_ffsplit_votequorum_node_list_heuristics_notify,
+       .votequorum_quorum_notify               = qdevice_net_algo_ffsplit_votequorum_quorum_notify,
+       .votequorum_expected_votes_notify       = qdevice_net_algo_ffsplit_votequorum_expected_votes_notify,
+       .config_node_list_reply_received        = qdevice_net_algo_ffsplit_config_node_list_reply_received,
+       .membership_node_list_reply_received    = qdevice_net_algo_ffsplit_membership_node_list_reply_received,
+       .quorum_node_list_reply_received        = qdevice_net_algo_ffsplit_quorum_node_list_reply_received,
+       .ask_for_vote_reply_received            = qdevice_net_algo_ffsplit_ask_for_vote_reply_received,
+       .vote_info_received                     = qdevice_net_algo_ffsplit_vote_info_received,
+       .echo_reply_received                    = qdevice_net_algo_ffsplit_echo_reply_received,
+       .echo_reply_not_received                = qdevice_net_algo_ffsplit_echo_reply_not_received,
+       .heuristics_change                      = qdevice_net_algo_ffsplit_heuristics_change,
+       .heuristics_change_reply_received       = qdevice_net_algo_ffsplit_heuristics_change_reply_received,
+       .disconnected                           = qdevice_net_algo_ffsplit_disconnected,
+       .destroy                                = qdevice_net_algo_ffsplit_destroy,
+};
+
+int
+qdevice_net_algo_ffsplit_register(void)
+{
+       return (qdevice_net_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_FFSPLIT, &qdevice_net_algo_ffsplit));
+}
diff --git a/qdevices/qdevice-net-algo-ffsplit.h b/qdevices/qdevice-net-algo-ffsplit.h
new file mode 100644 (file)
index 0000000..e47a35c
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_ALGO_FFSPLIT_H_
+#define _QDEVICE_NET_ALGO_FFSPLIT_H_
+
+#include "qdevice-net-algorithm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_net_algo_ffsplit_init(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_ffsplit_connected(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list,
+    int *send_quorum_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_config_node_list_changed(
+    struct qdevice_net_instance *instance, const struct node_list *nlist,
+    int config_version_set, uint64_t config_version, int *send_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_votequorum_node_list_notify(
+    struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], int *pause_cast_vote_timer,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_votequorum_node_list_heuristics_notify(
+    struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], int *send_node_list, enum tlv_vote *vote,
+    enum tlv_heuristics *heuristics);
+
+extern int     qdevice_net_algo_ffsplit_votequorum_quorum_notify(
+    struct qdevice_net_instance *instance, uint32_t quorate, uint32_t node_list_entries,
+    votequorum_node_t node_list[], int *send_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_votequorum_expected_votes_notify(
+    struct qdevice_net_instance *instance, uint32_t expected_votes, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_config_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, int initial,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_membership_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, const struct tlv_ring_id *ring_id,
+    int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_quorum_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_ask_for_vote_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_echo_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, int is_expected_seq_number);
+
+extern int     qdevice_net_algo_ffsplit_echo_reply_not_received(
+    struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_ffsplit_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_ffsplit_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote);
+
+extern void    qdevice_net_algo_ffsplit_destroy(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_ffsplit_register(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_ALGO_FFSPLIT_H_ */
diff --git a/qdevices/qdevice-net-algo-lms.c b/qdevices/qdevice-net-algo-lms.c
new file mode 100644 (file)
index 0000000..eab0c4c
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "qdevice-net-algo-lms.h"
+#include "qdevice-log.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-cast-vote-timer.h"
+
+struct algo_lms_instance_data {
+       uint32_t quorate;
+       uint8_t have_wfa;
+       enum tlv_vote vote;
+};
+
+int
+qdevice_net_algo_lms_init(struct qdevice_net_instance *instance)
+{
+       struct algo_lms_instance_data *data;
+       int res;
+
+       data = malloc(sizeof(struct algo_lms_instance_data));
+       if (!data) {
+               return (-1);
+       }
+       instance->algorithm_data = data;
+
+       data->quorate = 0;
+       data->vote = TLV_VOTE_ASK_LATER;
+       res = cmap_get_uint8(instance->qdevice_instance_ptr->cmap_handle, "quorum.wait_for_all", &data->have_wfa);
+       if (res != CS_OK) {
+               qdevice_log(LOG_DEBUG, "algo-lms: Can't get WFA res = %d", res);
+               data->have_wfa = 0;
+       }
+
+       qdevice_log(LOG_DEBUG, "algo-lms: initialised. WFA = %d", data->have_wfa);
+       return (0);
+}
+
+
+int
+qdevice_net_algo_lms_connected(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list,
+    int *send_quorum_node_list, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_config_node_list_changed(struct qdevice_net_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version,
+    int *send_node_list, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_votequorum_node_list_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *pause_cast_vote_timer, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_votequorum_quorum_notify(struct qdevice_net_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list,
+    enum tlv_vote *vote)
+{
+       struct algo_lms_instance_data *data = instance->algorithm_data;
+
+       data->quorate = quorate;
+       qdevice_log(LOG_DEBUG, "algo-lms: quorum_notify. quorate = %d", data->quorate);
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_config_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_membership_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_quorum_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_votequorum_expected_votes_notify(struct qdevice_net_instance *instance,
+    uint32_t expected_votes, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_ask_for_vote_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_echo_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int is_expected_seq_number)
+{
+
+       return (is_expected_seq_number ? 0 : -1);
+}
+
+int
+qdevice_net_algo_lms_echo_reply_not_received(struct qdevice_net_instance *instance)
+{
+       struct algo_lms_instance_data *data = instance->algorithm_data;
+
+       qdevice_log(LOG_DEBUG, "algo-lms: echo_not_recvd. quorate = %d, WFA = %d", data->quorate, data->have_wfa);
+
+       /* qnetd server is disconnected, if we were already quorate AND WFA is enabled
+          then we can continue to provide our vote.
+          Otherwise ... no
+       */
+       if (data->quorate && data->have_wfa) {
+               return (0);
+       }
+
+       return (-1);
+}
+
+int
+qdevice_net_algo_lms_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote)
+{
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_heuristics heuristics, enum tlv_vote *vote)
+{
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_algo_lms_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote)
+{
+       struct algo_lms_instance_data *data = instance->algorithm_data;
+
+       qdevice_log(LOG_DEBUG, "algo-lms: disconnected. quorate = %d, WFA = %d", data->quorate, data->have_wfa);
+       qdevice_log(LOG_DEBUG, "algo-lms: disconnected. reason = %d, WFA = %d", disconnect_reason, data->have_wfa);
+
+       if (!data->quorate || !data->have_wfa) {
+               *vote = TLV_VOTE_NACK;
+       }
+       *try_reconnect = 1;
+       return (0);
+}
+
+void
+qdevice_net_algo_lms_destroy(struct qdevice_net_instance *instance)
+{
+       free(instance->algorithm_data);
+}
+
+static struct qdevice_net_algorithm qdevice_net_algo_lms = {
+       .init                                   = qdevice_net_algo_lms_init,
+       .connected                              = qdevice_net_algo_lms_connected,
+       .config_node_list_changed               = qdevice_net_algo_lms_config_node_list_changed,
+       .votequorum_node_list_notify            = qdevice_net_algo_lms_votequorum_node_list_notify,
+       .votequorum_node_list_heuristics_notify = qdevice_net_algo_lms_votequorum_node_list_heuristics_notify,
+       .votequorum_quorum_notify               = qdevice_net_algo_lms_votequorum_quorum_notify,
+       .votequorum_expected_votes_notify       = qdevice_net_algo_lms_votequorum_expected_votes_notify,
+       .config_node_list_reply_received        = qdevice_net_algo_lms_config_node_list_reply_received,
+       .membership_node_list_reply_received    = qdevice_net_algo_lms_membership_node_list_reply_received,
+       .quorum_node_list_reply_received        = qdevice_net_algo_lms_quorum_node_list_reply_received,
+       .ask_for_vote_reply_received            = qdevice_net_algo_lms_ask_for_vote_reply_received,
+       .vote_info_received                     = qdevice_net_algo_lms_vote_info_received,
+       .echo_reply_received                    = qdevice_net_algo_lms_echo_reply_received,
+       .echo_reply_not_received                = qdevice_net_algo_lms_echo_reply_not_received,
+       .heuristics_change                      = qdevice_net_algo_lms_heuristics_change,
+       .heuristics_change_reply_received       = qdevice_net_algo_lms_heuristics_change_reply_received,
+       .disconnected                           = qdevice_net_algo_lms_disconnected,
+       .destroy                                = qdevice_net_algo_lms_destroy,
+};
+
+int
+qdevice_net_algo_lms_register(void)
+{
+       return (qdevice_net_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_LMS, &qdevice_net_algo_lms));
+}
diff --git a/qdevices/qdevice-net-algo-lms.h b/qdevices/qdevice-net-algo-lms.h
new file mode 100644 (file)
index 0000000..f4bcd7e
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_ALGO_LMS_H_
+#define _QDEVICE_NET_ALGO_LMS_H_
+
+#include "qdevice-net-algorithm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_net_algo_lms_init(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_lms_connected(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list,
+    int *send_quorum_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_config_node_list_changed(
+    struct qdevice_net_instance *instance, const struct node_list *nlist,
+    int config_version_set, uint64_t config_version, int *send_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_votequorum_node_list_notify(
+    struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], int *pause_cast_vote_timer,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_votequorum_node_list_heuristics_notify(
+    struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], int *send_node_list, enum tlv_vote *vote,
+    enum tlv_heuristics *heuristics);
+
+extern int     qdevice_net_algo_lms_votequorum_quorum_notify(
+    struct qdevice_net_instance *instance, uint32_t quorate, uint32_t node_list_entries,
+    votequorum_node_t node_list[], int *send_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_votequorum_expected_votes_notify(
+    struct qdevice_net_instance *instance, uint32_t expected_votes, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_config_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, int initial,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_membership_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, const struct tlv_ring_id *ring_id,
+    int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_quorum_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_ask_for_vote_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_echo_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, int is_expected_seq_number);
+
+extern int     qdevice_net_algo_lms_echo_reply_not_received(
+    struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_lms_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_lms_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote);
+
+extern void    qdevice_net_algo_lms_destroy(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_lms_register(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_ALGO_LMS_H_ */
diff --git a/qdevices/qdevice-net-algo-test.c b/qdevices/qdevice-net-algo-test.c
new file mode 100644 (file)
index 0000000..9955bf5
--- /dev/null
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "qdevice-net-algo-test.h"
+#include "qdevice-log.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-cast-vote-timer.h"
+
+/*
+ * Called after qdevice_net_instance is initialized. Connection to server is not yet
+ * established. Used mainly for allocating instance->algorithm_data.
+ *
+ * Callback should return 0 on success or -1 on failure.
+ */
+int
+qdevice_net_algo_test_init(struct qdevice_net_instance *instance)
+{
+
+       instance->algorithm_data = NULL;
+       qdevice_log(LOG_INFO, "algo-test: Initialized");
+
+       return (0);
+}
+
+/*
+ * Called after qdevice connected to qnetd.
+ * send_config_node_list, send_membership_node_list and send_quorum_node_list can be set to
+ * nonzero (default) to make qdevice-net send given lists to qnetd
+ * vote (default TLV_VOTE_WAIT_FOR_REPLY) can be set to update voting timer
+ * heuristics is result of pre connect heuristics and can be updated
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_connected(struct qdevice_net_instance *instance, enum tlv_heuristics *heuristics,
+    int *send_config_node_list, int *send_membership_node_list, int *send_quorum_node_list, enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Connected");
+
+       return (0);
+}
+
+/*
+ * Called after config node list changed.
+ *
+ * Callback can override send_node_list and vote.
+ * Depending on net_instance->state, they are set acordingly:
+ * If net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS
+ *   send_node_list = 0
+ *   if cast_vote_timer_vote != TLV_VOTE_ACK
+ *     vote = TLV_VOTE_NO_CHANGE
+ *   if cast_vote_timer_vote = TLV_VOTE_ACK
+ *     vote = TLV_VOTE_NACK.
+ * Otherwise send_node_list = 1 and vote = TLV_VOTE_NO_CHANGE
+ * If send_node_list is set to non zero, node list is sent to qnetd
+ */
+int
+qdevice_net_algo_test_config_node_list_changed(struct qdevice_net_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version,
+    int *send_node_list, enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Config node list changed");
+
+       return (0);
+}
+
+/*
+ * Called after votequorum node list notify is dispatched but heuristics result is not
+ * yet known (heuristics were just executed). Full list together with result of heuristics
+ * are received in qdevice_net_algo_test_votequorum_node_list_heuristics_notify.
+ * This applies also if heuristics are disabled.
+ *
+ * pause_cast_vote_timer is preset to 1. If after callback this value is still != 0,
+ * cast vote timer is paused until heuristics finishes.
+ *
+ * vote is by default set to TLV_VOTE_NO_CHANGE with exception of situation when
+ *   net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS and
+ *   cast_vote_timer_vote = TLV_VOTE_ACK
+ * then vote is set to TLV_VOTE_NACK. This is to allow qnetd disconnection and still vote
+ * ACK as long as no change happens.
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_votequorum_node_list_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *pause_cast_vote_timer, enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Votequorum list notify");
+
+       return (0);
+}
+
+/*
+ * Called after votequorum node list notify is dispatched and heuristics result is known.
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ *
+ * If net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS
+ *   send_node_list = 0
+ *   if cast_vote_timer_vote != TLV_VOTE_ACK
+ *     vote = TLV_VOTE_NO_CHANGE
+ *   if cast_vote_timer_vote = TLV_VOTE_ACK
+ *     vote = TLV_VOTE_NACK.
+ * Otherwise send_node_list = 1 and vote = TLV_VOTE_WAIT_FOR_REPLY
+ * If send_node_list is set to non zero, node list is sent to qnetd
+ * heuristics results can be used (and overwrite)
+ */
+int
+qdevice_net_algo_test_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Votequorum heuristics list notify");
+
+       return (0);
+}
+
+/*
+ * Called after votequorum quorum notify is dispatched.
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ *
+ * Callback can override send_node_list and vote.
+ * Depending on net_instance->state, they are set acordingly:
+ * If net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS
+ *   send_node_list = 0
+ *   if cast_vote_timer_vote != TLV_VOTE_ACK
+ *     vote = TLV_VOTE_NO_CHANGE
+ *   if cast_vote_timer_vote = TLV_VOTE_ACK
+ *     vote = TLV_VOTE_NACK.
+ * Otherwise send_node_list = 1 and vote = TLV_VOTE_NO_CHANGE
+ *
+ * If send_node_list is set to non zero, node list is sent to qnetd
+ */
+int
+qdevice_net_algo_test_votequorum_quorum_notify(struct qdevice_net_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list,
+    enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Votequorum quorum notify");
+
+       return (0);
+}
+/*
+ * Called after votequorum expected_votes notify is dispatched.
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ *
+ * Vote is set to TLV_VOTE_NO_CHANGE
+ */
+int
+qdevice_net_algo_test_votequorum_expected_votes_notify(struct qdevice_net_instance *instance,
+    uint32_t expected_votes, enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Votequorum expected votes notify");
+
+       return (0);
+}
+
+/*
+ * Called when config node list reply is received. Vote is set to value returned by server (and can
+ * be overwriten by algorithm). ring_id is returned by server. ring_id_is_valid is set to 1 only if
+ * received ring id matches last sent ring id. If it is 0, then it usually makes sense to not
+ * update timer.
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_config_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Config node list reply");
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+/*
+ * Called when membership node list reply (reply for votequorum votequorum_nodelist_notify_fn)
+ * is received. Vote is set to value returned by server (and can be overwriten by algorithm).
+ *
+ * Also if server returned TLV_VOTE_ASK_LATER, it's good idea to create timer (call timer_list_add
+ * with instance->main_timer_list parameter) and ask for reply (qdevice_net_send_ask_for_vote).
+ * Another option may be to wait for vote_info message (if server algorithm is configured so).
+ *
+ * ring_id and ring_id_is_valid have same meaning as for
+ * qdevice_net_algo_test_config_node_list_reply_received
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_membership_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Membership node list reply");
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+/*
+ * Called when quorum node list reply (reply for votequorum votequorum_quorum_notify_fn)
+ * is received. Vote is set to value returned by server (and can be overwriten by algorithm).
+ *
+ * ring_id and ring_id_is_valid have same meaning as for
+ * qdevice_net_algo_test_config_node_list_reply_received
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_quorum_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Quorum node list reply");
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+/*
+ * Called when reply for ask for vote message was received.
+ * Vote is set to value returned by server (and can be overwriten by algorithm).
+ *
+ * ring_id and ring_id_is_valid have same meaning as for
+ * qdevice_net_algo_test_config_node_list_reply_received
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_ask_for_vote_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Ask for vote reply received");
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+/*
+ * Called when vote info message from server was received.
+ * Vote is set to value sent by server (and can be overwriten by algorithm).
+ *
+ * ring_id and ring_id_is_valid have same meaning as for
+ * qdevice_net_algo_test_config_node_list_reply_received
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Vote info received");
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+/*
+ * Called when echo reply message was received.
+ * is_expected_seq_number is set to 1 if received seq_number was equal to last sent echo request.
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_echo_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int is_expected_seq_number)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Echo reply received");
+
+       return (is_expected_seq_number ? 0 : -1);
+}
+
+/*
+ * Called when client is about to send echo request but echo reply to previous echo request
+ * was not yet received.
+ *
+ * Callback should return 0 if processing should continue (echo request is not sent but timer is
+ * scheduled again) otherwise -1 (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_echo_reply_not_received(struct qdevice_net_instance *instance)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Echo reply not received");
+
+       return (-1);
+}
+
+/*
+ * Called when regular heuristics finished and it's result differs from previous heuristics.
+ *
+ * heuristics contains result of heuristics
+ * send_msg distinquish if message should be sent to qnetd. Default value is 0 if qnetd is not
+ *   connected and 1 otherwise
+ * vote can be set to change cast_vote_timer voting value (default is TLV_VOTE_NO_CHANGE)
+ *
+ * It's not possible to set send_msg to 1 and heuristics to TLV_HEURISTICS_UNDEFINED
+ *
+ * Callback should return 0 on success, -1 on failure (-> force exit)
+ */
+int
+qdevice_net_algo_test_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Heuristics change");
+
+       return (0);
+}
+
+/*
+ * Called when reply for heuristics change was received.
+ * Vote is set to value returned by server (and can be overwriten by algorithm).
+ *
+ * ring_id and ring_id_is_valid have same meaning as for
+ * qdevice_net_algo_test_config_node_list_reply_received
+ *
+ * heuristics is unmodified value sent to server (and set by qdevice_net_algo_test_heuristics_change)
+ *
+ * Callback should return 0 on success or -1 on failure (-> disconnect client).
+ */
+int
+qdevice_net_algo_test_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_heuristics heuristics, enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: heuristics change reply received");
+
+       if (!ring_id_is_valid) {
+               *vote = TLV_VOTE_NO_CHANGE;
+       }
+
+       return (0);
+}
+
+/*
+ * Called when client disconnects from server.
+ *
+ * disconnect_reason contains one of QDEVICE_NET_DISCONNECT_REASON_
+ * try_reconnect can be set to non zero value if reconnect to server should be tried
+ * vote (default TLV_VOTE_NO_CHANGE) can be set to update voting timer
+ *
+ * Callback should return 0 on success, -1 on failure (-> force exit)
+ */
+int
+qdevice_net_algo_test_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Disconnected");
+
+       return (0);
+}
+
+/*
+ * Called when qdevice-net is going down.
+ */
+void
+qdevice_net_algo_test_destroy(struct qdevice_net_instance *instance)
+{
+
+       qdevice_log(LOG_INFO, "algo-test: Destroy");
+}
+
+static struct qdevice_net_algorithm qdevice_net_algo_test = {
+       .init                                   = qdevice_net_algo_test_init,
+       .connected                              = qdevice_net_algo_test_connected,
+       .config_node_list_changed               = qdevice_net_algo_test_config_node_list_changed,
+       .votequorum_node_list_notify            = qdevice_net_algo_test_votequorum_node_list_notify,
+       .votequorum_node_list_heuristics_notify = qdevice_net_algo_test_votequorum_node_list_heuristics_notify,
+       .votequorum_quorum_notify               = qdevice_net_algo_test_votequorum_quorum_notify,
+       .votequorum_expected_votes_notify       = qdevice_net_algo_test_votequorum_expected_votes_notify,
+       .config_node_list_reply_received        = qdevice_net_algo_test_config_node_list_reply_received,
+       .membership_node_list_reply_received    = qdevice_net_algo_test_membership_node_list_reply_received,
+       .quorum_node_list_reply_received        = qdevice_net_algo_test_quorum_node_list_reply_received,
+       .ask_for_vote_reply_received            = qdevice_net_algo_test_ask_for_vote_reply_received,
+       .vote_info_received                     = qdevice_net_algo_test_vote_info_received,
+       .echo_reply_received                    = qdevice_net_algo_test_echo_reply_received,
+       .echo_reply_not_received                = qdevice_net_algo_test_echo_reply_not_received,
+       .heuristics_change                      = qdevice_net_algo_test_heuristics_change,
+       .heuristics_change_reply_received       = qdevice_net_algo_test_heuristics_change_reply_received,
+       .disconnected                           = qdevice_net_algo_test_disconnected,
+       .destroy                                = qdevice_net_algo_test_destroy,
+};
+
+int
+qdevice_net_algo_test_register(void)
+{
+       return (qdevice_net_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_TEST, &qdevice_net_algo_test));
+}
diff --git a/qdevices/qdevice-net-algo-test.h b/qdevices/qdevice-net-algo-test.h
new file mode 100644 (file)
index 0000000..ab8af85
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_ALGO_TEST_H_
+#define _QDEVICE_NET_ALGO_TEST_H_
+
+#include "qdevice-net-algorithm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_net_algo_test_init(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_test_connected(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list,
+    int *send_quorum_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_config_node_list_changed(
+    struct qdevice_net_instance *instance, const struct node_list *nlist,
+    int config_version_set, uint64_t config_version, int *send_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_votequorum_node_list_notify(
+    struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], int *pause_cast_vote_timer,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_votequorum_node_list_heuristics_notify(
+    struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], int *send_node_list, enum tlv_vote *vote,
+    enum tlv_heuristics *heuristics);
+
+extern int     qdevice_net_algo_test_votequorum_quorum_notify(
+    struct qdevice_net_instance *instance, uint32_t quorate, uint32_t node_list_entries,
+    votequorum_node_t node_list[], int *send_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_votequorum_expected_votes_notify(
+    struct qdevice_net_instance *instance, uint32_t expected_votes, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_config_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, int initial,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_membership_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, const struct tlv_ring_id *ring_id,
+    int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_quorum_node_list_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_ask_for_vote_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number,
+    const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_echo_reply_received(
+    struct qdevice_net_instance *instance, uint32_t seq_number, int is_expected_seq_number);
+
+extern int     qdevice_net_algo_test_echo_reply_not_received(
+    struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_test_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algo_test_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote);
+
+extern void    qdevice_net_algo_test_destroy(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algo_test_register(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_ALGO_TEST_H_ */
diff --git a/qdevices/qdevice-net-algorithm.c b/qdevices/qdevice-net-algorithm.c
new file mode 100644 (file)
index 0000000..9919383
--- /dev/null
@@ -0,0 +1,388 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include "qnet-config.h"
+#include "qdevice-net-algorithm.h"
+#include "qdevice-log.h"
+
+#include "qdevice-net-algo-test.h"
+#include "qdevice-net-algo-ffsplit.h"
+#include "qdevice-net-algo-2nodelms.h"
+#include "qdevice-net-algo-lms.h"
+
+static struct qdevice_net_algorithm *qdevice_net_algorithm_array[QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE];
+
+int
+qdevice_net_algorithm_init(struct qdevice_net_instance *instance)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_init unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->init(instance));
+}
+
+int
+qdevice_net_algorithm_connected(struct qdevice_net_instance *instance, enum tlv_heuristics *heuristics,
+    int *send_config_node_list, int *send_membership_node_list, int *send_quorum_node_list, enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_connected unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->connected(instance,
+           heuristics, send_config_node_list, send_membership_node_list, send_quorum_node_list, vote));
+}
+
+int
+qdevice_net_algorithm_config_node_list_changed(struct qdevice_net_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version,
+    int *send_node_list, enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_connected unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           config_node_list_changed(instance, nlist, config_version_set, config_version,
+           send_node_list, vote));
+}
+
+int
+qdevice_net_algorithm_votequorum_node_list_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *pause_cast_vote_timer, enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_votequorum_node_list_notify "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->votequorum_node_list_notify(
+           instance, ring_id, node_list_entries, node_list, pause_cast_vote_timer, vote));
+}
+
+int
+qdevice_net_algorithm_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_votequorum_node_list_heuristics_notify "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           votequorum_node_list_heuristics_notify(
+           instance, ring_id, node_list_entries, node_list, send_node_list, vote, heuristics));
+}
+
+int
+qdevice_net_algorithm_votequorum_quorum_notify(struct qdevice_net_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list,
+    enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_votequorum_quorum_notify "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           votequorum_quorum_notify(instance, quorate, node_list_entries, node_list,
+           send_node_list, vote));
+}
+
+int
+qdevice_net_algorithm_votequorum_expected_votes_notify(struct qdevice_net_instance *instance,
+    uint32_t expected_votes, enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_votequorum_expected_votes_notify "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           votequorum_expected_votes_notify(instance, expected_votes, vote));
+}
+
+int
+qdevice_net_algorithm_config_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_config_node_list_reply_received "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           config_node_list_reply_received(instance, seq_number, initial, ring_id,
+           ring_id_is_valid, vote));
+}
+
+int
+qdevice_net_algorithm_membership_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_membership_node_list_reply_received "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           membership_node_list_reply_received(instance, seq_number, ring_id, ring_id_is_valid,
+           vote));
+}
+
+int
+qdevice_net_algorithm_quorum_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_quorum_node_list_reply_received "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           quorum_node_list_reply_received(instance, seq_number, ring_id, ring_id_is_valid,
+           vote));
+}
+
+int
+qdevice_net_algorithm_ask_for_vote_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_ask_for_vote_reply_received "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           ask_for_vote_reply_received(instance, seq_number, ring_id, ring_id_is_valid, vote));
+}
+
+int
+qdevice_net_algorithm_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_vote_info_received "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           vote_info_received(instance, seq_number, ring_id, ring_id_is_valid, vote));
+}
+
+int
+qdevice_net_algorithm_echo_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int is_expected_seq_number)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_echo_reply_received "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           echo_reply_received(instance, seq_number, is_expected_seq_number));
+}
+
+int
+qdevice_net_algorithm_echo_reply_not_received(struct qdevice_net_instance *instance)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_echo_reply_not_received "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           echo_reply_not_received(instance));
+}
+
+int
+qdevice_net_algorithm_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_heuristics_change "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           heuristics_change(instance, heuristics, send_msg, vote));
+}
+
+int
+qdevice_net_algorithm_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_heuristics heuristics, enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_heuristics_change_reply_received "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           heuristics_change_reply_received(instance, seq_number, ring_id, ring_id_is_valid,
+           heuristics, vote));
+}
+
+int
+qdevice_net_algorithm_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_disconnected "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           disconnected(instance, disconnect_reason, try_reconnect, vote));
+}
+
+void
+qdevice_net_algorithm_destroy(struct qdevice_net_instance *instance)
+{
+
+       if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_algorithm_destroy "
+                   "unhandled decision algorithm");
+               exit(1);
+       }
+
+       return (qdevice_net_algorithm_array[instance->decision_algorithm]->
+           destroy(instance));
+}
+
+int
+qdevice_net_algorithm_register(enum tlv_decision_algorithm_type algorithm_number,
+    struct qdevice_net_algorithm *algorithm)
+{
+
+       if (algorithm_number >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE) {
+               return (-1);
+       }
+
+       if (qdevice_net_algorithm_array[algorithm_number] != NULL) {
+               return (-1);
+       }
+
+       qdevice_net_algorithm_array[algorithm_number] = algorithm;
+
+       return (0);
+}
+
+int
+qdevice_net_algorithm_register_all(void)
+{
+
+       if (qdevice_net_algo_test_register() != 0) {
+               qdevice_log(LOG_CRIT, "Failed to register decision algorithm 'test' ");
+               return (-1);
+       }
+
+       if (qdevice_net_algo_ffsplit_register() != 0) {
+               qdevice_log(LOG_CRIT, "Failed to register decision algorithm 'ffsplit' ");
+               return (-1);
+       }
+
+       if (qdevice_net_algo_2nodelms_register() != 0) {
+               qdevice_log(LOG_CRIT, "Failed to register decision algorithm '2nodelms' ");
+               return (-1);
+       }
+
+       if (qdevice_net_algo_lms_register() != 0) {
+               qdevice_log(LOG_CRIT, "Failed to register decision algorithm 'lms' ");
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-net-algorithm.h b/qdevices/qdevice-net-algorithm.h
new file mode 100644 (file)
index 0000000..2ef7e95
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_ALGORITHM_H_
+#define _QDEVICE_NET_ALGORITHM_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include "node-list.h"
+#include "qdevice-net-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_net_algorithm_init(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algorithm_connected(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list,
+    int *send_quorum_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_config_node_list_changed(struct qdevice_net_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version,
+    int *send_node_list, enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_votequorum_node_list_notify(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *pause_cast_vote_timer, enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_votequorum_node_list_heuristics_notify(
+    struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+    int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics);
+
+extern int     qdevice_net_algorithm_votequorum_quorum_notify(struct qdevice_net_instance *instance,
+    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_votequorum_expected_votes_notify(struct qdevice_net_instance *instance,
+    uint32_t expected_votes, enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_config_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_membership_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_quorum_node_list_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_ask_for_vote_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_vote_info_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_echo_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, int is_expected_seq_number);
+
+extern int     qdevice_net_algorithm_echo_reply_not_received(struct qdevice_net_instance *instance);
+
+extern int     qdevice_net_algorithm_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_heuristics_change_reply_received(struct qdevice_net_instance *instance,
+    uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics,
+    enum tlv_vote *vote);
+
+extern int     qdevice_net_algorithm_disconnected(struct qdevice_net_instance *instance,
+    enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote);
+
+extern void    qdevice_net_algorithm_destroy(struct qdevice_net_instance *instance);
+
+struct qdevice_net_algorithm {
+       int (*init)(struct qdevice_net_instance *instance);
+       int (*connected)(struct qdevice_net_instance *instance,
+           enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list,
+           int *send_quorum_node_list, enum tlv_vote *vote);
+       int (*config_node_list_changed)(struct qdevice_net_instance *instance,
+           const struct node_list *nlist, int config_version_set, uint64_t config_version,
+           int *send_node_list, enum tlv_vote *vote);
+       int (*votequorum_node_list_notify)(struct qdevice_net_instance *instance,
+           const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+           int *pause_cast_vote_timer, enum tlv_vote *vote);
+       int (*votequorum_node_list_heuristics_notify)(struct qdevice_net_instance *instance,
+           const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[],
+           int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics);
+       int (*votequorum_quorum_notify)(struct qdevice_net_instance *instance, uint32_t quorate,
+           uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list,
+           enum tlv_vote *vote);
+       int (*votequorum_expected_votes_notify)(struct qdevice_net_instance *instance,
+           uint32_t expected_votes, enum tlv_vote *vote);
+       int (*config_node_list_reply_received)(struct qdevice_net_instance *instance,
+           uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+           enum tlv_vote *vote);
+       int (*membership_node_list_reply_received)(struct qdevice_net_instance *instance,
+           uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+           enum tlv_vote *vote);
+       int (*quorum_node_list_reply_received)(struct qdevice_net_instance *instance,
+           uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+           enum tlv_vote *vote);
+       int (*ask_for_vote_reply_received)(struct qdevice_net_instance *instance,
+           uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+           enum tlv_vote *vote);
+       int (*vote_info_received)(struct qdevice_net_instance *instance,
+           uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+           enum tlv_vote *vote);
+       int (*echo_reply_received)(struct qdevice_net_instance *instance,
+           uint32_t seq_number, int is_expected_seq_number);
+       int (*echo_reply_not_received)(struct qdevice_net_instance *instance);
+       int (*heuristics_change)(struct qdevice_net_instance *instance,
+           enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote);
+       int (*heuristics_change_reply_received)(struct qdevice_net_instance *instance,
+           uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid,
+           enum tlv_heuristics heuristics, enum tlv_vote *vote);
+       int (*disconnected)(struct qdevice_net_instance *instance,
+           enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect,
+           enum tlv_vote *vote);
+       void (*destroy)(struct qdevice_net_instance *instance);
+};
+
+extern int              qdevice_net_algorithm_register(
+       enum tlv_decision_algorithm_type algorithm_number, struct qdevice_net_algorithm *algorithm);
+
+extern int              qdevice_net_algorithm_register_all(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_ALGORITHM_H_ */
diff --git a/qdevices/qdevice-net-cast-vote-timer.c b/qdevices/qdevice-net-cast-vote-timer.c
new file mode 100644 (file)
index 0000000..a95b905
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qnet-config.h"
+#include "qdevice-log.h"
+#include "qdevice-net-cast-vote-timer.h"
+#include "qdevice-votequorum.h"
+
+static int
+qdevice_net_cast_vote_timer_callback(void *data1, void *data2)
+{
+       struct qdevice_net_instance *instance;
+       int cast_vote;
+       int case_processed;
+
+       instance = (struct qdevice_net_instance *)data1;
+
+       if (instance->cast_vote_timer_paused) {
+               return (-1);
+       }
+
+       case_processed = 0;
+
+       switch (instance->cast_vote_timer_vote) {
+       case TLV_VOTE_ACK:
+               case_processed = 1;
+               cast_vote = 1;
+               break;
+       case TLV_VOTE_NACK:
+               case_processed = 1;
+               cast_vote = 0;
+               break;
+       case TLV_VOTE_ASK_LATER:
+       case TLV_VOTE_WAIT_FOR_REPLY:
+       case TLV_VOTE_NO_CHANGE:
+       case TLV_VOTE_UNDEFINED:
+               /*
+                * Shouldn't happen
+                */
+               break;
+       /*
+        * Default is not defined intentionally. Compiler shows warning when
+        * new tlv_vote is added.
+        */
+       }
+
+       if (!case_processed) {
+               qdevice_log(LOG_CRIT, "qdevice_net_timer_cast_vote: Unhandled cast_vote_timer_vote %u\n",
+                   instance->cast_vote_timer_vote);
+               exit(1);
+       }
+
+       if (qdevice_votequorum_poll(instance->qdevice_instance_ptr, cast_vote) != 0) {
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               instance->schedule_disconnect = 1;
+               instance->cast_vote_timer = NULL;
+               return (0);
+       }
+
+       /*
+        * Schedule this function callback again
+        */
+       return (-1);
+}
+
+int
+qdevice_net_cast_vote_timer_update(struct qdevice_net_instance *instance, enum tlv_vote vote)
+{
+       int timer_needs_running;
+       int case_processed;
+
+       case_processed = 0;
+
+       switch (vote) {
+       case TLV_VOTE_UNDEFINED:
+               break;
+       case TLV_VOTE_ACK:
+       case TLV_VOTE_NACK:
+               case_processed = 1;
+               timer_needs_running = 1;
+               break;
+       case TLV_VOTE_WAIT_FOR_REPLY:
+       case TLV_VOTE_ASK_LATER:
+               case_processed = 1;
+               timer_needs_running = 0;
+               break;
+       case TLV_VOTE_NO_CHANGE:
+               case_processed = 1;
+               return (0);
+
+               break;
+       /*
+        * Default is not defined intentionally. Compiler shows warning when
+        * new tlv_vote is added.
+        */
+       }
+
+       if (!case_processed) {
+               qdevice_log(LOG_CRIT, "qdevice_net_cast_vote_timer_update_vote: Unhandled vote parameter %u\n",
+                   vote);
+               exit(1);
+       }
+
+       instance->cast_vote_timer_vote = vote;
+
+       if (timer_needs_running) {
+               if (instance->cast_vote_timer == NULL) {
+                       instance->cast_vote_timer = timer_list_add(&instance->main_timer_list,
+                           instance->cast_vote_timer_interval,
+                           qdevice_net_cast_vote_timer_callback, (void *)instance, NULL);
+
+                       if (instance->cast_vote_timer == NULL) {
+                               qdevice_log(LOG_ERR, "Can't schedule sending of "
+                                   "votequorum poll");
+
+                               return (-1);
+                       } else {
+                               qdevice_log(LOG_DEBUG, "Cast vote timer is now scheduled every "
+                                   "%"PRIu32"ms voting %s.", instance->cast_vote_timer_interval,
+                                   tlv_vote_to_str(instance->cast_vote_timer_vote));
+                       }
+               } else {
+                       qdevice_log(LOG_DEBUG, "Cast vote timer remains scheduled every "
+                           "%"PRIu32"ms voting %s.", instance->cast_vote_timer_interval,
+                           tlv_vote_to_str(instance->cast_vote_timer_vote));
+               }
+
+               if (qdevice_net_cast_vote_timer_callback((void *)instance, NULL) != -1) {
+                       return (-1);
+               }
+       } else {
+               if (instance->cast_vote_timer != NULL) {
+                       timer_list_delete(&instance->main_timer_list, instance->cast_vote_timer);
+                       instance->cast_vote_timer = NULL;
+                       qdevice_log(LOG_DEBUG, "Cast vote timer is now stopped.");
+               } else {
+                       qdevice_log(LOG_DEBUG, "Cast vote timer remains stopped.");
+               }
+       }
+
+       return (0);
+}
+
+void
+qdevice_net_cast_vote_timer_set_paused(struct qdevice_net_instance *instance, int paused)
+{
+
+       if (paused != instance->cast_vote_timer_paused) {
+               instance->cast_vote_timer_paused = paused;
+
+               if (instance->cast_vote_timer_paused) {
+                       qdevice_log(LOG_DEBUG, "Cast vote timer is now paused.");
+               } else {
+                       qdevice_log(LOG_DEBUG, "Cast vote timer is no longer paused.");
+               }
+       }
+}
diff --git a/qdevices/qdevice-net-cast-vote-timer.h b/qdevices/qdevice-net-cast-vote-timer.h
new file mode 100644 (file)
index 0000000..f7014d1
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_CAST_VOTE_TIMER_H_
+#define _QDEVICE_NET_CAST_VOTE_TIMER_H_
+
+#include "qdevice-net-instance.h"
+#include "tlv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qdevice_net_cast_vote_timer_update(
+    struct qdevice_net_instance *instance, enum tlv_vote vote);
+
+extern void            qdevice_net_cast_vote_timer_set_paused(
+    struct qdevice_net_instance *instance, int paused);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_CAST_VOTE_TIMER_H_ */
diff --git a/qdevices/qdevice-net-disconnect-reason.h b/qdevices/qdevice-net-disconnect-reason.h
new file mode 100644 (file)
index 0000000..8c86b64
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_DISCONNECT_REASON_H_
+#define _QDEVICE_NET_DISCONNECT_REASON_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum qdevice_net_disconnect_reason {
+       /* Undefined reason. If this error appears, it's error in source code */
+       QDEVICE_NET_DISCONNECT_REASON_UNDEFINED,
+
+       /* Received known message, but it was not expected */
+       QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG,
+       /* Received unknown message */
+       QDEVICE_NET_DISCONNECT_REASON_UNSUPPORTED_MSG,
+       /* TLS setting of server and client are incompatible */
+       QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_TLS,
+       /* MSG setting of server and client are incompatible */
+       QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_MSG_SIZE,
+       /* Message doesn't contain required option */
+       QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING,
+
+       /* Can't allocate send list item or message is too long to fit into send buffer */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER,
+       /* Impossible to create or update heartbeat sending timer */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_HB_TIMER,
+       /* Impossible to create or update votequorum poll timer */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER,
+       /* Impossible to create or update regular heuristics timer */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_HEURISTICS_TIMER,
+       /* Impossible to exec heuristics */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_START_HEURISTICS,
+       /* Impossible to activate/deactive heuristics result notifier */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER,
+       /* Impossible to register votequorum callback */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_REGISTER_VOTEQUORUM_CALLBACK,
+       /* Impossible to register cmap callback */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_REGISTER_CMAP_CALLBACK,
+       /* Impossible to start TLS session */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_START_TLS,
+
+       /* Received message with error field set to non TLV_REPLY_ERROR_CODE_NO_ERROR value */
+       QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ERROR,
+       /* Received message with error field set to TLV_REPLY_ERROR_CODE_DUPLICATE_NODE_ID value */
+       QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_DUPLICATE_NODE_ID_ERROR,
+       /* Received message with error field set to TLV_REPLY_ERROR_CODE_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES value */
+       QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES_ERROR,
+       /* Received message with error field set to TLV_REPLY_ERROR_CODE_ALGORITHM_DIFFERS_FROM_OTHER_NODES value */
+       QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ALGORITHM_DIFFERS_FROM_OTHER_NODES_ERROR,
+
+       /* Server doesn't support client selected decision algorithm */
+       QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_ALGORITHM,
+       /* Server doesn't support client required option */
+       QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT,
+
+       /* Can't decode message sent by server */
+       QDEVICE_NET_DISCONNECT_REASON_MSG_DECODE_ERROR,
+       /* Server closed connection */
+       QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION,
+       /* Can't read or store message received from server */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE,
+       /* Can't send message to server */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_SEND_MESSAGE,
+
+       /* Can't dispatch cmap or votequroum. This cannot be overwritten and always means end of qdevice-net */
+       QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED,
+
+       /* Local socket closed is reasult of sigint */
+       QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED,
+
+       /* It was not possible to establish connection with qnetd */
+       QDEVICE_NET_DISCONNECT_REASON_CANT_CONNECT_TO_THE_SERVER,
+
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_CONNECTED_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_CONFIG_NODE_LIST_CHANGED_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_QUORUM_NOTIFY_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_NOTIFY_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_HEURISTICS_NOTIFY_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_EXPECTED_VOTES_NOTIFY_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_NODE_LIST_REPLY_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_ASK_FOR_VOTE_REPLY_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTE_INFO_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_RECEIVED_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_NOT_RECEIVED_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_ERR,
+       QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_REPLY_ERR,
+
+       QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED,
+       QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_CANT_SEND_RECEIVE_MSG,
+};
+
+#define qdevice_net_disconnect_reason_try_reconnect(reason) (                                          \
+    reason == QDEVICE_NET_DISCONNECT_REASON_MSG_DECODE_ERROR ||                                                \
+    reason == QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION ||                                        \
+    reason == QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE ||                                       \
+    reason == QDEVICE_NET_DISCONNECT_REASON_CANT_SEND_MESSAGE ||                                       \
+    reason == QDEVICE_NET_DISCONNECT_REASON_CANT_CONNECT_TO_THE_SERVER ||                              \
+    reason == QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_NOT_RECEIVED_ERR ||                                \
+    reason == QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_DUPLICATE_NODE_ID_ERROR ||                     \
+    reason == QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES_ERROR ||  \
+    reason == QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ALGORITHM_DIFFERS_FROM_OTHER_NODES_ERROR)
+
+
+#define qdevice_net_disconnect_reason_force_disconnect(reason) (                       \
+    reason == QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED ||              \
+    reason == QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED ||                     \
+    reason == QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER ||              \
+    reason == QDEVICE_NET_DISCONNECT_REASON_CANT_REGISTER_VOTEQUORUM_CALLBACK ||       \
+    reason == QDEVICE_NET_DISCONNECT_REASON_CANT_REGISTER_CMAP_CALLBACK ||             \
+    reason == QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED ||                        \
+    reason == QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_CANT_SEND_RECEIVE_MSG)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_DISCONNECT_REASON_H_ */
diff --git a/qdevices/qdevice-net-echo-request-timer.c b/qdevices/qdevice-net-echo-request-timer.c
new file mode 100644 (file)
index 0000000..280793e
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-net-algorithm.h"
+#include "qdevice-net-echo-request-timer.h"
+#include "qdevice-net-send.h"
+#include "qdevice-log.h"
+
+static int
+qdevice_net_echo_request_timer_callback(void *data1, void *data2)
+{
+       struct qdevice_net_instance *instance;
+
+       instance = (struct qdevice_net_instance *)data1;
+
+       if (instance->echo_reply_received_msg_seq_num !=
+           instance->echo_request_expected_msg_seq_num) {
+               qdevice_log(LOG_ERR, "Server didn't send echo reply message on time");
+
+               if (qdevice_net_algorithm_echo_reply_not_received(instance) != 0) {
+                       qdevice_log(LOG_DEBUG, "Algorithm decided to disconnect");
+
+                       instance->schedule_disconnect = 1;
+                       instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_NOT_RECEIVED_ERR;
+
+                       instance->echo_request_timer = NULL;
+                       return (0);
+               } else {
+                       qdevice_log(LOG_DEBUG, "Algorithm decided to continue send heartbeat");
+
+                       return (-1);
+               }
+       }
+
+       if (qdevice_net_send_echo_request(instance) == -1) {
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+
+               instance->schedule_disconnect = 1;
+               instance->echo_request_timer = NULL;
+               return (0);
+       }
+
+       /*
+        * Schedule this function callback again
+        */
+       return (-1);
+}
+
+int
+qdevice_net_echo_request_timer_schedule(struct qdevice_net_instance *instance)
+{
+       instance->echo_request_expected_msg_seq_num = 0;
+       instance->echo_reply_received_msg_seq_num = 0;
+
+       if (instance->echo_request_timer != NULL) {
+               timer_list_delete(&instance->main_timer_list, instance->echo_request_timer);
+               instance->echo_request_timer = NULL;
+       }
+
+       qdevice_log(LOG_DEBUG, "Scheduling send of heartbeat every %"PRIu32"ms", instance->heartbeat_interval);
+       instance->echo_request_timer = timer_list_add(&instance->main_timer_list,
+           instance->heartbeat_interval, qdevice_net_echo_request_timer_callback,
+           (void *)instance, NULL);
+
+       if (instance->echo_request_timer == NULL) {
+               qdevice_log(LOG_ERR, "Can't schedule regular sending of heartbeat.");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_HB_TIMER;
+
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-net-echo-request-timer.h b/qdevices/qdevice-net-echo-request-timer.h
new file mode 100644 (file)
index 0000000..2191c55
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2016-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_ECHO_REQUEST_TIMER_H_
+#define _QDEVICE_NET_ECHO_REQUEST_TIMER_H_
+
+#include "qdevice-net-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_net_echo_request_timer_schedule(struct qdevice_net_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_ECHO_REQUEST_TIMER_H_ */
diff --git a/qdevices/qdevice-net-heuristics.c b/qdevices/qdevice-net-heuristics.c
new file mode 100644 (file)
index 0000000..2bf23cf
--- /dev/null
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-log.h"
+#include "qdevice-net-algorithm.h"
+#include "qdevice-net-cast-vote-timer.h"
+#include "qdevice-net-heuristics.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-votequorum.h"
+
+enum tlv_heuristics
+qdevice_net_heuristics_exec_result_to_tlv(enum qdevice_heuristics_exec_result exec_result)
+{
+       enum tlv_heuristics res;
+
+       switch (exec_result) {
+       case QDEVICE_HEURISTICS_EXEC_RESULT_DISABLED: res = TLV_HEURISTICS_UNDEFINED; break;
+       case QDEVICE_HEURISTICS_EXEC_RESULT_PASS: res = TLV_HEURISTICS_PASS; break;
+       case QDEVICE_HEURISTICS_EXEC_RESULT_FAIL: res = TLV_HEURISTICS_FAIL; break;
+       default:
+               qdevice_log(LOG_ERR, "qdevice_net_heuristics_exec_result_to_tlv: Unhandled "
+                   "heuristics exec result %s",
+                   qdevice_heuristics_exec_result_to_str(exec_result));
+               exit(1);
+               break;
+       }
+
+       return (res);
+}
+
+static int
+qdevice_net_regular_heuristics_exec_result_callback(void *heuristics_instance_ptr,
+    uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result)
+{
+       struct qdevice_heuristics_instance *heuristics_instance;
+       struct qdevice_instance *instance;
+       struct qdevice_net_instance *net_instance;
+       int send_msg;
+       enum tlv_vote vote;
+       enum tlv_heuristics heuristics;
+
+       heuristics_instance = (struct qdevice_heuristics_instance *)heuristics_instance_ptr;
+       instance = heuristics_instance->qdevice_instance_ptr;
+       net_instance = instance->model_data;
+
+       if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list,
+           qdevice_net_regular_heuristics_exec_result_callback, 0) != 0) {
+               qdevice_log(LOG_ERR, "Can't deactivate net regular heuristics exec callback notifier");
+
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       }
+
+       heuristics = qdevice_net_heuristics_exec_result_to_tlv(exec_result);
+
+       if (exec_result == QDEVICE_HEURISTICS_EXEC_RESULT_DISABLED) {
+               /*
+                * Can happen when user disables heuristics during runtime
+                */
+               return (0);
+       }
+
+       if (net_instance->latest_heuristics_result != heuristics) {
+               qdevice_log(LOG_ERR, "Heuristics result changed from %s to %s",
+                   tlv_heuristics_to_str(net_instance->latest_heuristics_result),
+                   tlv_heuristics_to_str(heuristics));
+
+               if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+                       /*
+                        * Not connected to qnetd
+                        */
+                       send_msg = 0;
+               } else {
+                       send_msg = 1;
+               }
+
+               vote = TLV_VOTE_NO_CHANGE;
+
+               if (qdevice_net_algorithm_heuristics_change(net_instance, &heuristics, &send_msg,
+                   &vote) == -1) {
+                       qdevice_log(LOG_ERR, "Algorithm returned error. Disconnecting.");
+
+                       net_instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_ERR;
+                       net_instance->schedule_disconnect = 1;
+
+                       return (0);
+               } else {
+                       qdevice_log(LOG_DEBUG, "Algorithm decided to %s message with heuristics result "
+                           "%s and result vote is %s", (send_msg ? "send" : "not send"),
+                           tlv_heuristics_to_str(heuristics), tlv_vote_to_str(vote));
+               }
+
+               if (send_msg) {
+                       if (heuristics == TLV_HEURISTICS_UNDEFINED) {
+                               qdevice_log(LOG_ERR, "Inconsistent algorithm result. "
+                                   "It's not possible to send message with undefined heuristics. "
+                                   "Disconnecting.");
+
+                               net_instance->disconnect_reason =
+                                   QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_ERR;
+                               net_instance->schedule_disconnect = 1;
+
+                               return (0);
+                       }
+
+                       if (!net_instance->server_supports_heuristics) {
+                               qdevice_log(LOG_ERR, "Server doesn't support heuristics. "
+                                   "Disconnecting.");
+
+                               net_instance->disconnect_reason =
+                                   QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT;
+                               net_instance->schedule_disconnect = 1;
+
+                               return (0);
+                       }
+
+                       if (qdevice_net_send_heuristics_change(net_instance, heuristics) != 0) {
+                               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+                               net_instance->schedule_disconnect = 1;
+
+                               return (0);
+                       }
+               }
+
+               if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
+                       qdevice_log(LOG_CRIT, "qdevice_net_heuristics_exec_result_callback "
+                           "Can't update cast vote timer");
+
+                       net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+                       net_instance->schedule_disconnect = 1;
+
+                       return (0);
+               }
+       }
+
+       net_instance->latest_regular_heuristics_result = heuristics;
+       net_instance->latest_heuristics_result = heuristics;
+
+       if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) {
+               return (0);
+       }
+
+       return (0);
+}
+
+static int
+qdevice_net_connect_heuristics_exec_result_callback(void *heuristics_instance_ptr,
+    uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result)
+{
+       struct qdevice_heuristics_instance *heuristics_instance;
+       struct qdevice_instance *instance;
+       struct qdevice_net_instance *net_instance;
+       enum tlv_vote vote;
+       enum tlv_heuristics heuristics;
+       int send_config_node_list;
+       int send_membership_node_list;
+       int send_quorum_node_list;
+       struct tlv_ring_id tlv_rid;
+       enum tlv_quorate quorate;
+
+       heuristics_instance = (struct qdevice_heuristics_instance *)heuristics_instance_ptr;
+       instance = heuristics_instance->qdevice_instance_ptr;
+       net_instance = instance->model_data;
+
+
+       if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list,
+           qdevice_net_connect_heuristics_exec_result_callback, 0) != 0) {
+               qdevice_log(LOG_ERR, "Can't deactivate net connect heuristics exec callback notifier");
+
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       }
+
+       heuristics = qdevice_net_heuristics_exec_result_to_tlv(exec_result);
+
+       send_config_node_list = 1;
+       send_membership_node_list = 1;
+       send_quorum_node_list = 1;
+       vote = TLV_VOTE_WAIT_FOR_REPLY;
+
+       if (qdevice_net_algorithm_connected(net_instance, &heuristics, &send_config_node_list,
+           &send_membership_node_list, &send_quorum_node_list, &vote) != 0) {
+               qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting.");
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_CONNECTED_ERR;
+               return (0);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm decided to %s config node list, %s membership "
+                   "node list, %s quorum node list, heuristics is %s and result vote is %s",
+                   (send_config_node_list ? "send" : "not send"),
+                   (send_membership_node_list ? "send" : "not send"),
+                   (send_quorum_node_list ? "send" : "not send"),
+                   tlv_heuristics_to_str(heuristics),
+                   tlv_vote_to_str(vote));
+       }
+
+       /*
+        * Now we can finally really send node list, votequorum node list and update timer
+        */
+       if (send_config_node_list) {
+               if (qdevice_net_send_config_node_list(net_instance,
+                   &instance->config_node_list,
+                   instance->config_node_list_version_set,
+                   instance->config_node_list_version, 1) != 0) {
+                       net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+                       return (0);
+               }
+       }
+
+       if (send_membership_node_list) {
+               qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid,
+                   &instance->vq_node_list_ring_id);
+
+               if (qdevice_net_send_membership_node_list(net_instance, &tlv_rid,
+                   instance->vq_node_list_entries,
+                   instance->vq_node_list,
+                   heuristics) != 0) {
+                       net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+                       return (0);
+               }
+       }
+
+       if (send_quorum_node_list) {
+               quorate = (instance->vq_quorum_quorate ?
+                   TLV_QUORATE_QUORATE : TLV_QUORATE_INQUORATE);
+
+               if (qdevice_net_send_quorum_node_list(net_instance,
+                   quorate,
+                   instance->vq_quorum_node_list_entries,
+                   instance->vq_quorum_node_list) != 0) {
+                       net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+                       return (0);
+               }
+       }
+
+       if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
+               qdevice_log(LOG_CRIT, "qdevice_net_msg_received_set_option_reply fatal error. "
+                   " Can't update cast vote timer vote");
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+       }
+
+       net_instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS;
+       net_instance->connected_since_time = time(NULL);
+
+       net_instance->latest_connect_heuristics_result = heuristics;
+       net_instance->latest_heuristics_result = heuristics;
+
+       return (0);
+}
+
+static int
+qdevice_net_heuristics_timer_callback(void *data1, void *data2)
+{
+       struct qdevice_net_instance *net_instance;
+       struct qdevice_heuristics_instance *heuristics_instance;
+
+       net_instance = (struct qdevice_net_instance *)data1;
+       heuristics_instance = &net_instance->qdevice_instance_ptr->heuristics_instance;
+
+       if (qdevice_heuristics_waiting_for_result(heuristics_instance)) {
+               qdevice_log(LOG_DEBUG, "Not executing regular heuristics because other heuristics is already running.");
+
+               return (1);
+       }
+
+       net_instance->regular_heuristics_timer = NULL;
+
+       qdevice_log(LOG_DEBUG, "Executing regular heuristics.");
+
+       if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list,
+           qdevice_net_regular_heuristics_exec_result_callback, 1) != 0) {
+               qdevice_log(LOG_ERR, "Can't activate net regular heuristics exec callback notifier");
+
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER;
+               net_instance->schedule_disconnect = 1;
+
+               return (0);
+       }
+
+       if (qdevice_heuristics_exec(heuristics_instance,
+           net_instance->qdevice_instance_ptr->sync_in_progress) != 0) {
+               qdevice_log(LOG_ERR, "Can't execute regular heuristics.");
+
+               net_instance->schedule_disconnect = 1;
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_START_HEURISTICS;
+
+               return (0);
+       }
+
+       /*
+        * Do not schedule this callback again. It's going to be scheduled in the
+        * qdevice_net_heuristics_exec_result_callback
+        */
+       return (0);
+}
+
+int
+qdevice_net_heuristics_stop_timer(struct qdevice_net_instance *net_instance)
+{
+       struct qdevice_instance *instance;
+       struct qdevice_heuristics_instance *heuristics_instance;
+
+       instance = net_instance->qdevice_instance_ptr;
+       heuristics_instance = &instance->heuristics_instance;
+
+       if (net_instance->regular_heuristics_timer != NULL) {
+               qdevice_log(LOG_DEBUG, "Regular heuristics timer stopped");
+
+               timer_list_delete(&net_instance->main_timer_list, net_instance->regular_heuristics_timer);
+               net_instance->regular_heuristics_timer = NULL;
+
+               if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list,
+                   qdevice_net_regular_heuristics_exec_result_callback, 0) != 0) {
+                       qdevice_log(LOG_ERR, "Can't deactivate net regular heuristics exec callback notifier");
+
+                       net_instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER;
+                       net_instance->schedule_disconnect = 1;
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_heuristics_schedule_timer(struct qdevice_net_instance *net_instance)
+{
+       uint32_t interval;
+       struct qdevice_instance *instance;
+       struct qdevice_heuristics_instance *heuristics_instance;
+
+       instance = net_instance->qdevice_instance_ptr;
+       heuristics_instance = &instance->heuristics_instance;
+
+        if (heuristics_instance->mode != QDEVICE_HEURISTICS_MODE_ENABLED) {
+               qdevice_log(LOG_DEBUG, "Not scheduling heuristics timer because mode is not enabled");
+
+               if (qdevice_net_heuristics_stop_timer(net_instance) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+        }
+
+       if (net_instance->regular_heuristics_timer != NULL) {
+               qdevice_log(LOG_DEBUG, "Not scheduling heuristics timer because it is already scheduled");
+
+               return (0);
+       }
+
+       interval = heuristics_instance->interval;
+
+       qdevice_log(LOG_DEBUG, "Scheduling next regular heuristics in %"PRIu32"ms", interval);
+
+       net_instance->regular_heuristics_timer = timer_list_add(&net_instance->main_timer_list,
+               interval,
+               qdevice_net_heuristics_timer_callback,
+               (void *)net_instance, NULL);
+
+       if (net_instance->regular_heuristics_timer == NULL) {
+               qdevice_log(LOG_ERR, "Can't schedule regular heuristics.");
+
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_HEURISTICS_TIMER;
+               net_instance->schedule_disconnect = 1;
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_heuristics_init(struct qdevice_net_instance *net_instance)
+{
+
+       if (qdevice_heuristics_result_notifier_list_add(
+           &net_instance->qdevice_instance_ptr->heuristics_instance.exec_result_notifier_list,
+           qdevice_net_regular_heuristics_exec_result_callback) == NULL) {
+               qdevice_log(LOG_ERR, "Can't add net regular heuristics exec callback into notifier");
+
+               return (-1);
+       }
+
+       if (qdevice_heuristics_result_notifier_list_add(
+           &net_instance->qdevice_instance_ptr->heuristics_instance.exec_result_notifier_list,
+           qdevice_net_connect_heuristics_exec_result_callback) == NULL) {
+               qdevice_log(LOG_ERR, "Can't add net connect heuristics exec callback into notifier");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_heuristics_exec_after_connect(struct qdevice_net_instance *net_instance)
+{
+       struct qdevice_instance *instance;
+       struct qdevice_heuristics_instance *heuristics_instance;
+
+       instance = net_instance->qdevice_instance_ptr;
+       heuristics_instance = &instance->heuristics_instance;
+
+       qdevice_log(LOG_DEBUG, "Executing after-connect heuristics.");
+
+       if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list,
+           qdevice_net_connect_heuristics_exec_result_callback, 1) != 0) {
+               qdevice_log(LOG_ERR, "Can't activate net connect heuristics exec callback notifier");
+
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER;
+               net_instance->schedule_disconnect = 1;
+
+               return (-1);
+       }
+
+       if (qdevice_heuristics_exec(heuristics_instance,
+           instance->sync_in_progress) != 0) {
+               qdevice_log(LOG_ERR, "Can't execute connect heuristics.");
+
+               net_instance->schedule_disconnect = 1;
+               net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_START_HEURISTICS;
+
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-net-heuristics.h b/qdevices/qdevice-net-heuristics.h
new file mode 100644 (file)
index 0000000..6e0a1ff
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_HEURISTICS_H_
+#define _QDEVICE_NET_HEURISTICS_H_
+
+#include "qdevice-heuristics-exec-result.h"
+#include "qdevice-net-instance.h"
+#include "tlv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern enum tlv_heuristics     qdevice_net_heuristics_exec_result_to_tlv(
+    enum qdevice_heuristics_exec_result exec_result);
+
+extern int                     qdevice_net_heuristics_stop_timer(struct qdevice_net_instance *net_instance);
+
+extern int                     qdevice_net_heuristics_schedule_timer(struct qdevice_net_instance *net_instance);
+
+extern int                     qdevice_net_heuristics_init(struct qdevice_net_instance *net_instance);
+
+extern int                     qdevice_net_heuristics_exec_after_connect(struct qdevice_net_instance *net_instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_HEURISTICS_H_ */
diff --git a/qdevices/qdevice-net-instance.c b/qdevices/qdevice-net-instance.c
new file mode 100644 (file)
index 0000000..e4b7b04
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-config.h"
+#include "qdevice-log.h"
+#include "qdevice-net-instance.h"
+#include "qnet-config.h"
+#include "utils.h"
+#include "qdevice-net-poll-array-user-data.h"
+#include "qdevice-ipc.h"
+
+/*
+ * Needed for creating nspr handle from unix fd
+ */
+#include <private/pprio.h>
+
+int
+qdevice_net_instance_init(struct qdevice_net_instance *instance,
+    enum tlv_tls_supported tls_supported,
+    enum tlv_decision_algorithm_type decision_algorithm, uint32_t heartbeat_interval,
+    uint32_t sync_heartbeat_interval, uint32_t cast_vote_timer_interval,
+    const char *host_addr, uint16_t host_port, const char *cluster_name,
+    const struct tlv_tie_breaker *tie_breaker, uint32_t connect_timeout,
+    int force_ip_version, int cmap_fd, int votequorum_fd, int local_socket_fd,
+    const struct qdevice_advanced_settings *advanced_settings,
+    int heuristics_pipe_cmd_send_fd, int heuristics_pipe_cmd_recv_fd,
+    int heuristics_pipe_log_recv_fd)
+{
+
+       memset(instance, 0, sizeof(*instance));
+
+       instance->advanced_settings = advanced_settings;
+       instance->decision_algorithm = decision_algorithm;
+       instance->heartbeat_interval = heartbeat_interval;
+       instance->sync_heartbeat_interval = sync_heartbeat_interval;
+       instance->cast_vote_timer_interval = cast_vote_timer_interval;
+       instance->cast_vote_timer = NULL;
+       instance->host_addr = host_addr;
+       instance->host_port = host_port;
+       instance->cluster_name = cluster_name;
+       instance->connect_timeout = connect_timeout;
+       instance->last_msg_seq_num = 1;
+       instance->echo_request_expected_msg_seq_num = 1;
+       instance->echo_reply_received_msg_seq_num = 1;
+       instance->force_ip_version = force_ip_version;
+       instance->last_echo_reply_received_time = ((time_t) -1);
+       instance->connected_since_time = ((time_t) -1);
+
+       memcpy(&instance->tie_breaker, tie_breaker, sizeof(*tie_breaker));
+
+       dynar_init(&instance->receive_buffer, advanced_settings->net_initial_msg_receive_size);
+
+       send_buffer_list_init(&instance->send_buffer_list, advanced_settings->net_max_send_buffers,
+           advanced_settings->net_initial_msg_send_size);
+
+       timer_list_init(&instance->main_timer_list);
+
+       pr_poll_array_init(&instance->poll_array, sizeof(struct qdevice_net_poll_array_user_data));
+
+       instance->tls_supported = tls_supported;
+
+       if ((instance->cmap_poll_fd = PR_CreateSocketPollFd(cmap_fd)) == NULL) {
+               qdevice_log_nss(LOG_CRIT, "Can't create NSPR cmap poll fd");
+               return (-1);
+       }
+
+       if ((instance->votequorum_poll_fd = PR_CreateSocketPollFd(votequorum_fd)) == NULL) {
+               qdevice_log_nss(LOG_CRIT, "Can't create NSPR votequorum poll fd");
+               return (-1);
+       }
+
+       if ((instance->ipc_socket_poll_fd = PR_CreateSocketPollFd(local_socket_fd)) == NULL) {
+               qdevice_log_nss(LOG_CRIT, "Can't create NSPR IPC socket poll fd");
+               return (-1);
+       }
+
+       if ((instance->heuristics_pipe_cmd_send_poll_fd =
+           PR_CreateSocketPollFd(heuristics_pipe_cmd_send_fd)) == NULL) {
+               qdevice_log_nss(LOG_CRIT, "Can't create NSPR heuristics pipe command send poll fd");
+               return (-1);
+       }
+
+       if ((instance->heuristics_pipe_cmd_recv_poll_fd =
+           PR_CreateSocketPollFd(heuristics_pipe_cmd_recv_fd)) == NULL) {
+               qdevice_log_nss(LOG_CRIT, "Can't create NSPR heuristics pipe command recv poll fd");
+               return (-1);
+       }
+
+       if ((instance->heuristics_pipe_log_recv_poll_fd =
+           PR_CreateSocketPollFd(heuristics_pipe_log_recv_fd)) == NULL) {
+               qdevice_log_nss(LOG_CRIT, "Can't create NSPR heuristics pipe log recv poll fd");
+               return (-1);
+       }
+
+       return (0);
+}
+
+void
+qdevice_net_instance_clean(struct qdevice_net_instance *instance)
+{
+
+       dynar_clean(&instance->receive_buffer);
+
+       send_buffer_list_free(&instance->send_buffer_list);
+
+       instance->skipping_msg = 0;
+       instance->msg_already_received_bytes = 0;
+       instance->echo_request_expected_msg_seq_num = instance->echo_reply_received_msg_seq_num;
+       instance->using_tls = 0;
+       instance->tls_client_cert_sent = 0;
+       instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT;
+
+       instance->schedule_disconnect = 0;
+       instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNDEFINED;
+       instance->last_echo_reply_received_time = ((time_t) -1);
+       instance->connected_since_time = ((time_t) -1);
+}
+
+int
+qdevice_net_instance_destroy(struct qdevice_net_instance *instance)
+{
+       struct unix_socket_client *ipc_client;
+       const struct unix_socket_client_list *ipc_client_list;
+       struct qdevice_ipc_user_data *qdevice_ipc_user_data;
+       PRFileDesc *prfd;
+
+       ipc_client_list = &instance->qdevice_instance_ptr->local_ipc.clients;
+
+       TAILQ_FOREACH(ipc_client, ipc_client_list, entries) {
+               qdevice_ipc_user_data = (struct qdevice_ipc_user_data *)ipc_client->user_data;
+               prfd = (PRFileDesc *)qdevice_ipc_user_data->model_data;
+
+               if (PR_DestroySocketPollFd(prfd) != PR_SUCCESS) {
+                       qdevice_log_nss(LOG_WARNING, "Unable to destroy client IPC poll socket fd");
+               }
+       }
+
+       dynar_destroy(&instance->receive_buffer);
+
+       send_buffer_list_free(&instance->send_buffer_list);
+
+       pr_poll_array_destroy(&instance->poll_array);
+
+       timer_list_free(&instance->main_timer_list);
+
+       free((void *)instance->cluster_name);
+       free((void *)instance->host_addr);
+
+       if (PR_DestroySocketPollFd(instance->votequorum_poll_fd) != PR_SUCCESS) {
+               qdevice_log_nss(LOG_WARNING, "Unable to close votequorum connection fd");
+       }
+
+       if (PR_DestroySocketPollFd(instance->cmap_poll_fd) != PR_SUCCESS) {
+               qdevice_log_nss(LOG_WARNING, "Unable to close votequorum connection fd");
+       }
+
+       if (PR_DestroySocketPollFd(instance->ipc_socket_poll_fd) != PR_SUCCESS) {
+               qdevice_log_nss(LOG_WARNING, "Unable to close local socket poll fd");
+       }
+
+       if (PR_DestroySocketPollFd(instance->heuristics_pipe_cmd_send_poll_fd) != PR_SUCCESS) {
+               qdevice_log_nss(LOG_WARNING, "Unable to close heuristics pipe command send poll fd");
+               return (-1);
+       }
+
+       if (PR_DestroySocketPollFd(instance->heuristics_pipe_cmd_recv_poll_fd) != PR_SUCCESS) {
+               qdevice_log_nss(LOG_WARNING, "Unable to close heuristics pipe command recv poll fd");
+               return (-1);
+       }
+
+       if (PR_DestroySocketPollFd(instance->heuristics_pipe_log_recv_poll_fd) != PR_SUCCESS) {
+               qdevice_log_nss(LOG_WARNING, "Unable to close heuristics pipe log recv poll fd");
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_instance_init_from_cmap(struct qdevice_instance *instance)
+{
+       char *str;
+       cmap_handle_t cmap_handle;
+       enum tlv_tls_supported tls_supported;
+       int i;
+       long int li;
+       enum tlv_decision_algorithm_type decision_algorithm;
+       struct tlv_tie_breaker tie_breaker;
+       uint32_t heartbeat_interval;
+       uint32_t sync_heartbeat_interval;
+       uint32_t cast_vote_timer_interval;
+       char *host_addr;
+       int host_port;
+       char *ep;
+       char *cluster_name;
+       uint32_t connect_timeout;
+       struct qdevice_net_instance *net_instance;
+       int force_ip_version;
+
+       cmap_handle = instance->cmap_handle;
+
+       net_instance = malloc(sizeof(*net_instance));
+       if (net_instance == NULL) {
+               qdevice_log(LOG_ERR, "Can't alloc qdevice_net_instance");
+               return (-1);
+       }
+
+       /*
+        * Check tls
+        */
+       tls_supported = QDEVICE_NET_DEFAULT_TLS_SUPPORTED;
+
+       if (cmap_get_string(cmap_handle, "quorum.device.net.tls", &str) == CS_OK) {
+               if ((i = utils_parse_bool_str(str)) == -1) {
+                       if (strcasecmp(str, "required") != 0) {
+                               free(str);
+                               qdevice_log(LOG_ERR, "quorum.device.net.tls value is not valid.");
+
+                               goto error_free_instance;
+                       } else {
+                               tls_supported = TLV_TLS_REQUIRED;
+                       }
+               } else {
+                       if (i == 1) {
+                               tls_supported = TLV_TLS_SUPPORTED;
+                       } else {
+                               tls_supported = TLV_TLS_UNSUPPORTED;
+                       }
+               }
+
+               free(str);
+       }
+
+       /*
+        * Host
+        */
+       if (cmap_get_string(cmap_handle, "quorum.device.net.host", &str) != CS_OK) {
+               qdevice_log(LOG_ERR, "Qdevice net daemon address is not defined (quorum.device.net.host)");
+               goto error_free_instance;
+       }
+       host_addr = str;
+
+       if (cmap_get_string(cmap_handle, "quorum.device.net.port", &str) == CS_OK) {
+               host_port = strtol(str, &ep, 10);
+
+               if (host_port <= 0 || host_port > ((uint16_t)~0) || *ep != '\0') {
+                       qdevice_log(LOG_ERR, "quorum.device.net.port must be in range 0-65535");
+                       free(str);
+                       goto error_free_host_addr;
+               }
+               free(str);
+       } else {
+               host_port = QNETD_DEFAULT_HOST_PORT;
+       }
+
+       /*
+        * Cluster name
+        */
+       if (cmap_get_string(cmap_handle, "totem.cluster_name", &str) != CS_OK) {
+               qdevice_log(LOG_ERR, "Cluster name (totem.cluster_name) has to be defined.");
+               goto error_free_host_addr;
+       }
+       cluster_name = str;
+
+       /*
+        * Adjust qdevice timeouts to better suit qnetd
+        */
+       cast_vote_timer_interval = instance->heartbeat_interval * 0.5;
+       heartbeat_interval = instance->heartbeat_interval * 0.8;
+       if (heartbeat_interval < instance->advanced_settings->net_heartbeat_interval_min) {
+               qdevice_log(LOG_WARNING, "Heartbeat interval too small %"PRIu32". Adjusting to %"PRIu32".",
+                   heartbeat_interval, instance->advanced_settings->net_heartbeat_interval_min);
+               heartbeat_interval = instance->advanced_settings->net_heartbeat_interval_min;
+       }
+       if (heartbeat_interval > instance->advanced_settings->net_heartbeat_interval_max) {
+               qdevice_log(LOG_WARNING, "Heartbeat interval too big %"PRIu32". Adjusting to %"PRIu32".",
+                   heartbeat_interval, instance->advanced_settings->net_heartbeat_interval_max);
+               heartbeat_interval = instance->advanced_settings->net_heartbeat_interval_max;
+       }
+       sync_heartbeat_interval = instance->sync_heartbeat_interval * 0.8;
+
+       /*
+        * Choose decision algorithm
+        */
+       if (cmap_get_string(cmap_handle, "quorum.device.net.algorithm", &str) != CS_OK) {
+               decision_algorithm = QDEVICE_NET_DEFAULT_ALGORITHM;
+       } else {
+               if (strcmp(str, "test") == 0) {
+                       decision_algorithm = TLV_DECISION_ALGORITHM_TYPE_TEST;
+               } else if (strcmp(str, "ffsplit") == 0) {
+                       decision_algorithm = TLV_DECISION_ALGORITHM_TYPE_FFSPLIT;
+               } else if (strcmp(str, "2nodelms") == 0) {
+                       decision_algorithm = TLV_DECISION_ALGORITHM_TYPE_2NODELMS;
+               } else if (strcmp(str, "lms") == 0) {
+                       decision_algorithm = TLV_DECISION_ALGORITHM_TYPE_LMS;
+               } else {
+                       qdevice_log(LOG_ERR, "Unknown decision algorithm %s", str);
+                       free(str);
+                       goto error_free_cluster_name;
+               }
+
+               free(str);
+       }
+
+       if (decision_algorithm == TLV_DECISION_ALGORITHM_TYPE_TEST &&
+           !instance->advanced_settings->net_test_algorithm_enabled) {
+               qdevice_log(LOG_ERR, "Test algorithm is not enabled. You can force enable it by "
+                   "passing -S net_test_algorithm_enabled=on to %s command", QDEVICE_PROGRAM_NAME);
+
+               goto error_free_cluster_name;
+       }
+       /*
+        * Load tie_breaker mode
+        */
+       memset(&tie_breaker, 0, sizeof(tie_breaker));
+
+       if (cmap_get_string(cmap_handle, "quorum.device.net.tie_breaker", &str) != CS_OK) {
+               tie_breaker.mode = QDEVICE_NET_DEFAULT_TIE_BREAKER_MODE;
+       } else {
+               if (strcmp(str, "lowest") == 0) {
+                       tie_breaker.mode = TLV_TIE_BREAKER_MODE_LOWEST;
+               } else if (strcmp(str, "highest") == 0) {
+                       tie_breaker.mode = TLV_TIE_BREAKER_MODE_HIGHEST;
+               } else {
+                       li = strtol(str, &ep, 10);
+                       if (li <= 0 || li > ((uint32_t)~0) || *ep != '\0') {
+                               qdevice_log(LOG_ERR, "tie_breaker must be lowest|highest|valid_node_id");
+                               free(str);
+                               goto error_free_cluster_name;
+                       }
+
+                       tie_breaker.mode = TLV_TIE_BREAKER_MODE_NODE_ID;
+                       tie_breaker.node_id = li;
+               }
+
+               free(str);
+       }
+
+       /*
+        * Get connect timeout
+        */
+       if (cmap_get_string(cmap_handle, "quorum.device.net.connect_timeout", &str) != CS_OK) {
+               connect_timeout = heartbeat_interval;
+       } else {
+               li = strtol(str, &ep, 10);
+               if (li < instance->advanced_settings->net_min_connect_timeout ||
+                   li > instance->advanced_settings->net_max_connect_timeout || *ep != '\0') {
+                       qdevice_log(LOG_ERR, "connect_timeout must be valid number in "
+                           "range <%"PRIu32",%"PRIu32">",
+                           instance->advanced_settings->net_min_connect_timeout,
+                           instance->advanced_settings->net_max_connect_timeout);
+                       free(str);
+                       goto error_free_cluster_name;
+               }
+
+               connect_timeout = li;
+
+               free(str);
+       }
+
+       if (cmap_get_string(cmap_handle, "quorum.device.net.force_ip_version", &str) != CS_OK) {
+               force_ip_version = 0;
+       } else {
+               li = strtol(str, &ep, 10);
+               if ((li != 0 && li != 4 && li != 6) || *ep != '\0') {
+                       qdevice_log(LOG_ERR, "force_ip_version must be one of 0|4|6");
+                       free(str);
+                       goto error_free_cluster_name;
+               }
+
+               force_ip_version = li;
+
+               free(str);
+       }
+
+       /*
+        * Really initialize instance
+        */
+       if (qdevice_net_instance_init(net_instance,
+           tls_supported, decision_algorithm,
+           heartbeat_interval, sync_heartbeat_interval, cast_vote_timer_interval,
+           host_addr, host_port, cluster_name, &tie_breaker, connect_timeout,
+           force_ip_version,
+           instance->cmap_poll_fd, instance->votequorum_poll_fd,
+           instance->local_ipc.socket, instance->advanced_settings,
+           instance->heuristics_instance.pipe_cmd_send,
+           instance->heuristics_instance.pipe_cmd_recv,
+           instance->heuristics_instance.pipe_log_recv) == -1) {
+               qdevice_log(LOG_ERR, "Can't initialize qdevice-net instance");
+               goto error_free_instance;
+       }
+
+       net_instance->qdevice_instance_ptr = instance;
+       instance->model_data = net_instance;
+
+       return (0);
+
+error_free_cluster_name:
+       free(cluster_name);
+error_free_host_addr:
+       free(host_addr);
+error_free_instance:
+       free(net_instance);
+       return (-1);
+}
diff --git a/qdevices/qdevice-net-instance.h b/qdevices/qdevice-net-instance.h
new file mode 100644 (file)
index 0000000..0b2c40c
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_INSTANCE_H_
+#define _QDEVICE_NET_INSTANCE_H_
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "nss-sock.h"
+
+#include "qdevice-instance.h"
+
+#include "dynar.h"
+#include "node-list.h"
+#include "pr-poll-array.h"
+#include "qdevice-net-disconnect-reason.h"
+#include "send-buffer-list.h"
+#include "tlv.h"
+#include "timer-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum qdevice_net_instance_state {
+       QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT,
+       QDEVICE_NET_INSTANCE_STATE_SENDING_PREINIT_REPLY,
+       QDEVICE_NET_INSTANCE_STATE_WAITING_PREINIT_REPLY,
+       QDEVICE_NET_INSTANCE_STATE_WAITING_STARTTLS_BEING_SENT,
+       QDEVICE_NET_INSTANCE_STATE_WAITING_INIT_REPLY,
+       QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS,
+};
+
+struct qdevice_net_instance {
+       PRFileDesc *socket;
+       struct dynar receive_buffer;
+       struct send_buffer_list send_buffer_list;
+       int skipping_msg;
+       size_t msg_already_received_bytes;
+       enum qdevice_net_instance_state state;
+       uint32_t last_msg_seq_num;
+       uint32_t echo_request_expected_msg_seq_num;
+       uint32_t echo_reply_received_msg_seq_num;
+       enum tlv_tls_supported tls_supported;
+       int using_tls;
+       int tls_client_cert_sent;
+       uint32_t heartbeat_interval;            /* Adjusted heartbeat interval during normal operation */
+       uint32_t sync_heartbeat_interval;       /* Adjusted heartbeat interval during corosync sync */
+       uint32_t cast_vote_timer_interval;      /* Timer for cast vote */
+       uint32_t connect_timeout;
+       struct timer_list_entry *cast_vote_timer;
+       enum tlv_vote cast_vote_timer_vote;
+       int cast_vote_timer_paused;
+       const char *host_addr;
+       uint16_t host_port;
+       const char *cluster_name;
+       enum tlv_decision_algorithm_type decision_algorithm;
+       struct timer_list main_timer_list;
+       struct timer_list_entry *echo_request_timer;
+       int schedule_disconnect;
+       PRFileDesc *votequorum_poll_fd;
+       PRFileDesc *cmap_poll_fd;
+       PRFileDesc *ipc_socket_poll_fd;
+       struct tlv_ring_id last_sent_ring_id;
+       struct tlv_tie_breaker tie_breaker;
+       void *algorithm_data;
+       enum qdevice_net_disconnect_reason disconnect_reason;
+       struct qdevice_instance *qdevice_instance_ptr;
+       struct nss_sock_non_blocking_client non_blocking_client;
+       struct timer_list_entry *connect_timer;
+       int force_ip_version;
+       struct pr_poll_array poll_array;
+       time_t last_echo_reply_received_time;
+       time_t connected_since_time;
+       const struct qdevice_advanced_settings *advanced_settings;
+       PRFileDesc *heuristics_pipe_cmd_send_poll_fd;
+       PRFileDesc *heuristics_pipe_cmd_recv_poll_fd;
+       PRFileDesc *heuristics_pipe_log_recv_poll_fd;
+       struct timer_list_entry *regular_heuristics_timer;
+       int server_supports_heuristics;
+       enum tlv_heuristics latest_regular_heuristics_result;
+       enum tlv_heuristics latest_connect_heuristics_result;
+       enum tlv_heuristics latest_vq_heuristics_result;
+       enum tlv_heuristics latest_heuristics_result;
+};
+
+extern int             qdevice_net_instance_init(struct qdevice_net_instance *instance,
+    enum tlv_tls_supported tls_supported,
+    enum tlv_decision_algorithm_type decision_algorithm, uint32_t heartbeat_interval,
+    uint32_t sync_heartbeat_interval, uint32_t cast_vote_timer_interval,
+    const char *host_addr, uint16_t host_port, const char *cluster_name,
+    const struct tlv_tie_breaker *tie_breaker, uint32_t connect_timeout, int force_ip_version,
+    int cmap_fd, int votequorum_fd, int local_socket_fd,
+    const struct qdevice_advanced_settings *advanced_settings,
+    int heuristics_pipe_cmd_send_fd, int heuristics_pipe_cmd_recv_fd,
+    int heuristics_pipe_log_recv_fd);
+
+extern void            qdevice_net_instance_clean(struct qdevice_net_instance *instance);
+
+extern int             qdevice_net_instance_destroy(struct qdevice_net_instance *instance);
+
+extern int             qdevice_net_instance_init_from_cmap(struct qdevice_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_INSTANCE_H_ */
diff --git a/qdevices/qdevice-net-ipc-cmd.c b/qdevices/qdevice-net-ipc-cmd.c
new file mode 100644 (file)
index 0000000..0c7a3d9
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-net-ipc-cmd.h"
+#include "qdevice-log.h"
+#include "dynar-str.h"
+#include "qdevice-net-algorithm.h"
+#include "utils.h"
+
+static int
+qdevice_net_ipc_cmd_status_add_header(struct dynar *outbuf, int verbose)
+{
+
+       return ((dynar_str_catf(outbuf, "Qdevice-net information\n") != -1) &&
+           (dynar_str_catf(outbuf, "----------------------\n") != -1));
+}
+
+static int
+qdevice_net_ipc_cmd_status_add_tie_breaker(struct qdevice_net_instance *instance,
+    struct dynar *outbuf, int verbose)
+{
+
+       if (dynar_str_catf(outbuf, "Tie-breaker:\t\t") == -1) {
+               return (0);
+       }
+
+       switch (instance->tie_breaker.mode) {
+       case TLV_TIE_BREAKER_MODE_LOWEST:
+               if (dynar_str_catf(outbuf, "Node with lowest node ID") == -1) {
+                       return (0);
+               }
+               break;
+       case TLV_TIE_BREAKER_MODE_HIGHEST:
+               if (dynar_str_catf(outbuf, "Node with highest node ID") == -1) {
+                       return (0);
+               }
+               break;
+       case TLV_TIE_BREAKER_MODE_NODE_ID:
+               if (dynar_str_catf(outbuf, "Node with node ID "UTILS_PRI_NODE_ID,
+                   instance->tie_breaker.node_id) == -1) {
+                       return (0);
+               }
+               break;
+       }
+
+       return (dynar_str_catf(outbuf, "\n") != -1);
+}
+
+static int
+qdevice_net_ipc_cmd_status_add_basic_info(struct qdevice_net_instance *instance,
+    struct dynar *outbuf, int verbose)
+{
+
+       if (dynar_str_catf(outbuf, "Cluster name:\t\t%s\n", instance->cluster_name) == -1) {
+               return (0);
+       }
+
+       if (dynar_str_catf(outbuf, "QNetd host:\t\t%s:%"PRIu16"\n",
+           instance->host_addr, instance->host_port) == -1) {
+               return (0);
+       }
+
+       if (verbose && instance->force_ip_version != 0) {
+               if (dynar_str_catf(outbuf, "Force IP version:\t%u\n",
+                   instance->force_ip_version) == -1) {
+                       return (0);
+               }
+       }
+
+       if (verbose) {
+               if ((dynar_str_catf(outbuf, "Connect timeout:\t%"PRIu32"ms\n",
+                   instance->connect_timeout) == -1) ||
+                   (dynar_str_catf(outbuf, "HB interval:\t\t%"PRIu32"ms\n",
+                   instance->heartbeat_interval) == -1) ||
+                   (dynar_str_catf(outbuf, "VQ vote timer interval:\t%"PRIu32"ms\n",
+                   instance->cast_vote_timer_interval) == -1)) {
+                       return (0);
+               }
+
+               if (dynar_str_catf(outbuf, "TLS:\t\t\t%s\n",
+                   tlv_tls_supported_to_str(instance->tls_supported)) == -1) {
+                       return (0);
+               }
+       }
+
+       if (dynar_str_catf(outbuf, "Algorithm:\t\t%s\n",
+           tlv_decision_algorithm_type_to_str(instance->decision_algorithm)) == -1) {
+               return (0);
+       }
+
+       return (1);
+}
+
+static int
+qdevice_net_ipc_cmd_status_add_poll_timer_status(struct qdevice_net_instance *instance,
+    struct dynar *outbuf, int verbose)
+{
+
+       if (!verbose) {
+               return (1);
+       }
+
+       if (dynar_str_catf(outbuf, "Poll timer running:\t%s",
+           (instance->cast_vote_timer != NULL ? "Yes" : "No")) == -1) {
+               return (0);
+       }
+
+       if (instance->cast_vote_timer != NULL && instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
+               if (dynar_str_catf(outbuf, " (cast vote)") == -1) {
+                       return (0);
+               }
+       }
+
+       return (dynar_str_catf(outbuf, "\n") != -1);
+}
+
+static int
+qdevice_net_ipc_cmd_status_add_state(struct qdevice_net_instance *instance,
+    struct dynar *outbuf, int verbose)
+{
+       const char *state;
+
+       if (instance->schedule_disconnect) {
+               state = "Disconnected";
+       } else {
+               if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+                       state = "Connected";
+               } else {
+                       if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT ||
+                           !instance->non_blocking_client.destroyed) {
+                               state = "Connecting";
+                       } else {
+                               state = "Connect failed";
+                       }
+               }
+       }
+
+       return (dynar_str_catf(outbuf, "State:\t\t\t%s\n", state) != -1);
+}
+
+static int
+qdevice_net_ipc_cmd_status_add_heuristics(struct qdevice_net_instance *instance,
+    struct dynar *outbuf, int verbose)
+{
+       enum qdevice_heuristics_mode active_heuristics_mode;
+       int heuristics_enabled;
+
+       active_heuristics_mode = instance->qdevice_instance_ptr->heuristics_instance.mode;
+       heuristics_enabled = (active_heuristics_mode == QDEVICE_HEURISTICS_MODE_ENABLED ||
+           active_heuristics_mode == QDEVICE_HEURISTICS_MODE_SYNC);
+
+       if (!heuristics_enabled) {
+               return (1);
+       }
+
+       if (dynar_str_catf(outbuf, "Heuristics result:\t%s",
+           tlv_heuristics_to_str(instance->latest_heuristics_result)) == -1) {
+               return (0);
+       }
+
+       if (verbose) {
+               if (dynar_str_catf(outbuf, " (regular: %s, membership: %s, connect: %s)",
+                   tlv_heuristics_to_str(instance->latest_regular_heuristics_result),
+                   tlv_heuristics_to_str(instance->latest_vq_heuristics_result),
+                   tlv_heuristics_to_str(instance->latest_connect_heuristics_result)) == -1) {
+                       return (0);
+               }
+       }
+
+       return (dynar_str_catf(outbuf, "\n") != -1);
+}
+
+static int
+qdevice_net_ipc_cmd_status_add_tls_state(struct qdevice_net_instance *instance,
+    struct dynar *outbuf, int verbose)
+{
+
+       if (!verbose || instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               return (1);
+       }
+
+       if (dynar_str_catf(outbuf, "TLS active:\t\t%s", (instance->using_tls ? "Yes" : "No")) == -1) {
+               return (0);
+       }
+
+       if (instance->using_tls && instance->tls_client_cert_sent) {
+               if (dynar_str_catf(outbuf, " (client certificate sent)") == -1) {
+                       return (0);
+               }
+       }
+
+       return (dynar_str_catf(outbuf, "\n") != -1);
+}
+
+static int
+qdevice_net_ipc_cmd_status_add_times(struct qdevice_net_instance *instance,
+    struct dynar *outbuf, int verbose)
+{
+       struct tm tm_res;
+
+       if (!verbose || instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               return (1);
+       }
+
+       if (instance->connected_since_time != ((time_t) -1)) {
+               localtime_r(&instance->connected_since_time, &tm_res);
+               if (dynar_str_catf(outbuf, "Connected since:\t%04d-%02d-%02dT%02d:%02d:%02d\n",
+                   tm_res.tm_year + 1900, tm_res.tm_mon + 1, tm_res.tm_mday,
+                   tm_res.tm_hour, tm_res.tm_min, tm_res.tm_sec) == -1) {
+                       return (0);
+               }
+       }
+
+       if (instance->last_echo_reply_received_time != ((time_t) -1)) {
+               localtime_r(&instance->last_echo_reply_received_time, &tm_res);
+               if (dynar_str_catf(outbuf, "Echo reply received:\t%04d-%02d-%02dT%02d:%02d:%02d\n",
+                   tm_res.tm_year + 1900, tm_res.tm_mon + 1, tm_res.tm_mday,
+                   tm_res.tm_hour, tm_res.tm_min, tm_res.tm_sec) == -1) {
+                       return (0);
+               }
+       }
+
+       return (1);
+}
+
+int
+qdevice_net_ipc_cmd_status(struct qdevice_net_instance *instance, struct dynar *outbuf, int verbose)
+{
+
+       if (qdevice_net_ipc_cmd_status_add_header(outbuf, verbose) &&
+           qdevice_net_ipc_cmd_status_add_basic_info(instance, outbuf, verbose) &&
+           qdevice_net_ipc_cmd_status_add_tie_breaker(instance, outbuf, verbose) &&
+           qdevice_net_ipc_cmd_status_add_poll_timer_status(instance, outbuf, verbose) &&
+           qdevice_net_ipc_cmd_status_add_state(instance, outbuf, verbose) &&
+           qdevice_net_ipc_cmd_status_add_heuristics(instance, outbuf, verbose) &&
+           qdevice_net_ipc_cmd_status_add_tls_state(instance, outbuf, verbose) &&
+           qdevice_net_ipc_cmd_status_add_times(instance, outbuf, verbose)) {
+               return (1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-net-ipc-cmd.h b/qdevices/qdevice-net-ipc-cmd.h
new file mode 100644 (file)
index 0000000..594f2ef
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_IPC_CMD_H_
+#define _QDEVICE_NET_IPC_CMD_H_
+
+#include "dynar.h"
+#include "qdevice-net-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qdevice_net_ipc_cmd_status(struct qdevice_net_instance *instance,
+    struct dynar *outbuf, int verbose);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_IPC_CMD_H_ */
diff --git a/qdevices/qdevice-net-msg-received.c b/qdevices/qdevice-net-msg-received.c
new file mode 100644 (file)
index 0000000..38a1906
--- /dev/null
@@ -0,0 +1,983 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-log.h"
+#include "qdevice-net-algorithm.h"
+#include "qdevice-net-cast-vote-timer.h"
+#include "qdevice-net-heuristics.h"
+#include "qdevice-net-msg-received.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-votequorum.h"
+#include "qdevice-net-echo-request-timer.h"
+#include "msg.h"
+#include "utils.h"
+
+/*
+ * -1 - Incompatible tls combination
+ *  0 - Don't use TLS
+ *  1 - Use TLS
+ */
+static int
+qdevice_net_msg_received_check_tls_compatibility(enum tlv_tls_supported server_tls,
+    enum tlv_tls_supported client_tls)
+{
+       int res;
+
+       res = -1;
+
+       switch (server_tls) {
+       case TLV_TLS_UNSUPPORTED:
+               switch (client_tls) {
+               case TLV_TLS_UNSUPPORTED: res = 0; break;
+               case TLV_TLS_SUPPORTED: res = 0; break;
+               case TLV_TLS_REQUIRED: res = -1; break;
+               }
+               break;
+       case TLV_TLS_SUPPORTED:
+               switch (client_tls) {
+               case TLV_TLS_UNSUPPORTED: res = 0; break;
+               case TLV_TLS_SUPPORTED: res = 1; break;
+               case TLV_TLS_REQUIRED: res = 1; break;
+               }
+               break;
+       case TLV_TLS_REQUIRED:
+               switch (client_tls) {
+               case TLV_TLS_UNSUPPORTED: res = -1; break;
+               case TLV_TLS_SUPPORTED: res = 1; break;
+               case TLV_TLS_REQUIRED: res = 1; break;
+               }
+               break;
+       }
+
+       return (res);
+}
+
+static void
+qdevice_net_msg_received_log_msg_decode_error(int ret)
+{
+
+       switch (ret) {
+       case -1:
+               qdevice_log(LOG_WARNING, "Received message with option with invalid length");
+               break;
+       case -2:
+               qdevice_log(LOG_CRIT, "Can't allocate memory");
+               break;
+       case -3:
+               qdevice_log(LOG_WARNING, "Received inconsistent msg (tlv len > msg size)");
+               break;
+       case -4:
+               qdevice_log(LOG_ERR, "Received message with option with invalid value");
+               break;
+       default:
+               qdevice_log(LOG_ERR, "Unknown error occurred when decoding message");
+               break;
+       }
+}
+
+static int
+qdevice_net_msg_received_unexpected_msg(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg, const char *msg_str)
+{
+
+       qdevice_log(LOG_ERR, "Received unexpected %s message. Disconnecting from server",
+           msg_str);
+
+       instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG;
+
+       return (-1);
+}
+
+static int
+qdevice_net_msg_received_init(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       return (qdevice_net_msg_received_unexpected_msg(instance, msg, "init"));
+}
+
+static int
+qdevice_net_msg_received_preinit(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       return (qdevice_net_msg_received_unexpected_msg(instance, msg, "preinit"));
+}
+
+static int
+qdevice_net_msg_check_seq_number(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       if (!msg->seq_number_set || msg->seq_number != instance->last_msg_seq_num) {
+               qdevice_log(LOG_ERR, "Received message doesn't contain seq_number or "
+                   "it's not expected one.");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+qdevice_net_msg_received_preinit_reply(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+       int res;
+       struct send_buffer_list_entry *send_buffer;
+
+       qdevice_log(LOG_DEBUG, "Received preinit reply msg");
+
+       if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_PREINIT_REPLY) {
+               qdevice_log(LOG_ERR, "Received unexpected preinit reply message. "
+                   "Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG;
+
+               return (-1);
+       }
+
+       if (qdevice_net_msg_check_seq_number(instance, msg) != 0) {
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+
+               return (-1);
+       }
+
+       /*
+        * Check TLS support
+        */
+       if (!msg->tls_supported_set || !msg->tls_client_cert_required_set) {
+               qdevice_log(LOG_ERR, "Required tls_supported or tls_client_cert_required "
+                   "option is unset");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+
+               return (-1);
+       }
+
+       res = qdevice_net_msg_received_check_tls_compatibility(msg->tls_supported, instance->tls_supported);
+       if (res == -1) {
+               qdevice_log(LOG_ERR, "Incompatible tls configuration (server %u client %u)",
+                   msg->tls_supported, instance->tls_supported);
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_TLS;
+
+               return (-1);
+       } else if (res == 1) {
+               /*
+                * Start TLS
+                */
+               send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+               if (send_buffer == NULL) {
+                       qdevice_log(LOG_ERR, "Can't allocate send list buffer for "
+                           "starttls msg");
+
+                       instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+
+                       return (-1);
+               }
+
+               instance->last_msg_seq_num++;
+               if (msg_create_starttls(&send_buffer->buffer, 1,
+                   instance->last_msg_seq_num) == 0) {
+                       qdevice_log(LOG_ERR, "Can't allocate send buffer for starttls msg");
+
+                       instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+
+                       send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+                       return (-1);
+               }
+
+               send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+               instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_STARTTLS_BEING_SENT;
+       } else if (res == 0) {
+               if (qdevice_net_send_init(instance) != 0) {
+                       instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+static int
+qdevice_net_msg_received_init_reply(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+       size_t zi;
+       int res;
+       enum qdevice_heuristics_mode active_heuristics_mode;
+
+       qdevice_log(LOG_DEBUG, "Received init reply msg");
+
+       if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_INIT_REPLY) {
+               qdevice_log(LOG_ERR, "Received unexpected init reply message. "
+                   "Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG;
+
+               return (-1);
+       }
+
+       if (qdevice_net_msg_check_seq_number(instance, msg) != 0) {
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+
+               return (-1);
+       }
+
+       if (!msg->reply_error_code_set) {
+               qdevice_log(LOG_ERR, "Received init reply message without error code."
+                   "Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+
+               return (-1);
+       }
+
+       if (msg->reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               qdevice_log(LOG_ERR, "Received init reply message with error code %"PRIu16". "
+                   "Disconnecting from server", msg->reply_error_code);
+
+               if (msg->reply_error_code == TLV_REPLY_ERROR_CODE_DUPLICATE_NODE_ID) {
+                       qdevice_log(LOG_ERR, "Duplicate node id may be result of server not yet "
+                           "accepted this node disconnect. Retry again.");
+                       instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_DUPLICATE_NODE_ID_ERROR;
+               } else if (msg->reply_error_code == TLV_REPLY_ERROR_CODE_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES) {
+                       qdevice_log(LOG_ERR, "Configured tie-breaker differs in cluster. This may be "
+                           "result of server not yet accepted this node disconnect. Retry again.");
+                       instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES_ERROR;
+               } else if (msg->reply_error_code == TLV_REPLY_ERROR_CODE_ALGORITHM_DIFFERS_FROM_OTHER_NODES) {
+                       qdevice_log(LOG_ERR, "Configured algorithm differs in cluster. This may be "
+                           "result of server not yet accepted this node disconnect. Retry again.");
+                       instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ALGORITHM_DIFFERS_FROM_OTHER_NODES_ERROR;
+               } else {
+                       instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ERROR;
+               }
+
+               return (-1);
+       }
+
+       if (!msg->server_maximum_request_size_set || !msg->server_maximum_reply_size_set) {
+               qdevice_log(LOG_ERR, "Required maximum_request_size or maximum_reply_size "
+                   "option is unset");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+
+               return (-1);
+       }
+
+       if (msg->supported_messages == NULL || msg->supported_options == NULL) {
+               qdevice_log(LOG_ERR, "Required supported messages or supported options "
+                   "option is unset");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+
+               return (-1);
+       }
+
+       if (msg->supported_decision_algorithms == NULL) {
+               qdevice_log(LOG_ERR, "Required supported decision algorithms option is unset");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+
+               return (-1);
+       }
+
+       if (msg->server_maximum_request_size < instance->advanced_settings->net_min_msg_send_size) {
+               qdevice_log(LOG_ERR,
+                   "Server accepts maximum %zu bytes message but this client minimum "
+                   "is %zu bytes.", msg->server_maximum_request_size,
+                   instance->advanced_settings->net_min_msg_send_size);
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_MSG_SIZE;
+               return (-1);
+       }
+
+       if (msg->server_maximum_reply_size > instance->advanced_settings->net_max_msg_receive_size) {
+               qdevice_log(LOG_ERR,
+                   "Server may send message up to %zu bytes message but this client maximum "
+                   "is %zu bytes.", msg->server_maximum_reply_size,
+                   instance->advanced_settings->net_max_msg_receive_size);
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_MSG_SIZE;
+               return (-1);
+       }
+
+       /*
+        * Change buffer sizes
+        */
+       dynar_set_max_size(&instance->receive_buffer, msg->server_maximum_reply_size);
+       send_buffer_list_set_max_buffer_size(&instance->send_buffer_list,
+           msg->server_maximum_request_size);
+
+
+       /*
+        * Check if server supports decision algorithm we need
+        */
+       res = 0;
+
+       for (zi = 0; zi < msg->no_supported_decision_algorithms && !res; zi++) {
+               if (msg->supported_decision_algorithms[zi] == instance->decision_algorithm) {
+                       res = 1;
+               }
+       }
+
+       if (!res) {
+               qdevice_log(LOG_ERR, "Server doesn't support required decision algorithm");
+
+               instance->disconnect_reason =
+                   QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_ALGORITHM;
+
+               return (-1);
+       }
+
+       /*
+        * Check if server supports heuristics
+        */
+       res = 0;
+       for (zi = 0; zi < msg->no_supported_options; zi++) {
+               if (msg->supported_options[zi] == TLV_OPT_HEURISTICS) {
+                       res = 1;
+               }
+       }
+
+       instance->server_supports_heuristics = res;
+
+       if (!res) {
+               active_heuristics_mode = instance->qdevice_instance_ptr->heuristics_instance.mode;
+
+               if (active_heuristics_mode == QDEVICE_HEURISTICS_MODE_ENABLED ||
+                   active_heuristics_mode == QDEVICE_HEURISTICS_MODE_SYNC) {
+                       qdevice_log(LOG_ERR, "Heuristics are enabled but not supported by server");
+
+                       instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT;
+
+                       return (-1);
+               }
+       }
+
+       /*
+        * Finally fully connected so it's possible to remove connection timer
+        */
+       if (instance->connect_timer != NULL) {
+               timer_list_delete(&instance->main_timer_list, instance->connect_timer);
+               instance->connect_timer = NULL;
+       }
+
+       /*
+        * Server accepted heartbeat interval -> schedule regular sending of echo request
+        */
+       if (qdevice_net_echo_request_timer_schedule(instance) != 0) {
+               return (-1);
+       }
+
+       /*
+        * Run heuristics (even when it is disabled, undefined result is ok, rest of sending
+        * is handled by qdevice_net_connect_heuristics_exec_result_callback
+        */
+       if (qdevice_net_heuristics_exec_after_connect(instance) != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+qdevice_net_msg_received_starttls(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       return (qdevice_net_msg_received_unexpected_msg(instance, msg, "starttls"));
+}
+
+static int
+qdevice_net_msg_received_server_error(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       if (!msg->reply_error_code_set) {
+               qdevice_log(LOG_ERR, "Received server error without error code set. "
+                   "Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+       } else {
+               qdevice_log(LOG_ERR, "Received server error %"PRIu16". "
+                   "Disconnecting from server", msg->reply_error_code);
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ERROR;
+       }
+
+       return (-1);
+}
+
+static int
+qdevice_net_msg_received_set_option(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       return (qdevice_net_msg_received_unexpected_msg(instance, msg, "set option"));
+}
+
+static int
+qdevice_net_msg_received_set_option_reply(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               qdevice_log(LOG_ERR, "Received unexpected set option reply message. "
+                   "Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG;
+
+               return (-1);
+       }
+
+       if (qdevice_net_msg_check_seq_number(instance, msg) != 0) {
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+
+               return (-1);
+       }
+
+       if (qdevice_net_echo_request_timer_schedule(instance) != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+qdevice_net_msg_received_echo_request(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       return (qdevice_net_msg_received_unexpected_msg(instance, msg, "echo request"));
+}
+
+static int
+qdevice_net_msg_received_echo_reply(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       if (!msg->seq_number_set) {
+               qdevice_log(LOG_ERR, "Received echo reply message doesn't contain seq_number.");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+               return (-1);
+       }
+
+       if (msg->seq_number != instance->echo_request_expected_msg_seq_num) {
+               qdevice_log(LOG_WARNING, "Received echo reply message seq_number is not expected one.");
+       }
+
+       if (qdevice_net_algorithm_echo_reply_received(instance, msg->seq_number,
+           msg->seq_number == instance->echo_request_expected_msg_seq_num) != 0) {
+               qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_RECEIVED_ERR;
+               return (-1);
+       }
+
+       instance->echo_reply_received_msg_seq_num = msg->seq_number;
+       instance->last_echo_reply_received_time = time(NULL);
+
+       return (0);
+}
+
+static int
+qdevice_net_msg_received_node_list(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       return (qdevice_net_msg_received_unexpected_msg(instance, msg, "node list"));
+}
+
+static int
+qdevice_net_msg_received_node_list_reply(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+       const char *str;
+       enum tlv_vote result_vote;
+       int res;
+       int case_processed;
+       int ring_id_is_valid;
+
+       if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               qdevice_log(LOG_ERR, "Received unexpected node list reply message. "
+                   "Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG;
+               return (-1);
+       }
+
+       if (!msg->vote_set || !msg->seq_number_set || !msg->node_list_type_set) {
+               qdevice_log(LOG_ERR, "Received node list reply message without "
+                   "required options. Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+               return (-1);
+       }
+
+       if (!msg->ring_id_set) {
+               qdevice_log(LOG_ERR, "Received node list reply message "
+                   "without ring id set. Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+               return (-1);
+       }
+
+       str = NULL;
+
+       switch (msg->node_list_type) {
+       case TLV_NODE_LIST_TYPE_INITIAL_CONFIG: str = "initial config"; break;
+       case TLV_NODE_LIST_TYPE_CHANGED_CONFIG: str = "changed config"; break;
+       case TLV_NODE_LIST_TYPE_MEMBERSHIP: str ="membership"; break;
+       case TLV_NODE_LIST_TYPE_QUORUM: str ="quorum"; break;
+       /*
+        * Default is not defined intentionally. Compiler shows warning when new node list type
+        * is added
+        */
+       }
+
+       if (str == NULL) {
+               qdevice_log(LOG_CRIT, "qdevice_net_msg_received_node_list_reply fatal error. "
+                   "Unhandled node_list_type (debug output)");
+               exit(1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Received %s node list reply", str);
+       qdevice_log(LOG_DEBUG, "  seq = "UTILS_PRI_MSG_SEQ, msg->seq_number);
+       qdevice_log(LOG_DEBUG, "  vote = %s", tlv_vote_to_str(msg->vote));
+       qdevice_log(LOG_DEBUG, "  ring id = ("UTILS_PRI_RING_ID")",
+                   msg->ring_id.node_id, msg->ring_id.seq);
+
+       /*
+        * Call algorithm
+        */
+       result_vote = msg->vote;
+
+       if (!tlv_ring_id_eq(&msg->ring_id, &instance->last_sent_ring_id)) {
+               ring_id_is_valid = 0;
+               qdevice_log(LOG_DEBUG, "Received node list reply with old ring id.");
+       } else {
+               ring_id_is_valid = 1;
+       }
+
+       case_processed = 0;
+       switch (msg->node_list_type) {
+       case TLV_NODE_LIST_TYPE_INITIAL_CONFIG:
+       case TLV_NODE_LIST_TYPE_CHANGED_CONFIG:
+               case_processed = 1;
+               res = qdevice_net_algorithm_config_node_list_reply_received(instance,
+                   msg->seq_number, (msg->node_list_type == TLV_NODE_LIST_TYPE_INITIAL_CONFIG),
+                   &msg->ring_id, ring_id_is_valid, &result_vote);
+               break;
+       case TLV_NODE_LIST_TYPE_MEMBERSHIP:
+               case_processed = 1;
+               res = qdevice_net_algorithm_membership_node_list_reply_received(instance,
+                   msg->seq_number, &msg->ring_id, ring_id_is_valid, &result_vote);
+               break;
+       case TLV_NODE_LIST_TYPE_QUORUM:
+               case_processed = 1;
+               res = qdevice_net_algorithm_quorum_node_list_reply_received(instance,
+                   msg->seq_number, &msg->ring_id, ring_id_is_valid, &result_vote);
+               break;
+       /*
+        * Default is not defined intentionally. Compiler shows warning when new node list type
+        * is added
+        */
+       }
+
+       if (!case_processed) {
+               qdevice_log(LOG_CRIT, "qdevice_net_msg_received_node_list_reply fatal error. "
+                   "Unhandled node_list_type (algorithm call)");
+               exit(1);
+       }
+
+       if (res != 0) {
+               qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting.");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_NODE_LIST_REPLY_ERR;
+               return (-1);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote));
+       }
+
+       if (qdevice_net_cast_vote_timer_update(instance, result_vote) != 0) {
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+qdevice_net_msg_received_ask_for_vote(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       return (qdevice_net_msg_received_unexpected_msg(instance, msg, "ask for vote"));
+}
+
+static int
+qdevice_net_msg_received_ask_for_vote_reply(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+       enum tlv_vote result_vote;
+       int ring_id_is_valid;
+
+       if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               qdevice_log(LOG_ERR, "Received unexpected ask for vote reply message. "
+                   "Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG;
+               return (-1);
+       }
+
+       if (!msg->vote_set || !msg->seq_number_set || !msg->ring_id_set) {
+               qdevice_log(LOG_ERR, "Received ask for vote reply message without "
+                   "required options. Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+               return (-1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Received ask for vote reply");
+       qdevice_log(LOG_DEBUG, "  seq = "UTILS_PRI_MSG_SEQ, msg->seq_number);
+       qdevice_log(LOG_DEBUG, "  vote = %s", tlv_vote_to_str(msg->vote));
+       qdevice_log(LOG_DEBUG, "  ring id = ("UTILS_PRI_RING_ID")",
+                   msg->ring_id.node_id, msg->ring_id.seq);
+
+       result_vote = msg->vote;
+
+       if (!tlv_ring_id_eq(&msg->ring_id, &instance->last_sent_ring_id)) {
+               ring_id_is_valid = 0;
+               qdevice_log(LOG_DEBUG, "Received ask for vote reply with old ring id.");
+       } else {
+               ring_id_is_valid = 1;
+       }
+
+       if (qdevice_net_algorithm_ask_for_vote_reply_received(instance, msg->seq_number,
+           &msg->ring_id, ring_id_is_valid, &result_vote) != 0) {
+               qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting.");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_ASK_FOR_VOTE_REPLY_ERR;
+               return (-1);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote));
+       }
+
+       if (qdevice_net_cast_vote_timer_update(instance, result_vote) != 0) {
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+qdevice_net_msg_received_vote_info(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+       struct send_buffer_list_entry *send_buffer;
+       enum tlv_vote result_vote;
+       int ring_id_is_valid;
+
+       if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               qdevice_log(LOG_ERR, "Received unexpected vote info message. "
+                   "Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG;
+               return (-1);
+       }
+
+       if (!msg->vote_set || !msg->seq_number_set || !msg->ring_id_set) {
+               qdevice_log(LOG_ERR, "Received node list reply message without "
+                   "required options. Disconnecting from server");
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+               return (-1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Received vote info");
+       qdevice_log(LOG_DEBUG, "  seq = "UTILS_PRI_MSG_SEQ, msg->seq_number);
+       qdevice_log(LOG_DEBUG, "  vote = %s", tlv_vote_to_str(msg->vote));
+       qdevice_log(LOG_DEBUG, "  ring id = ("UTILS_PRI_RING_ID")",
+                   msg->ring_id.node_id, msg->ring_id.seq);
+
+       result_vote = msg->vote;
+
+       if (!tlv_ring_id_eq(&msg->ring_id, &instance->last_sent_ring_id)) {
+               ring_id_is_valid = 0;
+               qdevice_log(LOG_DEBUG, "Received vote info with old ring id.");
+       } else {
+               ring_id_is_valid = 1;
+       }
+
+       if (qdevice_net_algorithm_vote_info_received(instance, msg->seq_number,
+           &msg->ring_id, ring_id_is_valid, &result_vote) != 0) {
+               qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting.");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTE_INFO_ERR;
+               return (-1);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote));
+       }
+
+       if (qdevice_net_cast_vote_timer_update(instance, result_vote) != 0) {
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               return (-1);
+       }
+
+       /*
+        * Create reply message
+        */
+       send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't allocate send list buffer for "
+                   "vote info reply msg");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+               return (-1);
+       }
+
+       if (msg_create_vote_info_reply(&send_buffer->buffer, msg->seq_number) == 0) {
+               qdevice_log(LOG_ERR, "Can't allocate send buffer for "
+                   "vote info reply list msg");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+               send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+               return (-1);
+       }
+
+       send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+static int
+qdevice_net_msg_received_vote_info_reply(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       return (qdevice_net_msg_received_unexpected_msg(instance, msg, "vote info reply"));
+}
+
+static int
+qdevice_net_msg_received_heuristics_change(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+
+       return (qdevice_net_msg_received_unexpected_msg(instance, msg, "heuristics change"));
+}
+
+static int
+qdevice_net_msg_received_heuristics_change_reply(struct qdevice_net_instance *instance,
+    const struct msg_decoded *msg)
+{
+       enum tlv_vote result_vote;
+       int ring_id_is_valid;
+
+       if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
+               qdevice_log(LOG_ERR, "Received unexpected heuristics change reply message. "
+                   "Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG;
+               return (-1);
+       }
+
+       if (!msg->vote_set || !msg->seq_number_set || !msg->ring_id_set ||
+           msg->heuristics == TLV_HEURISTICS_UNDEFINED) {
+               qdevice_log(LOG_ERR, "Received heuristics change reply message without "
+                   "required options. Disconnecting from server");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING;
+               return (-1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Received heuristics change reply");
+       qdevice_log(LOG_DEBUG, "  seq = "UTILS_PRI_MSG_SEQ, msg->seq_number);
+       qdevice_log(LOG_DEBUG, "  vote = %s", tlv_vote_to_str(msg->vote));
+       qdevice_log(LOG_DEBUG, "  ring id = ("UTILS_PRI_RING_ID")",
+                   msg->ring_id.node_id, msg->ring_id.seq);
+       qdevice_log(LOG_DEBUG, "  heuristics = %s", tlv_heuristics_to_str(msg->heuristics));
+
+       result_vote = msg->vote;
+
+       if (!tlv_ring_id_eq(&msg->ring_id, &instance->last_sent_ring_id)) {
+               ring_id_is_valid = 0;
+               qdevice_log(LOG_DEBUG, "Received heuristics change reply with old ring id.");
+       } else {
+               ring_id_is_valid = 1;
+       }
+
+       if (qdevice_net_algorithm_heuristics_change_reply_received(instance, msg->seq_number,
+           &msg->ring_id, ring_id_is_valid, msg->heuristics, &result_vote) != 0) {
+               qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting.");
+
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_REPLY_ERR;
+               return (-1);
+       } else {
+               qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote));
+       }
+
+       if (qdevice_net_cast_vote_timer_update(instance, result_vote) != 0) {
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_msg_received(struct qdevice_net_instance *instance)
+{
+       struct msg_decoded msg;
+       int res;
+       int ret_val;
+       int msg_processed;
+
+       msg_decoded_init(&msg);
+
+       res = msg_decode(&instance->receive_buffer, &msg);
+       if (res != 0) {
+               /*
+                * Error occurred. Disconnect.
+                */
+               qdevice_net_msg_received_log_msg_decode_error(res);
+               qdevice_log(LOG_ERR, "Disconnecting from server");
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_MSG_DECODE_ERROR;
+
+               return (-1);
+       }
+
+       ret_val = 0;
+
+       msg_processed = 0;
+
+       switch (msg.type) {
+       case MSG_TYPE_INIT:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_init(instance, &msg);
+               break;
+       case MSG_TYPE_PREINIT:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_preinit(instance, &msg);
+               break;
+       case MSG_TYPE_PREINIT_REPLY:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_preinit_reply(instance, &msg);
+               break;
+       case MSG_TYPE_STARTTLS:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_starttls(instance, &msg);
+               break;
+       case MSG_TYPE_SERVER_ERROR:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_server_error(instance, &msg);
+               break;
+       case MSG_TYPE_INIT_REPLY:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_init_reply(instance, &msg);
+               break;
+       case MSG_TYPE_SET_OPTION:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_set_option(instance, &msg);
+               break;
+       case MSG_TYPE_SET_OPTION_REPLY:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_set_option_reply(instance, &msg);
+               break;
+       case MSG_TYPE_ECHO_REQUEST:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_echo_request(instance, &msg);
+               break;
+       case MSG_TYPE_ECHO_REPLY:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_echo_reply(instance, &msg);
+               break;
+       case MSG_TYPE_NODE_LIST:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_node_list(instance, &msg);
+               break;
+       case MSG_TYPE_NODE_LIST_REPLY:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_node_list_reply(instance, &msg);
+               break;
+       case MSG_TYPE_ASK_FOR_VOTE:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_ask_for_vote(instance, &msg);
+               break;
+       case MSG_TYPE_ASK_FOR_VOTE_REPLY:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_ask_for_vote_reply(instance, &msg);
+               break;
+       case MSG_TYPE_VOTE_INFO:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_vote_info(instance, &msg);
+               break;
+       case MSG_TYPE_VOTE_INFO_REPLY:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_vote_info_reply(instance, &msg);
+               break;
+       case MSG_TYPE_HEURISTICS_CHANGE:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_heuristics_change(instance, &msg);
+               break;
+       case MSG_TYPE_HEURISTICS_CHANGE_REPLY:
+               msg_processed = 1;
+               ret_val = qdevice_net_msg_received_heuristics_change_reply(instance, &msg);
+       /*
+        * Default is not defined intentionally. Compiler shows warning when msg type is added
+        */
+       }
+
+       if (!msg_processed) {
+               qdevice_log(LOG_ERR, "Received unsupported message %u. "
+                   "Disconnecting from server", msg.type);
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG;
+
+               ret_val = -1;
+       }
+
+       msg_decoded_destroy(&msg);
+
+       return (ret_val);
+}
diff --git a/qdevices/qdevice-net-msg-received.h b/qdevices/qdevice-net-msg-received.h
new file mode 100644 (file)
index 0000000..12be9f7
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2016-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_MSG_RECEIVED_H_
+#define _QDEVICE_NET_MSG_RECEIVED_H_
+
+#include "qdevice-net-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qdevice_net_msg_received(struct qdevice_net_instance *instance);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_MSG_RECEIVED_H_ */
diff --git a/qdevices/qdevice-net-nss.c b/qdevices/qdevice-net-nss.c
new file mode 100644 (file)
index 0000000..7907c2d
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sslerr.h>
+#include <secerr.h>
+
+#include "qdevice-log.h"
+#include "qdevice-net-nss.h"
+#include "qdevice-net-instance.h"
+#include "qnet-config.h"
+
+SECStatus
+qdevice_net_nss_bad_cert_hook(void *arg, PRFileDesc *fd) {
+       if (PR_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE ||
+           PR_GetError() == SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE ||
+           PR_GetError() == SEC_ERROR_CRL_EXPIRED ||
+           PR_GetError() == SEC_ERROR_KRL_EXPIRED ||
+           PR_GetError() == SSL_ERROR_EXPIRED_CERT_ALERT) {
+               qdevice_log(LOG_WARNING, "Server certificate is expired.");
+
+               return (SECSuccess);
+       }
+
+       qdevice_log_nss(LOG_ERR, "Server certificate verification failure.");
+
+       return (SECFailure);
+}
+
+SECStatus
+qdevice_net_nss_get_client_auth_data(void *arg, PRFileDesc *sock, struct CERTDistNamesStr *caNames,
+    struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey)
+{
+       struct qdevice_net_instance *instance;
+
+       qdevice_log(LOG_DEBUG, "Sending client auth data.");
+
+       instance = (struct qdevice_net_instance *)arg;
+
+       instance->tls_client_cert_sent = 1;
+
+       return (NSS_GetClientAuthData((void *)instance->advanced_settings->net_nss_client_cert_nickname,
+           sock, caNames, pRetCert, pRetKey));
+}
diff --git a/qdevices/qdevice-net-nss.h b/qdevices/qdevice-net-nss.h
new file mode 100644 (file)
index 0000000..686f615
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_NSS_H_
+#define _QDEVICE_NET_NSS_H_
+
+#include <nspr.h>
+#include <nss.h>
+#include <ssl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern SECStatus               qdevice_net_nss_bad_cert_hook(void *arg, PRFileDesc *fd);
+
+extern SECStatus               qdevice_net_nss_get_client_auth_data(void *arg,
+    PRFileDesc *sock, struct CERTDistNamesStr *caNames,
+    struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_NSS_H_ */
diff --git a/qdevices/qdevice-net-poll-array-user-data.h b/qdevices/qdevice-net-poll-array-user-data.h
new file mode 100644 (file)
index 0000000..b696690
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_POLL_ARRAY_USER_DATA_H_
+#define _QDEVICE_NET_POLL_ARRAY_USER_DATA_H_
+
+#include "unix-socket-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum qdevice_net_poll_array_user_data_type {
+       QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM,
+       QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP,
+       QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET,
+       QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET,
+       QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT,
+       QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND,
+       QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV,
+       QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV,
+};
+
+struct qdevice_net_poll_array_user_data {
+       enum qdevice_net_poll_array_user_data_type type;
+       struct unix_socket_client *ipc_client;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_POLL_ARRAY_USER_DATA_H_ */
diff --git a/qdevices/qdevice-net-poll.c b/qdevices/qdevice-net-poll.c
new file mode 100644 (file)
index 0000000..e90f2b9
--- /dev/null
@@ -0,0 +1,546 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-cmap.h"
+#include "qdevice-net-poll.h"
+#include "qdevice-log.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-socket.h"
+#include "qdevice-votequorum.h"
+#include "qdevice-ipc.h"
+#include "qdevice-net-poll-array-user-data.h"
+#include "qdevice-heuristics.h"
+#include "qdevice-heuristics-cmd.h"
+
+/*
+ * Needed for creating nspr handle from unix fd
+ */
+#include <private/pprio.h>
+
+static void
+qdevice_net_poll_read_socket(struct qdevice_net_instance *instance)
+{
+
+       if (qdevice_net_socket_read(instance) == -1) {
+               instance->schedule_disconnect = 1;
+       }
+}
+
+static void
+qdevice_net_poll_read_votequorum(struct qdevice_net_instance *instance)
+{
+
+       if (qdevice_votequorum_dispatch(instance->qdevice_instance_ptr) == -1) {
+               instance->schedule_disconnect = 1;
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED;
+       }
+}
+
+static void
+qdevice_net_poll_read_cmap(struct qdevice_net_instance *instance)
+{
+
+       if (qdevice_cmap_dispatch(instance->qdevice_instance_ptr) == -1) {
+               instance->schedule_disconnect = 1;
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED;
+       }
+}
+
+static void
+qdevice_net_poll_write_socket(struct qdevice_net_instance *instance, const PRPollDesc *pfd)
+{
+       int res;
+
+       if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) {
+               res = nss_sock_non_blocking_client_succeeded(pfd);
+               if (res == -1) {
+                       /*
+                        * Connect failed -> try next
+                        */
+                       res = nss_sock_non_blocking_client_try_next(&instance->non_blocking_client);
+                       if (res == -1) {
+                               qdevice_log_nss(LOG_ERR, "Can't connect to qnetd host.");
+                               nss_sock_non_blocking_client_destroy(&instance->non_blocking_client);
+                       }
+               } else if (res == 0) {
+                       /*
+                        * Poll again
+                        */
+               } else if (res == 1) {
+                       /*
+                        * Connect success
+                        */
+                       instance->socket = instance->non_blocking_client.socket;
+                       nss_sock_non_blocking_client_destroy(&instance->non_blocking_client);
+                       instance->non_blocking_client.socket = NULL;
+
+                       instance->state = QDEVICE_NET_INSTANCE_STATE_SENDING_PREINIT_REPLY;
+
+                       qdevice_log(LOG_DEBUG, "Sending preinit msg to qnetd");
+                       if (qdevice_net_send_preinit(instance) != 0) {
+                               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+                               instance->schedule_disconnect = 1;
+                       }
+               } else {
+                       qdevice_log(LOG_CRIT, "Unhandled nss_sock_non_blocking_client_succeeded");
+                       exit(1);
+               }
+       } else {
+               if (qdevice_net_socket_write(instance) == -1) {
+                       instance->schedule_disconnect = 1;
+               }
+       }
+}
+
+static void
+qdevice_net_poll_err_socket(struct qdevice_net_instance *instance, const PRPollDesc *pfd)
+{
+
+       if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) {
+               /*
+                * Workaround for RHEL<7. Pollout is never set for nonblocking connect (doesn't work
+                * only with poll, select works as expected!???).
+                * So test if client is still valid and if pollout was not already called (ensured
+                * by default because of order in PR_Poll).
+                * If both applies it's possible to emulate pollout set by calling poll_write.
+                */
+               if (!instance->non_blocking_client.destroyed) {
+                       qdevice_net_poll_write_socket(instance, pfd);
+               }
+       } else {
+               qdevice_log(LOG_ERR, "POLL_ERR (%u) on main socket", pfd->out_flags);
+
+               instance->schedule_disconnect = 1;
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION;
+       }
+}
+
+static void
+qdevice_net_poll_read_heuristics_log(struct qdevice_net_instance *instance)
+{
+       int res;
+
+       res = qdevice_heuristics_log_read_from_pipe(&instance->qdevice_instance_ptr->heuristics_instance);
+       if (res == -1) {
+               instance->schedule_disconnect = 1;
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED;
+       }
+}
+
+static void
+qdevice_net_poll_read_heuristics_cmd(struct qdevice_net_instance *instance)
+{
+       int res;
+
+       res = qdevice_heuristics_cmd_read_from_pipe(&instance->qdevice_instance_ptr->heuristics_instance);
+       if (res == -1) {
+               instance->schedule_disconnect = 1;
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED;
+       }
+}
+
+static void
+qdevice_net_poll_write_heuristics_cmd(struct qdevice_net_instance *instance)
+{
+       int res;
+
+       res = qdevice_heuristics_cmd_write(&instance->qdevice_instance_ptr->heuristics_instance);
+       if (res == -1) {
+               instance->schedule_disconnect = 1;
+               instance->disconnect_reason =
+                   QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_CANT_SEND_RECEIVE_MSG;
+       }
+}
+
+static void
+qdevice_net_poll_read_ipc_socket(struct qdevice_net_instance *instance)
+{
+       struct unix_socket_client *client;
+       PRFileDesc *prfd;
+       struct qdevice_ipc_user_data *user_data;
+
+       if (qdevice_ipc_accept(instance->qdevice_instance_ptr, &client) != 0) {
+               return ;
+       }
+
+       prfd = PR_CreateSocketPollFd(client->socket);
+       if (prfd == NULL) {
+               qdevice_log_nss(LOG_CRIT, "Can't create NSPR poll fd for IPC client. "
+                   "Disconnecting client");
+               qdevice_ipc_client_disconnect(instance->qdevice_instance_ptr, client);
+
+               return ;
+       }
+
+       user_data = (struct qdevice_ipc_user_data *)client->user_data;
+       user_data->model_data = (void *)prfd;
+}
+
+static PRPollDesc *
+qdevice_net_pr_poll_array_create(struct qdevice_net_instance *instance)
+{
+       struct pr_poll_array *poll_array;
+       PRPollDesc *poll_desc;
+       struct qdevice_net_poll_array_user_data *user_data;
+       struct unix_socket_client *ipc_client;
+       const struct unix_socket_client_list *ipc_client_list;
+       struct qdevice_ipc_user_data *qdevice_ipc_user_data;
+
+       poll_array = &instance->poll_array;
+       ipc_client_list = &instance->qdevice_instance_ptr->local_ipc.clients;
+
+       if (qdevice_ipc_is_closed(instance->qdevice_instance_ptr)) {
+               qdevice_log(LOG_DEBUG, "Local socket is closed");
+               instance->schedule_disconnect = 1;
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED;
+
+               return (NULL);
+       }
+
+       pr_poll_array_clean(poll_array);
+
+       if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+               return (NULL);
+       }
+       poll_desc->fd = instance->votequorum_poll_fd;
+       poll_desc->in_flags = PR_POLL_READ;
+       user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM;
+
+       if (!instance->qdevice_instance_ptr->sync_in_progress) {
+               if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+                       return (NULL);
+               }
+               poll_desc->fd = instance->cmap_poll_fd;
+               poll_desc->in_flags = PR_POLL_READ;
+               user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP;
+       }
+
+       if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+               return (NULL);
+       }
+       poll_desc->fd = instance->ipc_socket_poll_fd;
+       poll_desc->in_flags = PR_POLL_READ;
+       user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET;
+
+       if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+               return (NULL);
+       }
+
+       poll_desc->fd = instance->heuristics_pipe_log_recv_poll_fd;
+       poll_desc->in_flags = PR_POLL_READ;
+       user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV;
+
+       if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+               return (NULL);
+       }
+
+       poll_desc->fd = instance->heuristics_pipe_cmd_recv_poll_fd;
+       poll_desc->in_flags = PR_POLL_READ;
+       user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV;
+
+       if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+               return (NULL);
+       }
+
+       if (!send_buffer_list_empty(
+           &instance->qdevice_instance_ptr->heuristics_instance.cmd_out_buffer_list)) {
+               poll_desc->fd = instance->heuristics_pipe_cmd_send_poll_fd;
+               poll_desc->in_flags = PR_POLL_WRITE;
+               user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND;
+
+               if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+                       return (NULL);
+               }
+       }
+
+       if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT ||
+           !instance->non_blocking_client.destroyed) {
+               if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+                       return (NULL);
+               }
+
+               user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET;
+
+               if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) {
+                       poll_desc->fd = instance->non_blocking_client.socket;
+                       poll_desc->in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
+               } else {
+                       poll_desc->fd = instance->socket;
+                       poll_desc->in_flags = PR_POLL_READ;
+
+                       if (!send_buffer_list_empty(&instance->send_buffer_list)) {
+                               poll_desc->in_flags |= PR_POLL_WRITE;
+                       }
+               }
+       }
+
+       TAILQ_FOREACH(ipc_client, ipc_client_list, entries) {
+               if (!ipc_client->reading_line && !ipc_client->writing_buffer) {
+                       continue;
+               }
+
+               if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) {
+                       return (NULL);
+               }
+
+               qdevice_ipc_user_data = (struct qdevice_ipc_user_data *)ipc_client->user_data;
+               poll_desc->fd = (PRFileDesc *)qdevice_ipc_user_data->model_data;
+               if (ipc_client->reading_line) {
+                       poll_desc->in_flags |= PR_POLL_READ;
+               }
+
+               if (ipc_client->writing_buffer) {
+                       poll_desc->in_flags |= PR_POLL_WRITE;
+               }
+
+               user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT;
+               user_data->ipc_client = ipc_client;
+       }
+
+       pr_poll_array_gc(poll_array);
+
+       return (poll_array->array);
+}
+
+int
+qdevice_net_poll(struct qdevice_net_instance *instance)
+{
+       PRPollDesc *pfds;
+       PRFileDesc *prfd;
+       PRInt32 poll_res;
+       ssize_t i;
+       struct qdevice_net_poll_array_user_data *user_data;
+       struct unix_socket_client *ipc_client;
+       struct qdevice_ipc_user_data *qdevice_ipc_user_data;
+       int case_processed;
+
+       pfds = qdevice_net_pr_poll_array_create(instance);
+       if (pfds == NULL) {
+               return (-1);
+       }
+
+       instance->schedule_disconnect = 0;
+
+       if ((poll_res = PR_Poll(pfds, pr_poll_array_size(&instance->poll_array),
+           timer_list_time_to_expire(&instance->main_timer_list))) > 0) {
+               for (i = 0; i < pr_poll_array_size(&instance->poll_array); i++) {
+                       user_data = pr_poll_array_get_user_data(&instance->poll_array, i);
+
+                       ipc_client = user_data->ipc_client;
+
+                       if (pfds[i].out_flags & PR_POLL_READ) {
+                               case_processed = 0;
+
+                               switch (user_data->type) {
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
+                                       case_processed = 1;
+                                       qdevice_net_poll_read_socket(instance);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM:
+                                       case_processed = 1;
+                                       qdevice_net_poll_read_votequorum(instance);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP:
+                                       case_processed = 1;
+                                       qdevice_net_poll_read_cmap(instance);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
+                                       case_processed = 1;
+                                       qdevice_net_poll_read_ipc_socket(instance);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
+                                       case_processed = 1;
+                                       qdevice_ipc_io_read(instance->qdevice_instance_ptr, ipc_client);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND:
+                                       /*
+                                        * Read on heuristics cmd send fd shouldn't happen
+                                        */
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV:
+                                       case_processed = 1;
+                                       qdevice_net_poll_read_heuristics_cmd(instance);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV:
+                                       case_processed = 1;
+                                       qdevice_net_poll_read_heuristics_log(instance);
+                                       break;
+                               /*
+                                * Default is not defined intentionally. Compiler shows warning when
+                                * new poll_array_user_data_type is added
+                                */
+                               }
+
+                               if (!case_processed) {
+                                       qdevice_log(LOG_CRIT, "Unhandled read on poll descriptor %u", i);
+                                       exit(1);
+                               }
+                       }
+
+                       if (!instance->schedule_disconnect && pfds[i].out_flags & PR_POLL_WRITE) {
+                               case_processed = 0;
+
+                               switch (user_data->type) {
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
+                                       case_processed = 1;
+                                       qdevice_net_poll_write_socket(instance, &pfds[i]);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
+                                       case_processed = 1;
+                                       qdevice_ipc_io_write(instance->qdevice_instance_ptr, ipc_client);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND:
+                                       case_processed = 1;
+                                       qdevice_net_poll_write_heuristics_cmd(instance);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM:
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP:
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV:
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV:
+                                       /*
+                                        * Write on votequorum, cmap, ipc socket and
+                                        * heuristics log shouldn't happen.
+                                        */
+                                       break;
+                               /*
+                                * Default is not defined intentionally. Compiler shows warning when
+                                * new poll_array_user_data_type is added
+                                */
+                               }
+
+                               if (!case_processed) {
+                                       qdevice_log(LOG_CRIT, "Unhandled write on poll descriptor %u", i);
+                                       exit(1);
+                               }
+                       }
+
+                       if (!instance->schedule_disconnect &&
+                           (pfds[i].out_flags & (PR_POLL_ERR|PR_POLL_NVAL|PR_POLL_HUP|PR_POLL_EXCEPT)) &&
+                           !(pfds[i].out_flags & (PR_POLL_READ|PR_POLL_WRITE))) {
+                               case_processed = 0;
+
+                               switch (user_data->type) {
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET:
+                                       case_processed = 1;
+                                       qdevice_net_poll_err_socket(instance, &pfds[i]);
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET:
+                                       case_processed = 1;
+                                       if (pfds[i].out_flags != PR_POLL_NVAL) {
+                                               qdevice_log(LOG_CRIT, "POLLERR (%u) on local socket",
+                                                   pfds[i].out_flags);
+                                               exit(1);
+                                       } else {
+                                               qdevice_log(LOG_DEBUG, "Local socket is closed");
+                                               instance->schedule_disconnect = 1;
+                                               instance->disconnect_reason =
+                                                   QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED;
+                                       }
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT:
+                                       case_processed = 1;
+                                       qdevice_log(LOG_DEBUG, "POLL_ERR (%u) on ipc client socket. "
+                                           "Disconnecting.",  pfds[i].out_flags);
+                                       ipc_client->schedule_disconnect = 1;
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM:
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP:
+                                       case_processed = 1;
+                                       qdevice_log(LOG_DEBUG, "POLL_ERR (%u) on corosync socket. "
+                                           "Disconnecting.",  pfds[i].out_flags);
+
+                                       instance->schedule_disconnect = 1;
+                                       instance->disconnect_reason =
+                                               QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED;
+                                       break;
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV:
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV:
+                               case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND:
+                                       case_processed = 1;
+
+                                       /*
+                                        * Closed pipe doesn't mean return of PR_POLL_READ. To display
+                                        * better log message, we call read log as if POLL_READ would
+                                        * be set.
+                                        */
+                                       qdevice_net_poll_read_heuristics_log(instance);
+
+                                       qdevice_log(LOG_DEBUG, "POLL_ERR (%u) on heuristics pipe. "
+                                           "Disconnecting.",  pfds[i].out_flags);
+
+                                       instance->schedule_disconnect = 1;
+                                       instance->disconnect_reason =
+                                               QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED;
+                                       break;
+                               /*
+                                * Default is not defined intentionally. Compiler shows warning when
+                                * new poll_array_user_data_type is added
+                                */
+                               }
+
+                               if (!case_processed) {
+                                       qdevice_log(LOG_CRIT, "Unhandled error on poll descriptor %u", i);
+                                       exit(1);
+                               }
+                       }
+
+                       if (user_data->type == QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT &&
+                           ipc_client->schedule_disconnect) {
+                               qdevice_ipc_user_data = (struct qdevice_ipc_user_data *)ipc_client->user_data;
+                               prfd = (PRFileDesc *)qdevice_ipc_user_data->model_data;
+
+                               if (PR_DestroySocketPollFd(prfd) != PR_SUCCESS) {
+                                       qdevice_log_nss(LOG_WARNING, "Unable to destroy client IPC poll socket fd");
+                               }
+
+                               qdevice_ipc_client_disconnect(instance->qdevice_instance_ptr, ipc_client);
+                       }
+               }
+       }
+
+       if (!instance->schedule_disconnect) {
+               timer_list_expire(&instance->main_timer_list);
+       }
+
+       if (instance->schedule_disconnect) {
+               /*
+                * Schedule disconnect can be set by this function, by some timer_list callback
+                * or cmap/votequorum callbacks
+                */
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-net-poll.h b/qdevices/qdevice-net-poll.h
new file mode 100644 (file)
index 0000000..fe7732f
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_POLL_H_
+#define _QDEVICE_NET_POLL_H_
+
+
+#include "qdevice-net-instance.h"
+
+extern int             qdevice_net_poll(struct qdevice_net_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_POLL_H_ */
diff --git a/qdevices/qdevice-net-send.c b/qdevices/qdevice-net-send.c
new file mode 100644 (file)
index 0000000..3709590
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-log.h"
+#include "qdevice-log-debug.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-votequorum.h"
+#include "msg.h"
+#include "utils.h"
+
+int
+qdevice_net_send_echo_request(struct qdevice_net_instance *instance)
+{
+       struct send_buffer_list_entry *send_buffer;
+
+       send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_CRIT, "Can't allocate send list buffer for reply msg.");
+
+               return (-1);
+       }
+
+       instance->echo_request_expected_msg_seq_num++;
+
+       if (msg_create_echo_request(&send_buffer->buffer, 1,
+           instance->echo_request_expected_msg_seq_num) == 0) {
+               qdevice_log(LOG_ERR, "Can't allocate send buffer for echo request msg");
+
+               send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+
+               return (-1);
+       }
+
+       send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+int
+qdevice_net_send_preinit(struct qdevice_net_instance *instance)
+{
+       struct send_buffer_list_entry *send_buffer;
+
+       send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't allocate send list buffer for preinit msg");
+
+               return (-1);
+       }
+
+       if (msg_create_preinit(&send_buffer->buffer, instance->cluster_name, 1,
+           instance->last_msg_seq_num) == 0) {
+               qdevice_log(LOG_ERR, "Can't allocate buffer");
+
+               send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+               return (-1);
+       }
+
+       send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+       instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_PREINIT_REPLY;
+
+       return (0);
+}
+
+int
+qdevice_net_send_init(struct qdevice_net_instance *instance)
+{
+       enum msg_type *supported_msgs;
+       size_t no_supported_msgs;
+       enum tlv_opt_type *supported_opts;
+       size_t no_supported_opts;
+       struct send_buffer_list_entry *send_buffer;
+       struct tlv_ring_id tlv_rid;
+
+       tlv_get_supported_options(&supported_opts, &no_supported_opts);
+       msg_get_supported_messages(&supported_msgs, &no_supported_msgs);
+       instance->last_msg_seq_num++;
+
+       send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't allocate send list buffer for init msg");
+
+               return (-1);
+       }
+
+       qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid,
+           &instance->qdevice_instance_ptr->vq_node_list_ring_id);
+
+       if (msg_create_init(&send_buffer->buffer, 1, instance->last_msg_seq_num,
+           instance->decision_algorithm,
+           supported_msgs, no_supported_msgs, supported_opts, no_supported_opts,
+           instance->qdevice_instance_ptr->node_id, instance->heartbeat_interval,
+           &instance->tie_breaker, &tlv_rid) == 0) {
+               qdevice_log(LOG_ERR, "Can't allocate send buffer for init msg");
+
+               send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+               return (-1);
+       }
+
+       memcpy(&instance->last_sent_ring_id, &tlv_rid, sizeof(instance->last_sent_ring_id));
+
+       send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+       instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_INIT_REPLY;
+
+       return (0);
+}
+
+int
+qdevice_net_send_ask_for_vote(struct qdevice_net_instance *instance)
+{
+       struct send_buffer_list_entry *send_buffer;
+
+       send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't allocate send list buffer for ask for vote msg");
+
+               return (-1);
+       }
+
+       instance->last_msg_seq_num++;
+
+       qdevice_log(LOG_DEBUG, "Sending ask for vote seq = "UTILS_PRI_MSG_SEQ,
+           instance->last_msg_seq_num);
+
+       if (msg_create_ask_for_vote(&send_buffer->buffer, instance->last_msg_seq_num) == 0) {
+               qdevice_log(LOG_ERR, "Can't allocate send buffer for ask for vote msg");
+
+               send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+               return (-1);
+       }
+
+       send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+int
+qdevice_net_send_config_node_list(struct qdevice_net_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version,
+    int initial)
+{
+       struct send_buffer_list_entry *send_buffer;
+
+       send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't allocate send list buffer for config "
+                   "node list msg");
+
+               return (-1);
+       }
+
+       instance->last_msg_seq_num++;
+
+       qdevice_log(LOG_DEBUG, "Sending config node list seq = "UTILS_PRI_MSG_SEQ,
+           instance->last_msg_seq_num);
+       qdevice_log_debug_dump_node_list(nlist);
+
+       if (msg_create_node_list(&send_buffer->buffer, instance->last_msg_seq_num,
+           (initial ? TLV_NODE_LIST_TYPE_INITIAL_CONFIG : TLV_NODE_LIST_TYPE_CHANGED_CONFIG),
+           0, NULL, config_version_set, config_version, 0, TLV_QUORATE_INQUORATE,
+           0, TLV_HEURISTICS_UNDEFINED, nlist) == 0) {
+               qdevice_log(LOG_ERR, "Can't allocate send buffer for config list msg");
+
+               send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+               return (-1);
+       }
+
+       send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+int
+qdevice_net_send_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics heuristics)
+{
+       struct send_buffer_list_entry *send_buffer;
+
+       send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't allocate send list buffer for heuristics change msg");
+
+               return (-1);
+       }
+
+       instance->last_msg_seq_num++;
+
+       qdevice_log(LOG_DEBUG, "Sending heuristics change seq = "UTILS_PRI_MSG_SEQ
+           ", heuristics = %s",
+           instance->last_msg_seq_num, tlv_heuristics_to_str(heuristics));
+
+       if (msg_create_heuristics_change(&send_buffer->buffer, instance->last_msg_seq_num,
+           heuristics) == 0) {
+               qdevice_log(LOG_ERR, "Can't allocate send buffer for heuristics change msg");
+
+               send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+               return (-1);
+       }
+
+       send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+int
+qdevice_net_send_membership_node_list(struct qdevice_net_instance *instance,
+    const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], enum tlv_heuristics heuristics)
+{
+       struct node_list nlist;
+       struct send_buffer_list_entry *send_buffer;
+       uint32_t i;
+
+       node_list_init(&nlist);
+
+       for (i = 0; i < node_list_entries; i++) {
+               if (node_list_add(&nlist, node_list[i], 0, TLV_NODE_STATE_NOT_SET) == NULL) {
+                       qdevice_log(LOG_ERR, "Can't allocate membership node list.");
+
+                       node_list_free(&nlist);
+
+                       return (-1);
+               }
+       }
+
+       send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't allocate send list buffer for membership "
+                   "node list msg");
+
+               node_list_free(&nlist);
+
+               return (-1);
+       }
+
+       instance->last_msg_seq_num++;
+
+       qdevice_log(LOG_DEBUG, "Sending membership node list seq = "UTILS_PRI_MSG_SEQ", "
+           "ringid = ("UTILS_PRI_RING_ID"), heuristics = %s.", instance->last_msg_seq_num,
+           ring_id->node_id, ring_id->seq, tlv_heuristics_to_str(heuristics));
+       qdevice_log_debug_dump_node_list(&nlist);
+
+       if (msg_create_node_list(&send_buffer->buffer, instance->last_msg_seq_num,
+           TLV_NODE_LIST_TYPE_MEMBERSHIP,
+           1, ring_id, 0, 0, 0, 0, 1, heuristics, &nlist) == 0) {
+               qdevice_log(LOG_ERR, "Can't allocate send buffer for membership list msg");
+
+               node_list_free(&nlist);
+
+               send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+               return (-1);
+       }
+
+       memcpy(&instance->last_sent_ring_id, ring_id, sizeof(instance->last_sent_ring_id));
+
+       node_list_free(&nlist);
+
+       send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+int
+qdevice_net_send_quorum_node_list(struct qdevice_net_instance *instance,
+    enum tlv_quorate quorate,
+    uint32_t node_list_entries, votequorum_node_t node_list[])
+{
+       struct node_list nlist;
+       struct send_buffer_list_entry *send_buffer;
+       uint32_t i;
+
+       node_list_init(&nlist);
+
+       for (i = 0; i < node_list_entries; i++) {
+               if (node_list[i].nodeid == 0) {
+                       continue;
+               }
+
+               if (node_list_add(&nlist, node_list[i].nodeid, 0,
+                   qdevice_net_votequorum_node_state_to_tlv(node_list[i].state)) == NULL) {
+                       qdevice_log(LOG_ERR, "Can't allocate quorum node list.");
+
+                       node_list_free(&nlist);
+
+                       return (-1);
+               }
+       }
+
+       send_buffer = send_buffer_list_get_new(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_ERR, "Can't allocate send list buffer for quorum "
+                   "node list msg");
+
+               node_list_free(&nlist);
+
+               return (-1);
+       }
+
+       instance->last_msg_seq_num++;
+
+       qdevice_log(LOG_DEBUG, "Sending quorum node list seq = "UTILS_PRI_MSG_SEQ", quorate = %u",
+           instance->last_msg_seq_num, quorate);
+       qdevice_log_debug_dump_node_list(&nlist);
+
+       if (msg_create_node_list(&send_buffer->buffer, instance->last_msg_seq_num,
+           TLV_NODE_LIST_TYPE_QUORUM,
+           0, NULL, 0, 0, 1, quorate, 0, TLV_HEURISTICS_UNDEFINED, &nlist) == 0) {
+               qdevice_log(LOG_ERR, "Can't allocate send buffer for quorum list msg");
+
+               node_list_free(&nlist);
+
+               send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer);
+               return (-1);
+       }
+
+       node_list_free(&nlist);
+
+       send_buffer_list_put(&instance->send_buffer_list, send_buffer);
+
+       return (0);
+}
diff --git a/qdevices/qdevice-net-send.h b/qdevices/qdevice-net-send.h
new file mode 100644 (file)
index 0000000..08763da
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_SEND_H_
+#define _QDEVICE_NET_SEND_H_
+
+#include <sys/types.h>
+
+#include "qdevice-net-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qdevice_net_send_echo_request(struct qdevice_net_instance *instance);
+
+extern int             qdevice_net_send_preinit(struct qdevice_net_instance *instance);
+
+extern int             qdevice_net_send_init(struct qdevice_net_instance *instance);
+
+extern int             qdevice_net_send_ask_for_vote(struct qdevice_net_instance *instance);
+
+extern int             qdevice_net_send_config_node_list(struct qdevice_net_instance *instance,
+    const struct node_list *nlist, int config_version_set, uint64_t config_version,
+    int initial);
+
+extern int             qdevice_net_send_heuristics_change(struct qdevice_net_instance *instance,
+    enum tlv_heuristics heuristics);
+
+extern int             qdevice_net_send_membership_node_list(
+    struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id,
+    uint32_t node_list_entries, uint32_t node_list[], enum tlv_heuristics heuristics);
+
+extern int             qdevice_net_send_quorum_node_list(
+    struct qdevice_net_instance *instance, enum tlv_quorate quorate,
+    uint32_t node_list_entries, votequorum_node_t node_list[]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_SEND_H_ */
diff --git a/qdevices/qdevice-net-socket.c b/qdevices/qdevice-net-socket.c
new file mode 100644 (file)
index 0000000..6dc0f3f
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "msg.h"
+#include "msgio.h"
+#include "qnet-config.h"
+#include "qdevice-log.h"
+#include "qdevice-net-msg-received.h"
+#include "qdevice-net-nss.h"
+#include "qdevice-net-send.h"
+#include "qdevice-net-socket.h"
+
+/*
+ * -1 means end of connection (EOF) or some other unhandled error. 0 = success
+ */
+int
+qdevice_net_socket_read(struct qdevice_net_instance *instance)
+{
+       int res;
+       int ret_val;
+       int orig_skipping_msg;
+
+       orig_skipping_msg = instance->skipping_msg;
+
+       res = msgio_read(instance->socket, &instance->receive_buffer,
+           &instance->msg_already_received_bytes, &instance->skipping_msg);
+
+       if (!orig_skipping_msg && instance->skipping_msg) {
+               qdevice_log(LOG_DEBUG, "msgio_read set skipping_msg");
+       }
+
+       ret_val = 0;
+
+       switch (res) {
+       case 0:
+               /*
+                * Partial read
+                */
+               break;
+       case -1:
+               qdevice_log(LOG_DEBUG, "Server closed connection");
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION;
+               ret_val = -1;
+               break;
+       case -2:
+               qdevice_log(LOG_ERR, "Unhandled error when reading from server. "
+                   "Disconnecting from server");
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE;
+               ret_val = -1;
+               break;
+       case -3:
+               qdevice_log(LOG_ERR, "Can't store message header from server. "
+                   "Disconnecting from server");
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE;
+               ret_val = -1;
+               break;
+       case -4:
+               qdevice_log(LOG_ERR, "Can't store message from server. "
+                   "Disconnecting from server");
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE;
+               ret_val = -1;
+               break;
+       case -5:
+               qdevice_log(LOG_WARNING, "Server sent unsupported msg type %u. "
+                   "Disconnecting from server", msg_get_type(&instance->receive_buffer));
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNSUPPORTED_MSG;
+               ret_val = -1;
+               break;
+       case -6:
+               qdevice_log(LOG_WARNING,
+                   "Server wants to send too long message %u bytes. Disconnecting from server",
+                   msg_get_len(&instance->receive_buffer));
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE;
+               ret_val = -1;
+               break;
+       case 1:
+               /*
+                * Full message received / skipped
+                */
+               if (!instance->skipping_msg) {
+                       if (qdevice_net_msg_received(instance) == -1) {
+                               ret_val = -1;
+                       }
+               } else {
+                       qdevice_log(LOG_CRIT, "net_socket_read in skipping msg state");
+                       exit(1);
+               }
+
+               instance->skipping_msg = 0;
+               instance->msg_already_received_bytes = 0;
+               dynar_clean(&instance->receive_buffer);
+               break;
+       default:
+               qdevice_log(LOG_CRIT, "qdevice_net_socket_read unhandled error %d", res);
+               exit(1);
+               break;
+       }
+
+       return (ret_val);
+}
+
+static int
+qdevice_net_socket_write_finished(struct qdevice_net_instance *instance)
+{
+       PRFileDesc *new_pr_fd;
+
+       if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_STARTTLS_BEING_SENT) {
+               /*
+                * StartTLS sent to server. Begin with TLS handshake
+                */
+               if ((new_pr_fd = nss_sock_start_ssl_as_client(instance->socket,
+                   instance->advanced_settings->net_nss_qnetd_cn,
+                   qdevice_net_nss_bad_cert_hook,
+                   qdevice_net_nss_get_client_auth_data,
+                   instance, 0, NULL)) == NULL) {
+                       qdevice_log_nss(LOG_ERR, "Can't start TLS");
+                       instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_START_TLS;
+                       return (-1);
+               }
+
+               /*
+                * And send init msg
+                */
+               if (qdevice_net_send_init(instance) != 0) {
+                       instance->disconnect_reason =
+                           QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
+
+                       return (-1);
+               }
+
+               instance->socket = new_pr_fd;
+               instance->using_tls = 1;
+       }
+
+       return (0);
+}
+
+int
+qdevice_net_socket_write(struct qdevice_net_instance *instance)
+{
+       int res;
+       struct send_buffer_list_entry *send_buffer;
+       enum msg_type sent_msg_type;
+
+       send_buffer = send_buffer_list_get_active(&instance->send_buffer_list);
+       if (send_buffer == NULL) {
+               qdevice_log(LOG_CRIT, "send_buffer_list_get_active returned NULL");
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SEND_MESSAGE;
+
+               return (-1);
+       }
+
+       res = msgio_write(instance->socket, &send_buffer->buffer,
+           &send_buffer->msg_already_sent_bytes);
+
+       if (res == 1) {
+               sent_msg_type = msg_get_type(&send_buffer->buffer);
+
+               send_buffer_list_delete(&instance->send_buffer_list, send_buffer);
+
+               if (sent_msg_type != MSG_TYPE_ECHO_REQUEST) {
+                       if (qdevice_net_socket_write_finished(instance) == -1) {
+                               return (-1);
+                       }
+               }
+       }
+
+       if (res == -1) {
+               qdevice_log_nss(LOG_CRIT, "PR_Send returned 0");
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION;
+               return (-1);
+       }
+
+       if (res == -2) {
+               qdevice_log_nss(LOG_ERR, "Unhandled error when sending message to server");
+               instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SEND_MESSAGE;
+
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-net-socket.h b/qdevices/qdevice-net-socket.h
new file mode 100644 (file)
index 0000000..045e4a9
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_SOCKET_H_
+#define _QDEVICE_NET_SOCKET_H_
+
+#include "qdevice-net-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qdevice_net_socket_read(struct qdevice_net_instance *instance);
+
+extern int             qdevice_net_socket_write(struct qdevice_net_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_SOCKET_H_ */
diff --git a/qdevices/qdevice-net-votequorum.c b/qdevices/qdevice-net-votequorum.c
new file mode 100644 (file)
index 0000000..4ede694
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qdevice-log.h"
+#include "qdevice-net-votequorum.h"
+
+enum tlv_node_state
+qdevice_net_votequorum_node_state_to_tlv(uint32_t votequorum_node_state)
+{
+       enum tlv_node_state res;
+
+       switch (votequorum_node_state) {
+       case VOTEQUORUM_NODESTATE_MEMBER: res = TLV_NODE_STATE_MEMBER; break;
+       case VOTEQUORUM_NODESTATE_DEAD: res = TLV_NODE_STATE_DEAD; break;
+       case VOTEQUORUM_NODESTATE_LEAVING: res = TLV_NODE_STATE_LEAVING; break;
+       default:
+               qdevice_log(LOG_ERR, "qdevice_net_votequorum_node_state_to_tlv: Unhandled votequorum "
+                   "node state %"PRIu32, votequorum_node_state);
+               exit(1);
+               break;
+       }
+
+       return (res);
+}
+
+void
+qdevice_net_votequorum_ring_id_to_tlv(struct tlv_ring_id *tlv_rid,
+    const votequorum_ring_id_t *votequorum_rid)
+{
+
+       tlv_rid->node_id = votequorum_rid->nodeid;
+       tlv_rid->seq = votequorum_rid->seq;
+}
diff --git a/qdevices/qdevice-net-votequorum.h b/qdevices/qdevice-net-votequorum.h
new file mode 100644 (file)
index 0000000..5d62df7
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_NET_VOTEQUORUM_H_
+#define _QDEVICE_NET_VOTEQUORUM_H_
+
+#include <corosync/votequorum.h>
+
+#include "qdevice-net-instance.h"
+#include "tlv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern enum tlv_node_state     qdevice_net_votequorum_node_state_to_tlv(
+    uint32_t votequorum_node_state);
+
+extern void                    qdevice_net_votequorum_ring_id_to_tlv(struct tlv_ring_id *tlv_rid,
+    const votequorum_ring_id_t *votequorum_rid);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_NET_VOTEQUORUM_H_ */
diff --git a/qdevices/qdevice-votequorum.c b/qdevices/qdevice-votequorum.c
new file mode 100644 (file)
index 0000000..b0a2e17
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <poll.h>
+
+#include "qdevice-config.h"
+#include "qdevice-log.h"
+#include "qdevice-votequorum.h"
+#include "qdevice-model.h"
+#include "utils.h"
+
+static void
+qdevice_votequorum_quorum_notify_callback(votequorum_handle_t votequorum_handle,
+    uint64_t context, uint32_t quorate,
+    uint32_t node_list_entries, votequorum_node_t node_list[])
+{
+       struct qdevice_instance *instance;
+       uint32_t u32;
+
+       if (votequorum_context_get(votequorum_handle, (void **)&instance) != CS_OK) {
+               qdevice_log(LOG_CRIT, "Fatal error. Can't get votequorum context");
+               exit(1);
+       }
+
+       instance->sync_in_progress = 0;
+
+       qdevice_log(LOG_DEBUG, "Votequorum quorum notify callback:");
+       qdevice_log(LOG_DEBUG, "  Quorate = %u", quorate);
+
+       qdevice_log(LOG_DEBUG, "  Node list (size = %"PRIu32"):", node_list_entries);
+       for (u32 = 0; u32 < node_list_entries; u32++) {
+               qdevice_log(LOG_DEBUG, "    %"PRIu32" nodeid = "UTILS_PRI_NODE_ID", state = %"PRIu32,
+                   u32, node_list[u32].nodeid, node_list[u32].state);
+       }
+
+       if (qdevice_model_votequorum_quorum_notify(instance, quorate, node_list_entries,
+           node_list) != 0) {
+               qdevice_log(LOG_DEBUG, "qdevice_model_votequorum_quorum_notify returned error -> exit");
+               exit(2);
+       }
+
+       instance->vq_quorum_quorate = quorate;
+       instance->vq_quorum_node_list_entries = node_list_entries;
+
+       free(instance->vq_quorum_node_list);
+       instance->vq_quorum_node_list = malloc(sizeof(*node_list) * node_list_entries);
+       if (instance->vq_quorum_node_list == NULL) {
+               qdevice_log(LOG_CRIT, "Can't alloc votequorum node list memory");
+               exit(1);
+       }
+       memcpy(instance->vq_quorum_node_list, node_list, sizeof(*node_list) * node_list_entries);
+}
+
+static int
+qdevice_votequorum_heuristics_exec_result_callback(
+    void *heuristics_instance_ptr,
+    uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result)
+{
+       struct qdevice_heuristics_instance *heuristics_instance;
+       struct qdevice_instance *instance;
+
+       heuristics_instance = (struct qdevice_heuristics_instance *)heuristics_instance_ptr;
+       instance = heuristics_instance->qdevice_instance_ptr;
+
+       if (qdevice_heuristics_result_notifier_list_set_active(
+           &instance->heuristics_instance.exec_result_notifier_list,
+           qdevice_votequorum_heuristics_exec_result_callback, 0) != 0) {
+               qdevice_log(LOG_CRIT, "Can't deactivate votequrorum heuristics exec callback notifier");
+               exit(2);
+       }
+
+       qdevice_log(LOG_DEBUG, "Votequorum heuristics exec result callback:");
+       qdevice_log(LOG_DEBUG, "  seq_number = %"PRIu32", exec_result = %s",
+           seq_number, qdevice_heuristics_exec_result_to_str(exec_result));
+
+       if (qdevice_model_votequorum_node_list_heuristics_notify(instance, instance->vq_node_list_ring_id,
+           instance->vq_node_list_entries, instance->vq_node_list, exec_result) != 0) {
+               qdevice_log(LOG_DEBUG, "qdevice_votequorum_node_list_heuristics_notify_callback returned error -> exit");
+               exit(2);
+       }
+
+       instance->vq_node_list_initial_heuristics_finished = 1;
+       instance->vq_node_list_heuristics_result = exec_result;
+
+       return (0);
+}
+
+static void
+qdevice_votequorum_node_list_notify_callback(votequorum_handle_t votequorum_handle,
+    uint64_t context, votequorum_ring_id_t votequorum_ring_id,
+    uint32_t node_list_entries, uint32_t node_list[])
+{
+       struct qdevice_instance *instance;
+       uint32_t u32;
+
+       if (votequorum_context_get(votequorum_handle, (void **)&instance) != CS_OK) {
+               qdevice_log(LOG_CRIT, "Fatal error. Can't get votequorum context");
+               exit(1);
+       }
+
+       instance->sync_in_progress = 1;
+       memcpy(&instance->vq_poll_ring_id, &votequorum_ring_id, sizeof(votequorum_ring_id));
+
+       qdevice_log(LOG_DEBUG, "Votequorum nodelist notify callback:");
+       qdevice_log(LOG_DEBUG, "  Ring_id = ("UTILS_PRI_RING_ID")",
+           votequorum_ring_id.nodeid, votequorum_ring_id.seq);
+
+       qdevice_log(LOG_DEBUG, "  Node list (size = %"PRIu32"):", node_list_entries);
+       for (u32 = 0; u32 < node_list_entries; u32++) {
+               qdevice_log(LOG_DEBUG, "    %"PRIu32" nodeid = "UTILS_PRI_NODE_ID,
+                   u32, node_list[u32]);
+       }
+
+       if (qdevice_model_votequorum_node_list_notify(instance, votequorum_ring_id, node_list_entries,
+           node_list) != 0) {
+               qdevice_log(LOG_DEBUG, "qdevice_votequorum_node_list_notify_callback returned error -> exit");
+               exit(2);
+       }
+
+       if (qdevice_heuristics_result_notifier_list_set_active(
+           &instance->heuristics_instance.exec_result_notifier_list,
+           qdevice_votequorum_heuristics_exec_result_callback, 1) != 0) {
+               qdevice_log(LOG_CRIT, "Can't activate votequrorum heuristics exec callback notifier");
+               exit(2);
+       }
+
+       if (qdevice_heuristics_exec(&instance->heuristics_instance, instance->sync_in_progress) != 0) {
+               qdevice_log(LOG_CRIT, "Can't start heuristics -> exit");
+               exit(2);
+       }
+
+       instance->vq_node_list_initial_ring_id_set = 1;
+       memcpy(&instance->vq_node_list_ring_id, &votequorum_ring_id, sizeof(votequorum_ring_id));
+       instance->vq_node_list_entries = node_list_entries;
+       free(instance->vq_node_list);
+       instance->vq_node_list = malloc(sizeof(*node_list) * node_list_entries);
+       if (instance->vq_node_list == NULL) {
+               qdevice_log(LOG_CRIT, "Can't alloc votequorum node list memory");
+               exit(1);
+       }
+       memcpy(instance->vq_node_list, node_list, sizeof(*node_list) * node_list_entries);
+}
+
+static void
+qdevice_votequorum_expected_votes_notify_callback(votequorum_handle_t votequorum_handle,
+    uint64_t context, uint32_t expected_votes)
+{
+       struct qdevice_instance *instance;
+
+       if (votequorum_context_get(votequorum_handle, (void **)&instance) != CS_OK) {
+               qdevice_log(LOG_CRIT, "Fatal error. Can't get votequorum context");
+               exit(1);
+       }
+
+       qdevice_log(LOG_DEBUG, "Votequorum expected_votes notify callback:");
+       qdevice_log(LOG_DEBUG, "  Expected_votes: "UTILS_PRI_EXPECTED_VOTES, expected_votes);
+
+       if (qdevice_model_votequorum_expected_votes_notify(instance, expected_votes) != 0) {
+               qdevice_log(LOG_DEBUG, "qdevice_votequorum_expected_votes_notify_callback returned error -> exit");
+               exit(2);
+       }
+
+       instance->vq_expected_votes = expected_votes;
+}
+
+void
+qdevice_votequorum_init(struct qdevice_instance *instance)
+{
+       votequorum_callbacks_t votequorum_callbacks;
+       votequorum_handle_t votequorum_handle;
+       cs_error_t res;
+       int no_retries;
+       struct votequorum_info vq_info;
+
+       memset(&votequorum_callbacks, 0, sizeof(votequorum_callbacks));
+
+       votequorum_callbacks.votequorum_quorum_notify_fn =
+           qdevice_votequorum_quorum_notify_callback;
+
+       votequorum_callbacks.votequorum_nodelist_notify_fn =
+           qdevice_votequorum_node_list_notify_callback;
+
+       votequorum_callbacks.votequorum_expectedvotes_notify_fn =
+           qdevice_votequorum_expected_votes_notify_callback;
+
+       no_retries = 0;
+
+       while ((res = votequorum_initialize(&votequorum_handle,
+           &votequorum_callbacks)) == CS_ERR_TRY_AGAIN &&
+           no_retries++ < instance->advanced_settings->max_cs_try_again) {
+               (void)poll(NULL, 0, 1000);
+       }
+
+       if (res != CS_OK) {
+               qdevice_log(LOG_CRIT, "Failed to initialize the votequorum API. Error %s", cs_strerror(res));
+               exit(1);
+       }
+
+       if ((res = votequorum_qdevice_register(votequorum_handle,
+           instance->advanced_settings->votequorum_device_name)) != CS_OK) {
+               qdevice_log(LOG_CRIT, "Can't register votequorum device. Error %s", cs_strerror(res));
+               exit(1);
+       }
+
+       if ((res = votequorum_context_set(votequorum_handle, (void *)instance)) != CS_OK) {
+               qdevice_log(LOG_CRIT, "Can't set votequorum context. Error %s", cs_strerror(res));
+               exit(1);
+       }
+
+       if ((res = votequorum_getinfo(votequorum_handle, VOTEQUORUM_QDEVICE_NODEID,
+           &vq_info)) != CS_OK) {
+               qdevice_log(LOG_CRIT, "Can't get votequorum information. Error %s", cs_strerror(res));
+               exit(1);
+       }
+       instance->vq_expected_votes = vq_info.node_expected_votes;
+
+       instance->votequorum_handle = votequorum_handle;
+
+       votequorum_fd_get(votequorum_handle, &instance->votequorum_poll_fd);
+
+       if ((res = votequorum_trackstart(instance->votequorum_handle, 0,
+           CS_TRACK_CHANGES)) != CS_OK) {
+               qdevice_log(LOG_CRIT, "Can't start tracking votequorum changes. Error %s",
+                   cs_strerror(res));
+               exit(1);
+       }
+
+       if (qdevice_heuristics_result_notifier_list_add(&instance->heuristics_instance.exec_result_notifier_list,
+           qdevice_votequorum_heuristics_exec_result_callback) == NULL) {
+               qdevice_log(LOG_CRIT, "Can't add votequrorum heuristics exec callback into notifier");
+               exit(1);
+       }
+}
+
+void
+qdevice_votequorum_destroy(struct qdevice_instance *instance)
+{
+       cs_error_t res;
+
+       free(instance->vq_quorum_node_list); instance->vq_quorum_node_list = NULL;
+       free(instance->vq_node_list); instance->vq_node_list = NULL;
+
+       res = votequorum_trackstop(instance->votequorum_handle);
+       if (res != CS_OK) {
+               qdevice_log(LOG_WARNING, "Can't start tracking votequorum changes. Error %s",
+                   cs_strerror(res));
+       }
+
+       res = votequorum_qdevice_unregister(instance->votequorum_handle,
+               instance->advanced_settings->votequorum_device_name);
+
+       if (res != CS_OK) {
+               qdevice_log(LOG_WARNING, "Unable to unregister votequorum device. Error %s", cs_strerror(res));
+       }
+
+       res = votequorum_finalize(instance->votequorum_handle);
+       if (res != CS_OK) {
+               qdevice_log(LOG_WARNING, "Unable to finalize votequorum. Error %s", cs_strerror(res));
+       }
+}
+
+int
+qdevice_votequorum_wait_for_ring_id(struct qdevice_instance *instance)
+{
+       int no_retries;
+
+       no_retries = 0;
+
+       while (qdevice_votequorum_dispatch(instance) != -1 &&
+           no_retries++ < instance->advanced_settings->max_cs_try_again &&
+           !instance->vq_node_list_initial_ring_id_set) {
+               (void)poll(NULL, 0, 1000);
+       }
+
+       if (!instance->vq_node_list_initial_ring_id_set) {
+               qdevice_log(LOG_CRIT, "Can't get initial votequorum membership information.");
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_votequorum_dispatch(struct qdevice_instance *instance)
+{
+       cs_error_t res;
+
+       res = votequorum_dispatch(instance->votequorum_handle, CS_DISPATCH_ALL);
+
+       if (res != CS_OK && res != CS_ERR_TRY_AGAIN) {
+               qdevice_log(LOG_ERR, "Can't dispatch votequorum messages");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qdevice_votequorum_poll(struct qdevice_instance *instance, int cast_vote)
+{
+       cs_error_t res;
+
+       instance->vq_last_poll = time(NULL);
+       instance->vq_last_poll_cast_vote = cast_vote;
+
+       res = votequorum_qdevice_poll(instance->votequorum_handle,
+           instance->advanced_settings->votequorum_device_name, cast_vote,
+           instance->vq_poll_ring_id);
+
+       if (res != CS_OK && res != CS_ERR_TRY_AGAIN) {
+               if (res == CS_ERR_MESSAGE_ERROR) {
+                       qdevice_log(LOG_INFO, "qdevice_votequorum_poll called with old ring id");
+               } else {
+                       qdevice_log(LOG_CRIT, "Can't call votequorum_qdevice_poll. Error %s",
+                           cs_strerror(res));
+
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+int
+qdevice_votequorum_master_wins(struct qdevice_instance *instance, int allow)
+{
+       cs_error_t res;
+       int final_allow;
+
+       final_allow = allow;
+
+       if (instance->advanced_settings->master_wins ==
+           QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_OFF && allow) {
+               qdevice_log(LOG_WARNING, "Allow of master wins is requested, but user forcibly "
+                   "disallowed it. Keeping master wins disallowed.");
+
+               final_allow = 0;
+       }
+
+       if (instance->advanced_settings->master_wins ==
+           QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_ON && !allow) {
+               qdevice_log(LOG_WARNING, "Disallow of master wins is requested, but user forcibly "
+                   "allowed it. Keeping master wins allowed.");
+
+               final_allow = 1;
+       }
+
+       res = votequorum_qdevice_master_wins(instance->votequorum_handle,
+           instance->advanced_settings->votequorum_device_name, final_allow);
+
+       if (res != CS_OK) {
+               qdevice_log(LOG_CRIT, "Can't set master wins. Error %s", cs_strerror(res));
+
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qdevice-votequorum.h b/qdevices/qdevice-votequorum.h
new file mode 100644 (file)
index 0000000..0d01d77
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QDEVICE_VOTEQUORUM_H_
+#define _QDEVICE_VOTEQUORUM_H_
+
+#include <corosync/votequorum.h>
+
+#include "qdevice-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void                    qdevice_votequorum_init(struct qdevice_instance *instance);
+
+extern void                    qdevice_votequorum_destroy(struct qdevice_instance *instance);
+
+extern int                     qdevice_votequorum_dispatch(struct qdevice_instance *instance);
+
+extern int                     qdevice_votequorum_poll(struct qdevice_instance *instance,
+    int cast_vote);
+
+extern int                     qdevice_votequorum_wait_for_ring_id(
+    struct qdevice_instance *instance);
+
+extern int                     qdevice_votequorum_master_wins(struct qdevice_instance *instance,
+    int allow);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QDEVICE_VOTEQUORUM_H_ */
diff --git a/qdevices/qnet-config.h b/qdevices/qnet-config.h
new file mode 100644 (file)
index 0000000..8435b53
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNET_CONFIG_H_
+#define _QNET_CONFIG_H_
+
+#include <config.h>
+
+#include "tlv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * There are "hardcoded" defaults for both qnetd and qdevice-net. It's not so good
+ * idea to change them as long as you are not 100% sure what you are doing. Also
+ * most of them can be changed in CLI via advanced_settings (-S).
+ */
+
+#define QNETD_PROGRAM_NAME                             "corosync-qnetd"
+#define QNETD_DEFAULT_HOST_PORT                                5403
+#define QNETD_DEFAULT_LISTEN_BACKLOG                   10
+#define QNETD_MIN_LISTEN_BACKLOG                       1
+#define QNETD_DEFAULT_MAX_CLIENT_SEND_BUFFERS          32
+#define QNETD_MIN_CLIENT_SEND_BUFFERS                  2
+#define QNETD_DEFAULT_MAX_CLIENT_SEND_SIZE             (1 << 15)
+#define QNETD_DEFAULT_MAX_CLIENT_RECEIVE_SIZE          (1 << 15)
+#define QNETD_MIN_CLIENT_RECEIVE_SEND_SIZE             16
+#define QNETD_DEFAULT_MAX_CLIENTS                      0
+
+#define QNETD_DEFAULT_NSS_DB_DIR                       COROSYSCONFDIR "/qnetd/nssdb"
+#define QNETD_DEFAULT_CERT_NICKNAME                    "QNetd Cert"
+
+#define QNETD_DEFAULT_TLS_SUPPORTED                    TLV_TLS_SUPPORTED
+#define QNETD_DEFAULT_TLS_CLIENT_CERT_REQUIRED         1
+
+#define QNETD_DEFAULT_HEARTBEAT_INTERVAL_MIN           (1*1000)
+#define QNETD_DEFAULT_HEARTBEAT_INTERVAL_MAX           (2*60*1000)
+#define QNETD_MIN_HEARTBEAT_INTERVAL                   1
+
+#define QNETD_DEFAULT_DPD_ENABLED                      1
+#define QNETD_DEFAULT_DPD_INTERVAL                     (10*1000)
+#define QNETD_MIN_DPD_INTERVAL                         1
+
+#define QNETD_DEFAULT_LOCK_FILE                                LOCALSTATEDIR"/run/corosync-qnetd/corosync-qnetd.pid"
+#define QNETD_DEFAULT_LOCAL_SOCKET_FILE                        LOCALSTATEDIR"/run/corosync-qnetd/corosync-qnetd.sock"
+#define QNETD_DEFAULT_LOCAL_SOCKET_BACKLOG             10
+#define QNETD_MIN_LOCAL_SOCKET_BACKLOG                 1
+
+#define QNETD_DEFAULT_IPC_MAX_CLIENTS                  10
+#define QNETD_MIN_IPC_MAX_CLIENTS                      0
+#define QNETD_DEFAULT_IPC_MAX_RECEIVE_SIZE             (4*1024)
+#define QNETD_DEFAULT_IPC_MAX_SEND_SIZE                        (10*1024*1024)
+#define QNETD_MIN_IPC_RECEIVE_SEND_SIZE                        1024
+
+#define QNETD_TOOL_PROGRAM_NAME                                "corosync-qnetd-tool"
+
+#define QDEVICE_NET_DEFAULT_NSS_DB_DIR                 COROSYSCONFDIR "/qdevice/net/nssdb"
+
+#define QDEVICE_NET_DEFAULT_INITIAL_MSG_RECEIVE_SIZE   (1 << 15)
+#define QDEVICE_NET_DEFAULT_INITIAL_MSG_SEND_SIZE      (1 << 15)
+#define QDEVICE_NET_DEFAULT_MIN_MSG_SEND_SIZE          QDEVICE_NET_DEFAULT_INITIAL_MSG_SEND_SIZE
+#define QDEVICE_NET_DEFAULT_MAX_MSG_RECEIVE_SIZE       (1 << 24)
+#define QDEVICE_NET_DEFAULT_MAX_SEND_BUFFERS           10
+#define QDEVICE_NET_MIN_MAX_SEND_BUFFERS               2
+#define QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE          16
+
+#define QDEVICE_NET_DEFAULT_NSS_QNETD_CN               "Qnetd Server"
+
+#define QDEVICE_NET_DEFAULT_NSS_CLIENT_CERT_NICKNAME   "Cluster Cert"
+
+
+#define QDEVICE_NET_DEFAULT_ALGORITHM                  TLV_DECISION_ALGORITHM_TYPE_FFSPLIT
+
+#define QDEVICE_NET_DEFAULT_TLS_SUPPORTED              TLV_TLS_SUPPORTED
+
+#define QDEVICE_NET_DEFAULT_TIE_BREAKER_MODE           TLV_TIE_BREAKER_MODE_LOWEST
+
+#define QDEVICE_NET_DEFAULT_HEARTBEAT_INTERVAL_MIN     QNETD_DEFAULT_HEARTBEAT_INTERVAL_MIN
+#define QDEVICE_NET_DEFAULT_HEARTBEAT_INTERVAL_MAX     QNETD_DEFAULT_HEARTBEAT_INTERVAL_MAX
+#define QDEVICE_NET_MIN_HEARTBEAT_INTERVAL             1
+
+#define QDEVICE_NET_DEFAULT_MIN_CONNECT_TIMEOUT                (1*1000)
+#define QDEVICE_NET_DEFAULT_MAX_CONNECT_TIMEOUT                (2*60*1000)
+#define QDEVICE_NET_MIN_CONNECT_TIMEOUT                        1
+
+#ifdef DEBUG
+#define QDEVICE_NET_DEFAULT_TEST_ALGORITHM_ENABLED     1
+#else
+#define QDEVICE_NET_DEFAULT_TEST_ALGORITHM_ENABLED     0
+#endif
+
+/*
+ * Decision algorithms supported by qnetd
+ */
+#define QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE        4
+
+extern enum tlv_decision_algorithm_type
+    qnetd_static_supported_decision_algorithms[QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE];
+
+#define QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE  QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNET_CONFIG_H_ */
diff --git a/qdevices/qnetd-advanced-settings.c b/qdevices/qnetd-advanced-settings.c
new file mode 100644 (file)
index 0000000..f1eff37
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "dynar.h"
+#include "dynar-getopt-lex.h"
+#include "dynar-str.h"
+#include "qnet-config.h"
+#include "qnetd-advanced-settings.h"
+#include "utils.h"
+
+int
+qnetd_advanced_settings_init(struct qnetd_advanced_settings *settings)
+{
+
+       memset(settings, 0, sizeof(*settings));
+       settings->listen_backlog = QNETD_DEFAULT_LISTEN_BACKLOG;
+       settings->max_client_send_buffers = QNETD_DEFAULT_MAX_CLIENT_SEND_BUFFERS;
+       settings->max_client_send_size = QNETD_DEFAULT_MAX_CLIENT_SEND_SIZE;
+       settings->max_client_receive_size = QNETD_DEFAULT_MAX_CLIENT_RECEIVE_SIZE;
+       if ((settings->nss_db_dir = strdup(QNETD_DEFAULT_NSS_DB_DIR)) == NULL) {
+               return (-1);
+       }
+       if ((settings->cert_nickname = strdup(QNETD_DEFAULT_CERT_NICKNAME)) == NULL) {
+               return (-1);
+       }
+       settings->heartbeat_interval_min = QNETD_DEFAULT_HEARTBEAT_INTERVAL_MIN;
+       settings->heartbeat_interval_max = QNETD_DEFAULT_HEARTBEAT_INTERVAL_MAX;
+       settings->dpd_enabled = QNETD_DEFAULT_DPD_ENABLED;
+       settings->dpd_interval = QNETD_DEFAULT_DPD_INTERVAL;
+       if ((settings->lock_file = strdup(QNETD_DEFAULT_LOCK_FILE)) == NULL) {
+               return (-1);
+       }
+       if ((settings->local_socket_file = strdup(QNETD_DEFAULT_LOCAL_SOCKET_FILE)) == NULL) {
+               return (-1);
+       }
+       settings->local_socket_backlog = QNETD_DEFAULT_LOCAL_SOCKET_BACKLOG;
+       settings->ipc_max_clients = QNETD_DEFAULT_IPC_MAX_CLIENTS;
+       settings->ipc_max_receive_size = QNETD_DEFAULT_IPC_MAX_RECEIVE_SIZE;
+       settings->ipc_max_send_size = QNETD_DEFAULT_IPC_MAX_SEND_SIZE;
+
+       return (0);
+}
+
+void
+qnetd_advanced_settings_destroy(struct qnetd_advanced_settings *settings)
+{
+
+       free(settings->nss_db_dir);
+       free(settings->cert_nickname);
+       free(settings->lock_file);
+       free(settings->local_socket_file);
+}
+
+/*
+ * 0 - No error
+ * -1 - Unknown option
+ * -2 - Incorrect value
+ */
+int
+qnetd_advanced_settings_set(struct qnetd_advanced_settings *settings,
+    const char *option, const char *value)
+{
+       long long int tmpll;
+       char *ep;
+
+       if (strcasecmp(option, "listen_backlog") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_LISTEN_BACKLOG || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->listen_backlog = (int)tmpll;
+       } else if (strcasecmp(option, "max_client_send_buffers") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_CLIENT_SEND_BUFFERS || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->max_client_send_buffers = (size_t)tmpll;
+       } else if (strcasecmp(option, "max_client_send_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_CLIENT_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->max_client_send_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "max_client_receive_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_CLIENT_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->max_client_receive_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "nss_db_dir") == 0) {
+               free(settings->nss_db_dir);
+
+               if ((settings->nss_db_dir = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "cert_nickname") == 0) {
+               free(settings->cert_nickname);
+
+               if ((settings->cert_nickname = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "heartbeat_interval_min") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_HEARTBEAT_INTERVAL || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heartbeat_interval_min = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "heartbeat_interval_max") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_HEARTBEAT_INTERVAL || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->heartbeat_interval_max = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "dpd_enabled") == 0) {
+               if ((tmpll = utils_parse_bool_str(value)) == -1) {
+                       return (-2);
+               }
+
+               settings->dpd_enabled = (uint8_t)tmpll;
+       } else if (strcasecmp(option, "dpd_interval") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_DPD_INTERVAL || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->dpd_interval = (uint32_t)tmpll;
+       } else if (strcasecmp(option, "lock_file") == 0) {
+               free(settings->lock_file);
+
+               if ((settings->lock_file = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "local_socket_file") == 0) {
+               free(settings->local_socket_file);
+
+               if ((settings->local_socket_file = strdup(value)) == NULL) {
+                       return (-1);
+               }
+       } else if (strcasecmp(option, "local_socket_backlog") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_LOCAL_SOCKET_BACKLOG || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->local_socket_backlog = (int)tmpll;
+       } else if (strcasecmp(option, "ipc_max_clients") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_IPC_MAX_CLIENTS || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->ipc_max_clients = (size_t)tmpll;
+       } else if (strcasecmp(option, "ipc_max_receive_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_IPC_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->ipc_max_receive_size = (size_t)tmpll;
+       } else if (strcasecmp(option, "ipc_max_send_size") == 0) {
+               tmpll = strtoll(value, &ep, 10);
+               if (tmpll < QNETD_MIN_IPC_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') {
+                       return (-2);
+               }
+
+               settings->ipc_max_send_size = (size_t)tmpll;
+       } else {
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qnetd-advanced-settings.h b/qdevices/qnetd-advanced-settings.h
new file mode 100644 (file)
index 0000000..b4ad0ae
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_ADVANCED_SETTINGS_H_
+#define _QNETD_ADVANCED_SETTINGS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qnetd_advanced_settings {
+       int listen_backlog;
+       size_t max_client_send_buffers;
+       size_t max_client_send_size;
+       size_t max_client_receive_size;
+       char *nss_db_dir;
+       char *cert_nickname;
+       uint32_t heartbeat_interval_min;
+       uint32_t heartbeat_interval_max;
+       uint8_t dpd_enabled;
+       uint32_t dpd_interval;
+       char *lock_file;
+       char *local_socket_file;
+       int local_socket_backlog;
+       size_t ipc_max_clients;
+       size_t ipc_max_send_size;
+       size_t ipc_max_receive_size;
+};
+
+extern int             qnetd_advanced_settings_init(struct qnetd_advanced_settings *settings);
+
+extern int             qnetd_advanced_settings_set(struct qnetd_advanced_settings *settings,
+    const char *option, const char *value);
+
+extern void            qnetd_advanced_settings_destroy(struct qnetd_advanced_settings *settings);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_ADVANCED_SETTINGS_H_ */
diff --git a/qdevices/qnetd-algo-2nodelms.c b/qdevices/qnetd-algo-2nodelms.c
new file mode 100644 (file)
index 0000000..079aca3
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Christine Caulfield (ccaulfie@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/*
+ * This is a simple 'last man standing' algorithm for 2 node clusters
+ *
+ * If the node is the only one left in the cluster that can see the
+ * qdevice server then we return a vote.
+ *
+ * If more than one node can see the qdevice server but the nodes can't
+ * see each other then we return a vote to the nominated tie_breaker node
+ *
+ * If there are more than two nodes, then we don't return a vote.
+ * this is not our job.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+#include <limits.h>
+
+#include "qnetd-algo-2nodelms.h"
+#include "qnetd-log.h"
+#include "qnetd-cluster-list.h"
+#include "qnetd-algo-utils.h"
+#include "utils.h"
+
+struct qnetd_algo_2nodelms_info {
+       int num_config_nodes;
+       enum tlv_vote last_result;
+};
+
+enum tlv_reply_error_code
+qnetd_algo_2nodelms_client_init(struct qnetd_client *client)
+{
+       struct qnetd_algo_2nodelms_info *info;
+
+       info = malloc(sizeof(struct qnetd_algo_2nodelms_info));
+       if (!info) {
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+       client->algorithm_data = info;
+       info->last_result = 0;
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * Called after client sent configuration node list
+ * All client fields are already set. Nodes is actual node list, initial is used
+ * to distinquish between initial node list and changed node list.
+ * msg_seq_num is 32-bit number set by client. If client sent config file version,
+ * config_version_set is set to 1 and config_version contains valid config file version.
+ *
+ * Function has to return result_vote. This can be one of ack/nack, ask_later (client
+ * should ask later for a vote) or wait_for_reply (client should wait for reply).
+ *
+ * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_*
+ * on failure (error is sent back to client)
+ */
+enum tlv_reply_error_code
+qnetd_algo_2nodelms_config_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, int config_version_set, uint64_t config_version,
+    const struct node_list *nodes, int initial, enum tlv_vote *result_vote)
+{
+       struct node_list_entry *node_info;
+       struct qnetd_algo_2nodelms_info *info = client->algorithm_data;
+       int node_count = 0;
+
+       /* Check this is a 2 node cluster */
+       TAILQ_FOREACH(node_info, nodes, entries) {
+               node_count++;
+       }
+       info->num_config_nodes = node_count;
+       qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s config_list has %d nodes", client->cluster_name, node_count);
+
+       if (node_count != 2) {
+               qnetd_log(LOG_INFO, "algo-2nodelms: cluster %s does not have 2 configured nodes, it has %d", client->cluster_name, node_count);
+
+               *result_vote = TLV_VOTE_NACK;
+               return (TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM);
+       }
+
+       *result_vote = TLV_VOTE_NO_CHANGE;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * Called after client sent membership node list.
+ * All client fields are already set. Nodes is actual node list.
+ * msg_seq_num is 32-bit number set by client. If client sent config file version,
+ * config_version_set is set to 1 and config_version contains valid config file version.
+ * ring_id and quorate are copied from client votequorum callback.
+ *
+ * Function has to return result_vote. This can be one of ack/nack, ask_later (client
+ * should ask later for a vote) or wait_for_reply (client should wait for reply).
+ *
+ * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_*
+ * on failure (error is sent back to client)
+ */
+
+enum tlv_reply_error_code
+qnetd_algo_2nodelms_membership_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, const struct tlv_ring_id *ring_id,
+    const struct node_list *nodes, enum tlv_heuristics heuristics,
+    enum tlv_vote *result_vote)
+{
+       struct node_list_entry *node_info;
+       struct qnetd_client *other_client;
+       struct qnetd_algo_2nodelms_info *info = client->algorithm_data;
+       int node_count = 0;
+       uint32_t low_node_id = UINT32_MAX;
+       uint32_t high_node_id = 0;
+       enum tlv_heuristics other_node_heuristics;
+
+       /* If we're a newcomer and there is another active partition, then we must NACK
+        * to avoid quorum moving to us from already active nodes.
+        */
+       if (info->last_result == 0) {
+               TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) {
+                       struct qnetd_algo_2nodelms_info *other_info = other_client->algorithm_data;
+                       if (!tlv_ring_id_eq(ring_id, &other_client->last_ring_id) &&
+                           other_info->last_result == TLV_VOTE_ACK) {
+
+                               /* Don't save NACK, we need to know subsequently if we haven't been voting */
+                               *result_vote = TLV_VOTE_NACK;
+                               qnetd_log(LOG_DEBUG, "algo-2nodelms: we are a new partition and another active partition exists. NACK");
+                               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+                       }
+               }
+       }
+
+       /* If both nodes are present, then we're OK. return a vote */
+       TAILQ_FOREACH(node_info, nodes, entries) {
+               node_count++;
+       }
+
+       qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s (client %p nodeid "UTILS_PRI_NODE_ID") membership list has %d member nodes (ring ID "UTILS_PRI_RING_ID")", client->cluster_name, client, client->node_id, node_count, ring_id->node_id, ring_id->seq);
+
+       if (node_count == 2) {
+               qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running normally. Both nodes active", client->cluster_name);
+               *result_vote = info->last_result = TLV_VOTE_ACK;
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+       /* Now look for other clients connected from this cluster that can't see us any more */
+       node_count = 0;
+       other_node_heuristics = TLV_HEURISTICS_UNDEFINED;
+       TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) {
+               node_count++;
+
+               qnetd_log(LOG_DEBUG, "algo-2nodelms: seen nodeid "UTILS_PRI_NODE_ID" on client %p (ring ID "UTILS_PRI_RING_ID")", other_client->node_id, other_client, other_client->last_ring_id.node_id, other_client->last_ring_id.seq);
+               if (other_client->node_id < low_node_id) {
+                       low_node_id = other_client->node_id;
+               }
+               if (other_client->node_id > high_node_id) {
+                       high_node_id = other_client->node_id;
+               }
+               if (other_client != client) {
+                       other_node_heuristics = other_client->last_heuristics;
+               }
+       }
+       qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s %d nodes running independently", client->cluster_name, node_count);
+
+       /* Only 1 node alive .. allow it to continue */
+       if (node_count == 1) {
+               qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running on 'last-man'", client->cluster_name);
+               *result_vote = info->last_result = TLV_VOTE_ACK;
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+       /*
+        * Both nodes are alive.
+        * Check their heuristics.
+        */
+       if (tlv_heuristics_cmp(heuristics, other_node_heuristics) > 0) {
+               *result_vote = info->last_result = TLV_VOTE_ACK;
+
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       } else if (tlv_heuristics_cmp(heuristics, other_node_heuristics) < 0) {
+               *result_vote = info->last_result = TLV_VOTE_NACK;
+
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+       /* Heuristics are equal -> Only give a vote to the nominated tie-breaker node */
+       switch (client->tie_breaker.mode) {
+
+       case TLV_TIE_BREAKER_MODE_LOWEST:
+               if (client->node_id == low_node_id) {
+                       qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running on low node-id %d", client->cluster_name, low_node_id);
+                       *result_vote = info->last_result = TLV_VOTE_ACK;
+               }
+               else {
+                       qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s node-id %d denied vote because low nodeid %d is active", client->cluster_name, client->node_id, low_node_id);
+                       *result_vote = info->last_result = TLV_VOTE_NACK;
+               }
+               break;
+       case TLV_TIE_BREAKER_MODE_HIGHEST:
+               if (client->node_id == high_node_id) {
+                       qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running on high node-id %d", client->cluster_name, high_node_id);
+                       *result_vote = info->last_result = TLV_VOTE_ACK;
+               }
+               else {
+                       qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s node-id %d denied vote because high nodeid %d is active", client->cluster_name, client->node_id, high_node_id);
+                       *result_vote = info->last_result = TLV_VOTE_NACK;
+               }
+               break;
+       case TLV_TIE_BREAKER_MODE_NODE_ID:
+               if (client->node_id == client->tie_breaker.node_id) {
+                       qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running on nominated tie-breaker node %d", client->cluster_name, client->tie_breaker.node_id);
+                       *result_vote = info->last_result = TLV_VOTE_ACK;
+               }
+               else {
+                       qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s node-id %d denied vote because nominated tie-breaker nodeid %d is active", client->cluster_name, client->node_id, client->tie_breaker.node_id);
+                       *result_vote = info->last_result = TLV_VOTE_NACK;
+               }
+               break;
+       default:
+               qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s node-id %d denied vote because tie-breaker option is invalid: %d", client->cluster_name, client->node_id, client->tie_breaker.mode);
+               *result_vote = info->last_result = TLV_VOTE_NACK;
+       }
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_2nodelms_quorum_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes,
+    enum tlv_vote *result_vote)
+{
+
+       *result_vote = TLV_VOTE_NO_CHANGE;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * Called after client disconnect. Client structure is still existing (and it's part
+ * of a client->cluster), but it is destroyed (and removed from cluster) right after
+ * this callback finishes. Callback is used mainly for destroing client->algorithm_data.
+ */
+void
+qnetd_algo_2nodelms_client_disconnect(struct qnetd_client *client, int server_going_down)
+{
+       qnetd_log(LOG_INFO, "algo-2nodelms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "disconnect", client, client->cluster_name, client->node_id);
+
+       qnetd_log(LOG_INFO, "algo-2nodelms:   server going down %u", server_going_down);
+
+       free(client->algorithm_data);
+}
+
+/*
+ * Called after client sent ask for vote message. This is usually happening after server
+ * replied TLV_VOTE_ASK_LATER.
+ */
+enum tlv_reply_error_code
+qnetd_algo_2nodelms_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_vote *result_vote)
+{
+       struct qnetd_algo_2nodelms_info *info = client->algorithm_data;
+
+       qnetd_log(LOG_INFO, "algo-2nodelms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "asked for a vote", client, client->cluster_name, client->node_id);
+
+       if (info->last_result == 0) {
+               *result_vote =  TLV_VOTE_ASK_LATER;
+       }
+       else {
+               *result_vote =  info->last_result;
+       }
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_2nodelms_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num)
+{
+
+       qnetd_log(LOG_INFO, "algo-2nodelms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "replied back to vote info message", client, client->cluster_name, client->node_id);
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_2nodelms_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics, enum tlv_vote *result_vote)
+{
+
+       qnetd_log(LOG_INFO, "algo-2nodelms: heuristics change is not supported.");
+
+       *result_vote = TLV_VOTE_NO_CHANGE;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_2nodelms_timer_callback(struct qnetd_client *client, int *reschedule_timer,
+    int *send_vote, enum tlv_vote *result_vote)
+{
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+static struct qnetd_algorithm qnetd_algo_2nodelms = {
+       .init                           = qnetd_algo_2nodelms_client_init,
+       .config_node_list_received      = qnetd_algo_2nodelms_config_node_list_received,
+       .membership_node_list_received  = qnetd_algo_2nodelms_membership_node_list_received,
+       .quorum_node_list_received      = qnetd_algo_2nodelms_quorum_node_list_received,
+       .client_disconnect              = qnetd_algo_2nodelms_client_disconnect,
+       .ask_for_vote_received          = qnetd_algo_2nodelms_ask_for_vote_received,
+       .vote_info_reply_received       = qnetd_algo_2nodelms_vote_info_reply_received,
+       .heuristics_change_received     = qnetd_algo_2nodelms_heuristics_change_received,
+       .timer_callback                 = qnetd_algo_2nodelms_timer_callback,
+};
+
+enum tlv_reply_error_code qnetd_algo_2nodelms_register()
+{
+       return qnetd_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_2NODELMS, &qnetd_algo_2nodelms);
+}
diff --git a/qdevices/qnetd-algo-2nodelms.h b/qdevices/qnetd-algo-2nodelms.h
new file mode 100644 (file)
index 0000000..3a48861
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_ALGO_2NODELMS_H_
+#define _QNETD_ALGO_2NODELMS_H_
+
+#include "qnetd-algorithm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern enum tlv_reply_error_code       qnetd_algo_2nodelms_client_init(struct qnetd_client *client);
+
+extern enum tlv_reply_error_code       qnetd_algo_2nodelms_config_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set,
+    uint64_t config_version, const struct node_list *nodes, int initial,
+    enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_2nodelms_membership_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    const struct tlv_ring_id *ring_id,
+    const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_2nodelms_quorum_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote);
+
+extern void                            qnetd_algo_2nodelms_client_disconnect(
+    struct qnetd_client *client, int server_going_down);
+
+extern enum tlv_reply_error_code       qnetd_algo_2nodelms_ask_for_vote_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_2nodelms_vote_info_reply_received(
+    struct qnetd_client *client, uint32_t msg_seq_num);
+
+extern enum tlv_reply_error_code       qnetd_algo_2nodelms_heuristics_change_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_2nodelms_timer_callback(
+    struct qnetd_client *client, int *reschedule_timer,
+    int *send_vote, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code qnetd_algo_2nodelms_register(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_ALGO_2NODELMS_H_ */
diff --git a/qdevices/qnetd-algo-ffsplit.c b/qdevices/qnetd-algo-ffsplit.c
new file mode 100644 (file)
index 0000000..3c8e6db
--- /dev/null
@@ -0,0 +1,885 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "qnetd-algo-ffsplit.h"
+#include "qnetd-log.h"
+#include "qnetd-log-debug.h"
+#include "qnetd-cluster-list.h"
+#include "qnetd-cluster.h"
+#include "qnetd-client-send.h"
+
+enum qnetd_algo_ffsplit_cluster_state {
+       QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE,
+       QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_STABLE_MEMBERSHIP,
+       QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_NACKS,
+       QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_ACKS,
+};
+
+struct qnetd_algo_ffsplit_cluster_data {
+       enum qnetd_algo_ffsplit_cluster_state cluster_state;
+       const struct node_list *quorate_partition_node_list;
+};
+
+enum qnetd_algo_ffsplit_client_state {
+       QNETD_ALGO_FFSPLIT_CLIENT_STATE_WAITING_FOR_CHANGE,
+       QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK,
+       QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK,
+};
+
+struct qnetd_algo_ffsplit_client_data {
+       enum qnetd_algo_ffsplit_client_state client_state;
+       uint32_t vote_info_expected_seq_num;
+};
+
+enum tlv_reply_error_code
+qnetd_algo_ffsplit_client_init(struct qnetd_client *client)
+{
+       struct qnetd_algo_ffsplit_cluster_data *cluster_data;
+       struct qnetd_algo_ffsplit_client_data *client_data;
+
+       if (qnetd_cluster_size(client->cluster) == 1) {
+               cluster_data = malloc(sizeof(*cluster_data));
+               if (cluster_data == NULL) {
+                       qnetd_log(LOG_ERR, "ffsplit: Can't initialize cluster data for client %s",
+                           client->addr_str);
+
+                       return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+               }
+               memset(cluster_data, 0, sizeof(*cluster_data));
+               cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE;
+               cluster_data->quorate_partition_node_list = NULL;
+
+               client->cluster->algorithm_data = cluster_data;
+       }
+
+       client_data = malloc(sizeof(*client_data));
+       if (client_data == NULL) {
+               qnetd_log(LOG_ERR, "ffsplit: Can't initialize node data for client %s",
+                   client->addr_str);
+
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+       memset(client_data, 0, sizeof(*client_data));
+       client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_WAITING_FOR_CHANGE;
+       client->algorithm_data = client_data;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+static int
+qnetd_algo_ffsplit_is_preferred_partition(const struct qnetd_client *client,
+    const struct node_list *config_node_list, const struct node_list *membership_node_list)
+{
+       uint32_t preferred_node_id;
+       struct node_list_entry *node_entry;
+       int case_processed;
+
+       preferred_node_id = 0;
+       case_processed = 0;
+
+       switch (client->tie_breaker.mode) {
+       case TLV_TIE_BREAKER_MODE_LOWEST:
+               node_entry = TAILQ_FIRST(config_node_list);
+
+               preferred_node_id = node_entry->node_id;
+
+               TAILQ_FOREACH(node_entry, config_node_list, entries) {
+                       if (node_entry->node_id < preferred_node_id) {
+                               preferred_node_id = node_entry->node_id;
+                       }
+               }
+               case_processed = 1;
+               break;
+       case TLV_TIE_BREAKER_MODE_HIGHEST:
+               node_entry = TAILQ_FIRST(config_node_list);
+
+               preferred_node_id = node_entry->node_id;
+
+               TAILQ_FOREACH(node_entry, config_node_list, entries) {
+                       if (node_entry->node_id > preferred_node_id) {
+                               preferred_node_id = node_entry->node_id;
+                       }
+               }
+               case_processed = 1;
+               break;
+       case TLV_TIE_BREAKER_MODE_NODE_ID:
+               preferred_node_id = client->tie_breaker.node_id;
+               case_processed = 1;
+               break;
+       }
+
+       if (!case_processed) {
+               qnetd_log(LOG_CRIT, "qnetd_algo_ffsplit_is_preferred_partition unprocessed "
+                   "tie_breaker.mode");
+               exit(1);
+       }
+
+       return (node_list_find_node_id(membership_node_list, preferred_node_id) != NULL);
+}
+
+static int
+qnetd_algo_ffsplit_is_membership_stable(const struct qnetd_client *client, int client_leaving,
+    const struct tlv_ring_id *ring_id, const struct node_list *config_node_list,
+    const struct node_list *membership_node_list)
+{
+       const struct qnetd_client *iter_client1, *iter_client2;
+       const struct node_list *config_node_list1, *config_node_list2;
+       const struct node_list *membership_node_list1, *membership_node_list2;
+       const struct node_list_entry *iter_node1, *iter_node2;
+       const struct node_list_entry *iter_node3, *iter_node4;
+       const struct tlv_ring_id *ring_id1, *ring_id2;
+
+       /*
+        * Test if all active clients share same config list.
+        */
+       TAILQ_FOREACH(iter_client1, &client->cluster->client_list, cluster_entries) {
+               TAILQ_FOREACH(iter_client2, &client->cluster->client_list, cluster_entries) {
+                       if (iter_client1 == iter_client2) {
+                               continue;
+                       }
+
+                       if (iter_client1->node_id == client->node_id) {
+                               if (client_leaving) {
+                                       continue;
+                               }
+
+                               config_node_list1 = config_node_list;
+                       } else {
+                               config_node_list1 = &iter_client1->configuration_node_list;
+                       }
+
+                       if (iter_client2->node_id == client->node_id) {
+                               if (client_leaving) {
+                                       continue;
+                               }
+
+                               config_node_list2 = config_node_list;
+                       } else {
+                               config_node_list2 = &iter_client2->configuration_node_list;
+                       }
+
+                       /*
+                        * Walk thru all node ids in given config node list...
+                        */
+                       TAILQ_FOREACH(iter_node1, config_node_list1, entries) {
+                               /*
+                                * ... and try to find given node id in other list
+                                */
+                               iter_node2 = node_list_find_node_id(config_node_list2, iter_node1->node_id);
+
+                               if (iter_node2 == NULL) {
+                                       /*
+                                        * Node with iter_node1->node_id was not found in
+                                        * config_node_list2 -> lists doesn't match
+                                        */
+                                       return (0);
+                               }
+                       }
+               }
+       }
+
+       /*
+        * Test if same partitions share same ring ids and membership node list
+        */
+       TAILQ_FOREACH(iter_client1, &client->cluster->client_list, cluster_entries) {
+               if (iter_client1->node_id == client->node_id) {
+                       if (client_leaving) {
+                               continue;
+                       }
+
+                       membership_node_list1 = membership_node_list;
+                       ring_id1 = ring_id;
+               } else {
+                       membership_node_list1 = &iter_client1->last_membership_node_list;
+                       ring_id1 = &iter_client1->last_ring_id;
+               }
+
+               /*
+                * Walk thru all memberships nodes
+                */
+               TAILQ_FOREACH(iter_node1, membership_node_list1, entries) {
+                       /*
+                        * try to find client with given node id
+                        */
+                       iter_client2 = qnetd_cluster_find_client_by_node_id(client->cluster,
+                           iter_node1->node_id);
+                       if (iter_client2 == NULL) {
+                               /*
+                                * Client with given id is not connected
+                                */
+                               continue;
+                       }
+
+                       if (iter_client2->node_id == client->node_id) {
+                               if (client_leaving) {
+                                       continue;
+                               }
+
+                               membership_node_list2 = membership_node_list;
+                               ring_id2 = ring_id;
+                       } else {
+                               membership_node_list2 = &iter_client2->last_membership_node_list;
+                               ring_id2 = &iter_client2->last_ring_id;
+                       }
+
+                       /*
+                        * Compare ring ids
+                        */
+                       if (!tlv_ring_id_eq(ring_id1, ring_id2)) {
+                               return (0);
+                       }
+
+                       /*
+                        * Now compare that membership node list equals, so walk thru all
+                        * members ...
+                        */
+                       TAILQ_FOREACH(iter_node3, membership_node_list1, entries) {
+                               /*
+                                * ... and try to find given node id in other membership node list
+                                */
+                               iter_node4 = node_list_find_node_id(membership_node_list2, iter_node3->node_id);
+
+                               if (iter_node4 == NULL) {
+                                       /*
+                                        * Node with iter_node3->node_id was not found in
+                                        * membership_node_list2 -> lists doesn't match
+                                        */
+                                       return (0);
+                               }
+                       }
+               }
+       }
+
+       return (1);
+}
+
+static void
+qnetd_algo_ffsplit_get_active_clients_in_partition_stats(const struct qnetd_client *client,
+    const struct node_list *client_membership_node_list, enum tlv_heuristics client_heuristics,
+    size_t *no_clients, size_t *no_heuristics_pass, size_t *no_heuristics_fail)
+{
+       const struct node_list_entry *iter_node;
+       const struct qnetd_client *iter_client;
+       enum tlv_heuristics iter_heuristics;
+
+       *no_clients = 0;
+       *no_heuristics_pass = 0;
+       *no_heuristics_fail = 0;
+
+       if (client == NULL || client_membership_node_list == NULL) {
+               return ;
+       }
+
+       TAILQ_FOREACH(iter_node, client_membership_node_list, entries) {
+               iter_client = qnetd_cluster_find_client_by_node_id(client->cluster,
+                   iter_node->node_id);
+               if (iter_client != NULL) {
+                       (*no_clients)++;
+
+                       if (iter_client == client) {
+                               iter_heuristics = client_heuristics;
+                       } else {
+                               iter_heuristics = iter_client->last_heuristics;
+                       }
+
+                       if (iter_heuristics == TLV_HEURISTICS_PASS) {
+                               (*no_heuristics_pass)++;
+                       } else if (iter_heuristics == TLV_HEURISTICS_FAIL) {
+                               (*no_heuristics_fail)++;
+                       }
+               }
+       }
+}
+
+/*
+ * Compares two partitions. Return 1 if client1, config_node_list1, membership_node_list1 is
+ * "better" than client2, config_node_list2, membership_node_list2
+ */
+static int
+qnetd_algo_ffsplit_partition_cmp(const struct qnetd_client *client1,
+    const struct node_list *config_node_list1, const struct node_list *membership_node_list1,
+    enum tlv_heuristics heuristics_1,
+    const struct qnetd_client *client2,
+    const struct node_list *config_node_list2, const struct node_list *membership_node_list2,
+    enum tlv_heuristics heuristics_2)
+{
+       size_t part1_active_clients, part2_active_clients;
+       size_t part1_no_heuristics_pass, part2_no_heuristics_pass;
+       size_t part1_no_heuristics_fail, part2_no_heuristics_fail;
+       size_t part1_score, part2_score;
+
+       int res;
+
+       res = -1;
+
+       if (node_list_size(config_node_list1) % 2 != 0) {
+               /*
+                * Odd clusters never split into 50:50.
+                */
+               if (node_list_size(membership_node_list1) > node_list_size(config_node_list1) / 2) {
+                       res = 1; goto exit_res;
+               } else {
+                       res = 0; goto exit_res;
+               }
+       } else {
+               if (node_list_size(membership_node_list1) > node_list_size(config_node_list1) / 2) {
+                       res = 1; goto exit_res;
+               } else if (node_list_size(membership_node_list1) < node_list_size(config_node_list1) / 2) {
+                       res = 0; goto exit_res;
+               }
+
+               /*
+                * 50:50 split
+                */
+
+               /*
+                * Check how many active clients are in partitions and heuristics results
+                */
+               qnetd_algo_ffsplit_get_active_clients_in_partition_stats(client1,
+                   membership_node_list1, heuristics_1, &part1_active_clients,
+                   &part1_no_heuristics_pass, &part1_no_heuristics_fail);
+               qnetd_algo_ffsplit_get_active_clients_in_partition_stats(client2,
+                   membership_node_list2, heuristics_2, &part2_active_clients,
+                   &part2_no_heuristics_pass, &part2_no_heuristics_fail);
+
+               /*
+                * Partition can contain clients with one of 4 states:
+                * 1. Not-connected to qnetd (D)
+                * 2. Disabled heuristics (U)
+                * 3. Enabled heuristics with pass result (P)
+                * 4. Enabled heuristics with fail result (F)
+                *
+                * The question is, what partition should get vote is kind of hard with
+                * so much states. Following simple "score" seems to be good enough, but may
+                * be suboptimal in some cases. As and example let's say there are
+                * 2 partitions with 4 nodes each. Partition 1 looks like PDDD and partition 2 looks
+                * like FUUU. Partition 1 score is 1 + (1 - 0), partition 2 score is 4 + (0 - 1).
+                * Partition 2 wins eventho there is one processor with failed heuristics.
+                */
+               part1_score = part1_active_clients + (part1_no_heuristics_pass - part1_no_heuristics_fail);
+               part2_score = part2_active_clients + (part2_no_heuristics_pass - part2_no_heuristics_fail);
+
+               if (part1_score > part2_score) {
+                       res = 1; goto exit_res;
+               } else if (part1_score < part2_score) {
+                       res = 0; goto exit_res;
+               }
+
+               if (part1_active_clients > part2_active_clients) {
+                       res = 1; goto exit_res;
+               } else if (part1_active_clients < part2_active_clients) {
+                       res = 0; goto exit_res;
+               }
+
+               /*
+                * Number of active clients in both partitions equals. Use tie-breaker.
+                */
+
+               if (qnetd_algo_ffsplit_is_preferred_partition(client1, config_node_list1,
+                   membership_node_list1)) {
+                       res = 1; goto exit_res;
+               } else {
+                       res = 0; goto exit_res;
+               }
+       }
+
+exit_res:
+       if (res == -1) {
+               qnetd_log(LOG_CRIT, "qnetd_algo_ffsplit_partition_cmp unhandled case");
+               exit(1);
+               /* NOTREACHED */
+       }
+
+       return (res);
+}
+
+/*
+ * Select best partition for given client->cluster.
+ * If there is no partition which could become quorate, NULL is returned
+ */
+static const struct node_list *
+qnetd_algo_ffsplit_select_partition(const struct qnetd_client *client, int client_leaving,
+    const struct node_list *config_node_list, const struct node_list *membership_node_list,
+    enum tlv_heuristics client_heuristics)
+{
+       const struct qnetd_client *iter_client;
+       const struct qnetd_client *best_client;
+       const struct node_list *best_config_node_list, *best_membership_node_list;
+       const struct node_list *iter_config_node_list, *iter_membership_node_list;
+       enum tlv_heuristics iter_heuristics, best_heuristics;
+
+       best_client = NULL;
+       best_config_node_list = best_membership_node_list = NULL;
+       best_heuristics = TLV_HEURISTICS_UNDEFINED;
+
+       /*
+        * Get highest score
+        */
+       TAILQ_FOREACH(iter_client, &client->cluster->client_list, cluster_entries) {
+               if (iter_client->node_id == client->node_id) {
+                       if (client_leaving) {
+                               continue;
+                       }
+
+                       iter_config_node_list = config_node_list;
+                       iter_membership_node_list = membership_node_list;
+                       iter_heuristics = client_heuristics;
+               } else {
+                       iter_config_node_list = &iter_client->configuration_node_list;
+                       iter_membership_node_list = &iter_client->last_membership_node_list;
+                       iter_heuristics = iter_client->last_heuristics;
+               }
+
+               if (qnetd_algo_ffsplit_partition_cmp(iter_client, iter_config_node_list,
+                   iter_membership_node_list, iter_heuristics, best_client, best_config_node_list,
+                   best_membership_node_list, best_heuristics) > 0) {
+                       best_client = iter_client;
+                       best_config_node_list = iter_config_node_list;
+                       best_membership_node_list = iter_membership_node_list;
+                       best_heuristics = iter_heuristics;
+               }
+       }
+
+       return (best_membership_node_list);
+}
+
+/*
+ * Update state of all nodes to match quorate_partition_node_list
+ */
+static void
+qnetd_algo_ffsplit_update_nodes_state(struct qnetd_client *client, int client_leaving,
+    const struct node_list *quorate_partition_node_list)
+{
+       const struct qnetd_client *iter_client;
+       struct qnetd_algo_ffsplit_client_data *iter_client_data;
+
+       TAILQ_FOREACH(iter_client, &client->cluster->client_list, cluster_entries) {
+               iter_client_data = (struct qnetd_algo_ffsplit_client_data *)iter_client->algorithm_data;
+
+               if (iter_client->node_id == client->node_id && client_leaving) {
+                       iter_client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_WAITING_FOR_CHANGE;
+
+                       continue;
+               }
+
+               if (quorate_partition_node_list == NULL ||
+                   node_list_find_node_id(quorate_partition_node_list, iter_client->node_id) == NULL) {
+                       iter_client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK;
+               } else {
+                       iter_client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK;
+               }
+       }
+}
+
+/*
+ * Send vote info. If client_leaving is set, client is ignored. if send_acks
+ * is set, only ACK votes are sent (nodes in QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK state),
+ * otherwise only NACK votes are sent (nodes in QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK state)
+ *
+ * Returns number of send votes
+ */
+static size_t
+qnetd_algo_ffsplit_send_votes(struct qnetd_client *client, int client_leaving,
+    const struct tlv_ring_id *ring_id, int send_acks)
+{
+       size_t sent_votes;
+       struct qnetd_client *iter_client;
+       struct qnetd_algo_ffsplit_client_data *iter_client_data;
+       const struct tlv_ring_id *ring_id_to_send;
+       enum tlv_vote vote_to_send;
+
+       sent_votes = 0;
+
+       TAILQ_FOREACH(iter_client, &client->cluster->client_list, cluster_entries) {
+               if (iter_client->node_id == client->node_id) {
+                       if (client_leaving) {
+                               continue;
+                       }
+
+                       ring_id_to_send = ring_id;
+               } else {
+                       ring_id_to_send = &iter_client->last_ring_id;
+               }
+
+               iter_client_data = (struct qnetd_algo_ffsplit_client_data *)iter_client->algorithm_data;
+               vote_to_send = TLV_VOTE_UNDEFINED;
+
+               if (send_acks) {
+                       if (iter_client_data->client_state == QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK) {
+                               vote_to_send = TLV_VOTE_ACK;
+                       }
+               } else {
+                       if (iter_client_data->client_state == QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK) {
+                               vote_to_send = TLV_VOTE_NACK;
+                       }
+               }
+
+               if (vote_to_send != TLV_VOTE_UNDEFINED) {
+                       iter_client_data->vote_info_expected_seq_num++;
+                       sent_votes++;
+
+                       if (qnetd_client_send_vote_info(iter_client,
+                           iter_client_data->vote_info_expected_seq_num, ring_id_to_send,
+                           vote_to_send) == -1) {
+                               client->schedule_disconnect = 1;
+                       }
+               }
+       }
+
+       return (sent_votes);
+}
+
+/*
+ * Return number of clients in QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK state if sending_acks is
+ * set or number of nodes in QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK state if sending_acks is
+ * not set
+ */
+static size_t
+qnetd_algo_ffsplit_no_clients_in_sending_state(struct qnetd_client *client, int sending_acks)
+{
+       size_t no_clients;
+       struct qnetd_client *iter_client;
+       struct qnetd_algo_ffsplit_client_data *iter_client_data;
+
+       no_clients = 0;
+
+       TAILQ_FOREACH(iter_client, &client->cluster->client_list, cluster_entries) {
+               iter_client_data = (struct qnetd_algo_ffsplit_client_data *)iter_client->algorithm_data;
+
+               if (sending_acks &&
+                   iter_client_data->client_state == QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK) {
+                       no_clients++;
+               }
+
+               if (!sending_acks &&
+                   iter_client_data->client_state == QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK) {
+                       no_clients++;
+               }
+       }
+
+       return (no_clients);
+}
+
+static enum tlv_vote
+qnetd_algo_ffsplit_do(struct qnetd_client *client, int client_leaving,
+    const struct tlv_ring_id *ring_id, const struct node_list *config_node_list,
+    const struct node_list *membership_node_list, enum tlv_heuristics client_heuristics)
+{
+       struct qnetd_algo_ffsplit_cluster_data *cluster_data;
+       const struct node_list *quorate_partition_node_list;
+
+       cluster_data = (struct qnetd_algo_ffsplit_cluster_data *)client->cluster->algorithm_data;
+
+       cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_STABLE_MEMBERSHIP;
+
+       if (!qnetd_algo_ffsplit_is_membership_stable(client, client_leaving,
+           ring_id, config_node_list, membership_node_list)) {
+               /*
+                * Wait until membership is stable
+                */
+               qnetd_log(LOG_DEBUG, "ffsplit: Membership for cluster %s is not yet stable", client->cluster_name);
+
+               return (TLV_VOTE_WAIT_FOR_REPLY);
+       }
+
+       qnetd_log(LOG_DEBUG, "ffsplit: Membership for cluster %s is now stable", client->cluster_name);
+
+       quorate_partition_node_list = qnetd_algo_ffsplit_select_partition(client, client_leaving,
+           config_node_list, membership_node_list, client_heuristics);
+       cluster_data->quorate_partition_node_list = quorate_partition_node_list;
+
+       if (quorate_partition_node_list == NULL) {
+               qnetd_log(LOG_DEBUG, "ffsplit: No quorate partition was selected");
+       } else {
+               qnetd_log(LOG_DEBUG, "ffsplit: Quorate partition selected");
+               qnetd_log_debug_dump_node_list(client, quorate_partition_node_list);
+       }
+
+       qnetd_algo_ffsplit_update_nodes_state(client, client_leaving, quorate_partition_node_list);
+
+       cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_NACKS;
+
+       if (qnetd_algo_ffsplit_send_votes(client, client_leaving, ring_id, 0) == 0) {
+               qnetd_log(LOG_DEBUG, "ffsplit: No client gets NACK");
+               /*
+                * No one gets nack -> send acks
+                */
+               cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_ACKS;
+
+               if (qnetd_algo_ffsplit_send_votes(client, client_leaving, ring_id, 1) == 0) {
+                       qnetd_log(LOG_DEBUG, "ffsplit: No client gets ACK");
+                       /*
+                        * No one gets acks -> finished
+                        */
+                       cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE;
+               }
+       }
+
+       return (TLV_VOTE_NO_CHANGE);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_ffsplit_config_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, int config_version_set, uint64_t config_version,
+    const struct node_list *nodes, int initial, enum tlv_vote *result_vote)
+{
+
+       if (node_list_size(nodes) == 0) {
+               /*
+                * Empty node list shouldn't happen
+                */
+               qnetd_log(LOG_ERR, "ffsplit: Received empty config node list for client %s",
+                           client->addr_str);
+
+               return (TLV_REPLY_ERROR_CODE_INVALID_CONFIG_NODE_LIST);
+       }
+
+       if (node_list_find_node_id(nodes, client->node_id) == NULL) {
+               /*
+                * Current node is not in node list
+                */
+               qnetd_log(LOG_ERR, "ffsplit: Received config node list without client %s",
+                           client->addr_str);
+
+               return (TLV_REPLY_ERROR_CODE_INVALID_CONFIG_NODE_LIST);
+       }
+
+       if (initial || node_list_size(&client->last_membership_node_list) == 0) {
+               /*
+                * Initial node list -> membership is going to be send by client
+                */
+               *result_vote = TLV_VOTE_ASK_LATER;
+       } else {
+               *result_vote = qnetd_algo_ffsplit_do(client, 0, &client->last_ring_id,
+                   nodes, &client->last_membership_node_list, client->last_heuristics);
+       }
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * Called after client sent membership node list.
+ * All client fields are already set. Nodes is actual node list.
+ * msg_seq_num is 32-bit number set by client. If client sent config file version,
+ * config_version_set is set to 1 and config_version contains valid config file version.
+ * ring_id and quorate are copied from client votequorum callback.
+ *
+ * Function has to return result_vote. This can be one of ack/nack, ask_later (client
+ * should ask later for a vote) or wait_for_reply (client should wait for reply).
+ *
+ * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_*
+ * on failure (error is send back to client)
+ */
+
+enum tlv_reply_error_code
+qnetd_algo_ffsplit_membership_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, const struct tlv_ring_id *ring_id,
+    const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote)
+{
+
+       if (node_list_size(nodes) == 0) {
+               /*
+                * Empty node list shouldn't happen
+                */
+               qnetd_log(LOG_ERR, "ffsplit: Received empty membership node list for client %s",
+                           client->addr_str);
+
+               return (TLV_REPLY_ERROR_CODE_INVALID_MEMBERSHIP_NODE_LIST);
+       }
+
+       if (node_list_find_node_id(nodes, client->node_id) == NULL) {
+               /*
+                * Current node is not in node list
+                */
+               qnetd_log(LOG_ERR, "ffsplit: Received membership node list without client %s",
+                           client->addr_str);
+
+               return (TLV_REPLY_ERROR_CODE_INVALID_MEMBERSHIP_NODE_LIST);
+       }
+
+       if (node_list_size(&client->configuration_node_list) == 0) {
+               /*
+                * Config node list not received -> it's going to be sent later
+                */
+               *result_vote = TLV_VOTE_ASK_LATER;
+       } else {
+               *result_vote = qnetd_algo_ffsplit_do(client, 0, ring_id,
+                   &client->configuration_node_list, nodes, heuristics);
+       }
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_ffsplit_quorum_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes,
+    enum tlv_vote *result_vote)
+{
+
+       /*
+        * Quorum node list is informative -> no change
+        */
+       *result_vote = TLV_VOTE_NO_CHANGE;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+void
+qnetd_algo_ffsplit_client_disconnect(struct qnetd_client *client, int server_going_down)
+{
+
+       (void)qnetd_algo_ffsplit_do(client, 1, &client->last_ring_id,
+           &client->configuration_node_list, &client->last_membership_node_list,
+           client->last_heuristics);
+
+       free(client->algorithm_data);
+
+       if (qnetd_cluster_size(client->cluster) == 1) {
+               /*
+                * Last client in the cluster
+                */
+                free(client->cluster->algorithm_data);
+       }
+}
+
+enum tlv_reply_error_code
+qnetd_algo_ffsplit_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_vote *result_vote)
+{
+
+       /*
+        * Ask for vote is not supported in current algorithm
+        */
+       return (TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM_MESSAGE);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_ffsplit_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num)
+{
+       struct qnetd_algo_ffsplit_cluster_data *cluster_data;
+       struct qnetd_algo_ffsplit_client_data *client_data;
+
+       cluster_data = (struct qnetd_algo_ffsplit_cluster_data *)client->cluster->algorithm_data;
+       client_data = (struct qnetd_algo_ffsplit_client_data *)client->algorithm_data;
+
+       if (client_data->vote_info_expected_seq_num != msg_seq_num) {
+               qnetd_log(LOG_DEBUG, "ffsplit: Received old vote info reply from client %s",
+                   client->addr_str);
+
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+       client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_WAITING_FOR_CHANGE;
+
+       if (cluster_data->cluster_state != QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_NACKS &&
+           cluster_data->cluster_state != QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_ACKS) {
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+       if (cluster_data->cluster_state == QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_NACKS) {
+               if (qnetd_algo_ffsplit_no_clients_in_sending_state(client, 0) == 0) {
+                       qnetd_log(LOG_DEBUG, "ffsplit: All NACK votes sent for cluster %s",
+                            client->cluster_name);
+
+                       cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_ACKS;
+
+                       if (qnetd_algo_ffsplit_send_votes(client, 0, &client->last_ring_id, 1) == 0) {
+                               qnetd_log(LOG_DEBUG, "ffsplit: No client gets ACK");
+                               /*
+                                * No one gets acks -> finished
+                                */
+                               cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE;
+                       }
+               }
+       } else {
+               if (qnetd_algo_ffsplit_no_clients_in_sending_state(client, 1) == 0) {
+                       qnetd_log(LOG_DEBUG, "ffsplit: All ACK votes sent for cluster %s",
+                            client->cluster_name);
+
+                       cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE;
+               }
+       }
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_ffsplit_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics, enum tlv_vote *result_vote)
+{
+
+       if (node_list_size(&client->configuration_node_list) == 0 ||
+           node_list_size(&client->last_membership_node_list) == 0) {
+               /*
+                * Config or membership node list not received -> it's going to be sent later
+                */
+               *result_vote = TLV_VOTE_ASK_LATER;
+       } else {
+               *result_vote = qnetd_algo_ffsplit_do(client, 0, &client->last_ring_id,
+                   &client->configuration_node_list, &client->last_membership_node_list,
+                   heuristics);
+       }
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_ffsplit_timer_callback(struct qnetd_client *client, int *reschedule_timer,
+    int *send_vote, enum tlv_vote *result_vote)
+{
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+static struct qnetd_algorithm qnetd_algo_ffsplit = {
+       .init                           = qnetd_algo_ffsplit_client_init,
+       .config_node_list_received      = qnetd_algo_ffsplit_config_node_list_received,
+       .membership_node_list_received  = qnetd_algo_ffsplit_membership_node_list_received,
+       .quorum_node_list_received      = qnetd_algo_ffsplit_quorum_node_list_received,
+       .client_disconnect              = qnetd_algo_ffsplit_client_disconnect,
+       .ask_for_vote_received          = qnetd_algo_ffsplit_ask_for_vote_received,
+       .vote_info_reply_received       = qnetd_algo_ffsplit_vote_info_reply_received,
+       .heuristics_change_received     = qnetd_algo_ffsplit_heuristics_change_received,
+       .timer_callback                 = qnetd_algo_ffsplit_timer_callback,
+};
+
+enum tlv_reply_error_code qnetd_algo_ffsplit_register()
+{
+
+       return (qnetd_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_FFSPLIT, &qnetd_algo_ffsplit));
+}
diff --git a/qdevices/qnetd-algo-ffsplit.h b/qdevices/qnetd-algo-ffsplit.h
new file mode 100644 (file)
index 0000000..4891b89
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_ALGO_FFSPLIT_H_
+#define _QNETD_ALGO_FFSPLIT_H_
+
+#include "qnetd-algorithm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern enum tlv_reply_error_code       qnetd_algo_ffsplit_client_init(struct qnetd_client *client);
+
+extern enum tlv_reply_error_code       qnetd_algo_ffsplit_config_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set,
+    uint64_t config_version, const struct node_list *nodes, int initial,
+    enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_ffsplit_membership_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    const struct tlv_ring_id *ring_id,
+    const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_ffsplit_quorum_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote);
+
+extern void                            qnetd_algo_ffsplit_client_disconnect(
+    struct qnetd_client *client, int server_going_down);
+
+extern enum tlv_reply_error_code       qnetd_algo_ffsplit_ask_for_vote_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_ffsplit_vote_info_reply_received(
+    struct qnetd_client *client, uint32_t msg_seq_num);
+
+extern enum tlv_reply_error_code       qnetd_algo_ffsplit_heuristics_change_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_ffsplit_timer_callback(
+    struct qnetd_client *client, int *reschedule_timer,
+    int *send_vote, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code qnetd_algo_ffsplit_register(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_ALGO_FFSPLIT_H_ */
diff --git a/qdevices/qnetd-algo-lms.c b/qdevices/qnetd-algo-lms.c
new file mode 100644 (file)
index 0000000..47b5262
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Christine Caulfield (ccaulfie@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/*
+ * This is a 'last man standing' algorithm for 2+ node clusters
+ *
+ * If the node is the only one left in the cluster that can see the
+ * qdevice server then we return a vote.
+ *
+ * If more than one node can see the qdevice server but some nodes can't
+ * see each other then we divide the cluster up into 'partitions' based on
+ * their ring_id and return a vote to nodes in the partition that contains
+ * a nominated nodeid. (lowest, highest, etc)
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <string.h>
+#include <limits.h>
+
+#include "qnetd-algo-lms.h"
+#include "qnetd-log.h"
+#include "qnetd-cluster-list.h"
+#include "qnetd-algo-utils.h"
+#include "qnetd-client-algo-timer.h"
+#include "utils.h"
+
+struct qnetd_algo_lms_info {
+       int num_config_nodes;
+       enum tlv_vote last_result;
+       partitions_list_t partition_list;
+};
+
+static enum tlv_reply_error_code do_lms_algorithm(struct qnetd_client *client, const struct tlv_ring_id *cur_ring_id, enum tlv_vote *result_vote)
+{
+       struct qnetd_client *other_client;
+       struct qnetd_algo_lms_info *info = client->algorithm_data;
+       struct qnetd_algo_partition *cur_partition;
+       struct qnetd_algo_partition *largest_partition;
+       struct qnetd_algo_partition *best_score_partition;
+       const struct tlv_ring_id *ring_id = cur_ring_id;
+       int num_partitions;
+       int joint_leader;
+
+       /* We are running the algorithm, don't do it again unless we say so */
+       qnetd_client_algo_timer_abort(client);
+
+       if (qnetd_algo_all_ring_ids_match(client, ring_id) == -1) {
+               qnetd_log(LOG_DEBUG, "algo-lms: nodeid %d: ring ID (" UTILS_PRI_RING_ID ") not unique in this membership, waiting",
+                         client->node_id, ring_id->node_id, ring_id->seq);
+
+               qnetd_client_algo_timer_schedule(client);
+               *result_vote = info->last_result = TLV_VOTE_WAIT_FOR_REPLY;
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+       /* Create and count the number of separate partitions */
+       if ( (num_partitions = qnetd_algo_create_partitions(client, &info->partition_list, ring_id)) == -1) {
+               qnetd_log(LOG_DEBUG, "algo-lms: Error creating partition list");
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       /* This can happen if we are first on the block */
+       if (num_partitions == 0) {
+               qnetd_log(LOG_DEBUG, "algo-lms: No partitions found");
+
+               qnetd_client_algo_timer_schedule(client);
+               *result_vote = info->last_result = TLV_VOTE_WAIT_FOR_REPLY;
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+       qnetd_algo_dump_partitions(&info->partition_list);
+
+       /* Only 1 partition - let votequorum sort it out */
+       if (num_partitions == 1) {
+               qnetd_log(LOG_DEBUG, "algo-lms: Only 1 partition. This is votequorum's problem, not ours");
+               qnetd_algo_free_partitions(&info->partition_list);
+               *result_vote = info->last_result = TLV_VOTE_ACK;
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+
+       /* If we're a newcomer and there is another active partition, then we must NACK
+        * to avoid quorum moving to us from already active nodes.
+        */
+       if (info->last_result == 0) {
+               TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) {
+                       struct qnetd_algo_lms_info *other_info = other_client->algorithm_data;
+                       if (!tlv_ring_id_eq(ring_id, &other_client->last_ring_id) &&
+                           other_info->last_result == TLV_VOTE_ACK) {
+                               qnetd_algo_free_partitions(&info->partition_list);
+
+                               /* Don't save NACK, we need to know subsequently if we haven't been voting */
+                               *result_vote = TLV_VOTE_NACK;
+                               qnetd_log(LOG_DEBUG, "algo-lms: we are a new partition and another active partition exists. NACK");
+                               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+                       }
+               }
+       }
+
+       /*
+        * Find the partition with highest score
+        */
+       best_score_partition = NULL;
+       TAILQ_FOREACH(cur_partition, &info->partition_list, entries) {
+               if (!best_score_partition ||
+                   best_score_partition->score < cur_partition->score) {
+                       best_score_partition = cur_partition;
+               }
+       }
+       qnetd_log(LOG_DEBUG, "algo-lms: best score partition is (" UTILS_PRI_RING_ID ") with score %d",
+                 best_score_partition->ring_id.node_id, best_score_partition->ring_id.seq, best_score_partition->score);
+
+       /* Now check if it's really the highest score, and not just the joint-highest */
+       joint_leader = 0;
+       TAILQ_FOREACH(cur_partition, &info->partition_list, entries) {
+               if (best_score_partition != cur_partition &&
+                   best_score_partition->score == cur_partition->score) {
+                       joint_leader = 1;
+               }
+       }
+
+       if (!joint_leader) {
+               /* Partition with highest score is unique, allow us to run if we're in that partition. */
+               if (tlv_ring_id_eq(&best_score_partition->ring_id, ring_id)) {
+                       qnetd_log(LOG_DEBUG, "algo-lms: We are in the best score partition. ACK");
+                       *result_vote = info->last_result = TLV_VOTE_ACK;
+               }
+               else {
+                       qnetd_log(LOG_DEBUG, "algo-lms: We are NOT in the best score partition. NACK");
+                       *result_vote = info->last_result = TLV_VOTE_NACK;
+               }
+
+               qnetd_algo_free_partitions(&info->partition_list);
+
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+       /*
+        * There are multiple partitions with same score. Find the largest partition
+        */
+       largest_partition = NULL;
+       TAILQ_FOREACH(cur_partition, &info->partition_list, entries) {
+               if (!largest_partition ||
+                   largest_partition->num_nodes < cur_partition->num_nodes) {
+                       largest_partition = cur_partition;
+               }
+       }
+
+       qnetd_log(LOG_DEBUG, "algo-lms: largest partition is (" UTILS_PRI_RING_ID ") with %d nodes",
+                 largest_partition->ring_id.node_id, largest_partition->ring_id.seq, largest_partition->num_nodes);
+
+       /* Now check if it's really the largest, and not just the joint-largest */
+       joint_leader = 0;
+       TAILQ_FOREACH(cur_partition, &info->partition_list, entries) {
+               if (largest_partition != cur_partition &&
+                   largest_partition->num_nodes == cur_partition->num_nodes) {
+                       joint_leader = 1;
+               }
+       }
+
+       if (!joint_leader) {
+               /* Largest partition is unique, allow us to run if we're in that partition. */
+               if (tlv_ring_id_eq(&largest_partition->ring_id, ring_id)) {
+                       qnetd_log(LOG_DEBUG, "algo-lms: We are in the largest partition. ACK");
+                       *result_vote = info->last_result = TLV_VOTE_ACK;
+               }
+               else {
+                       qnetd_log(LOG_DEBUG, "algo-lms: We are NOT in the largest partition. NACK");
+                       *result_vote = info->last_result = TLV_VOTE_NACK;
+               }
+       }
+       else {
+               uint32_t tb_node_id;
+               struct tlv_ring_id tb_node_ring_id = {0LL, 0};
+
+               /* Look for the tie-breaker node */
+               if (client->tie_breaker.mode == TLV_TIE_BREAKER_MODE_LOWEST) {
+                       tb_node_id = INT_MAX;
+               }
+               else if (client->tie_breaker.mode == TLV_TIE_BREAKER_MODE_HIGHEST) {
+                       tb_node_id = 0;
+               }
+               else if (client->tie_breaker.mode == TLV_TIE_BREAKER_MODE_NODE_ID) {
+                       tb_node_id = client->tie_breaker.node_id;
+               }
+               else {
+                       qnetd_log(LOG_DEBUG, "algo-lms: denied vote because tie-breaker option is invalid: %d",
+                                 client->tie_breaker.mode);
+                       tb_node_id = -1;
+               }
+
+               /* Find the tie_breaker node */
+               TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) {
+                       switch (client->tie_breaker.mode) {
+
+                       case TLV_TIE_BREAKER_MODE_LOWEST:
+                               if (other_client->node_id < tb_node_id) {
+                                       tb_node_id = other_client->node_id;
+                                       memcpy(&tb_node_ring_id, &other_client->last_ring_id, sizeof(struct tlv_ring_id));
+                                       qnetd_log(LOG_DEBUG, "algo-lms: Looking for low node ID. found %d (" UTILS_PRI_RING_ID ")",
+                                                 tb_node_id, tb_node_ring_id.node_id, tb_node_ring_id.seq);
+                               }
+                       break;
+
+                       case TLV_TIE_BREAKER_MODE_HIGHEST:
+                               if (other_client->node_id > tb_node_id) {
+                                       tb_node_id = other_client->node_id;
+                                       memcpy(&tb_node_ring_id, &other_client->last_ring_id, sizeof(struct tlv_ring_id));
+                                       qnetd_log(LOG_DEBUG, "algo-lms: Looking for high node ID. found %d (" UTILS_PRI_RING_ID ")",
+                                                 tb_node_id, tb_node_ring_id.node_id, tb_node_ring_id.seq);
+                               }
+                       break;
+                       case TLV_TIE_BREAKER_MODE_NODE_ID:
+                               if (client->tie_breaker.node_id == client->node_id) {
+                                       memcpy(&tb_node_ring_id, &other_client->last_ring_id, sizeof(struct tlv_ring_id));
+                                       qnetd_log(LOG_DEBUG, "algo-lms: Looking for nominated node ID. found %d (" UTILS_PRI_RING_ID ")",
+                                                 tb_node_id, tb_node_ring_id.node_id, tb_node_ring_id.seq);
+
+                               }
+                               break;
+                       default:
+                               qnetd_log(LOG_DEBUG, "algo-lms: denied vote because tie-breaker option is invalid: %d",
+                                         client->tie_breaker.mode);
+                               memset(&tb_node_ring_id, 0, sizeof(struct tlv_ring_id));
+                       }
+               }
+
+               if (client->node_id == tb_node_id || tlv_ring_id_eq(&tb_node_ring_id, ring_id)) {
+                       qnetd_log(LOG_DEBUG, "algo-lms: We are in the same partition (" UTILS_PRI_RING_ID ") as tie-breaker node id %d. ACK",
+                                 tb_node_ring_id.node_id, tb_node_ring_id.seq, tb_node_id);
+                       *result_vote = info->last_result = TLV_VOTE_ACK;
+               }
+               else {
+                       qnetd_log(LOG_DEBUG, "algo-lms: We are NOT in the same partition (" UTILS_PRI_RING_ID ") as tie-breaker node id %d. NACK",
+                                 tb_node_ring_id.node_id, tb_node_ring_id.seq, tb_node_id);
+                       *result_vote = info->last_result = TLV_VOTE_NACK;
+               }
+       }
+
+       qnetd_algo_free_partitions(&info->partition_list);
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_lms_client_init(struct qnetd_client *client)
+{
+       struct qnetd_algo_lms_info *info;
+
+       info = malloc(sizeof(struct qnetd_algo_lms_info));
+       if (!info) {
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       memset(info, 0, sizeof(*info));
+       client->algorithm_data = info;
+       info->last_result = 0; /* status unknown, or NEW */
+       TAILQ_INIT(&info->partition_list);
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * We got the config node list. Simply count the number of available nodes
+ * and wait for the quorum list.
+ */
+enum tlv_reply_error_code
+qnetd_algo_lms_config_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, int config_version_set, uint64_t config_version,
+    const struct node_list *nodes, int initial, enum tlv_vote *result_vote)
+{
+       struct node_list_entry *node_info;
+       struct qnetd_algo_lms_info *info = client->algorithm_data;
+       int node_count = 0;
+
+       TAILQ_FOREACH(node_info, nodes, entries) {
+               node_count++;
+       }
+       info->num_config_nodes = node_count;
+       qnetd_log(LOG_DEBUG, "algo-lms: cluster %s config_list has %d nodes", client->cluster_name, node_count);
+
+       *result_vote = TLV_VOTE_NO_CHANGE;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * membership node list. This is where we get to work.
+ */
+
+enum tlv_reply_error_code
+qnetd_algo_lms_membership_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, const struct tlv_ring_id *ring_id,
+    const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote)
+{
+       qnetd_log(LOG_DEBUG, " ");
+       qnetd_log(LOG_DEBUG, "algo-lms: membership list from node %d partition (" UTILS_PRI_RING_ID ")", client->node_id, ring_id->node_id, ring_id->seq);
+
+       return do_lms_algorithm(client, ring_id, result_vote);
+}
+
+/*
+ * The quorum node list is received after corosync has decided which nodes are in the cluster.
+ * We run our algorithm again to be sure that things still match. By this time we will (or should)
+ * all know the current ring_id (not guaranteed when the membership list is received). So this
+ * might be the most reliable return.
+ */
+enum tlv_reply_error_code
+qnetd_algo_lms_quorum_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote)
+{
+       qnetd_log(LOG_DEBUG, " ");
+       qnetd_log(LOG_DEBUG, "algo-lms: quorum node list from node %d partition (" UTILS_PRI_RING_ID ")", client->node_id, client->last_ring_id.node_id, client->last_ring_id.seq);
+       return do_lms_algorithm(client, &client->last_ring_id, result_vote);
+}
+
+/*
+ * Called after client disconnect. Client structure is still existing (and it's part
+ * of a client->cluster), but it is destroyed (and removed from cluster) right after
+ * this callback finishes. Callback is used mainly for destroing client->algorithm_data.
+ */
+void
+qnetd_algo_lms_client_disconnect(struct qnetd_client *client, int server_going_down)
+{
+       qnetd_log(LOG_DEBUG, "algo-lms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "disconnect", client, client->cluster_name, client->node_id);
+
+       qnetd_log(LOG_INFO, "algo-lms:   server going down %u", server_going_down);
+
+       free(client->algorithm_data);
+}
+
+/*
+ * Called after client sent ask for vote message. This is usually happening after server
+ * replied TLV_VOTE_WAIT_FOR_REPLY.
+ */
+enum tlv_reply_error_code
+qnetd_algo_lms_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_vote *result_vote)
+{
+       qnetd_log(LOG_DEBUG, " ");
+       qnetd_log(LOG_DEBUG, "algo-lms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "asked for a vote", client, client->cluster_name, client->node_id);
+
+       return do_lms_algorithm(client, &client->last_ring_id, result_vote);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_lms_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num)
+{
+       qnetd_log(LOG_DEBUG, "algo-lms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "replied back to vote info message", client, client->cluster_name, client->node_id);
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_lms_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics, enum tlv_vote *result_vote)
+{
+
+       qnetd_log(LOG_INFO, "algo-lms: heuristics change is not supported.");
+
+       *result_vote = TLV_VOTE_NO_CHANGE;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_lms_timer_callback(struct qnetd_client *client, int *reschedule_timer,
+    int *send_vote, enum tlv_vote *result_vote)
+{
+       enum tlv_reply_error_code ret;
+
+       qnetd_log(LOG_DEBUG, "algo-lms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "Timer callback", client, client->cluster_name, client->node_id);
+
+       ret = do_lms_algorithm(client, &client->last_ring_id, result_vote);
+
+       if (ret == TLV_REPLY_ERROR_CODE_NO_ERROR &&
+           (*result_vote == TLV_VOTE_ACK || *result_vote == TLV_VOTE_NACK)) {
+               *send_vote = 1;
+       }
+
+       if (ret == TLV_REPLY_ERROR_CODE_NO_ERROR &&
+           *result_vote == TLV_VOTE_WAIT_FOR_REPLY) {
+               /*
+                * Reschedule was called in the do_lms_algorithm but algo_timer is
+                * not stack based so there can only be one. So if do_lms aborted
+                * the active timer, and scheduled it again the timer would be aborted
+                * if reschedule_timer was not set.
+                */
+               *reschedule_timer = 1;
+       }
+
+       return ret;
+}
+
+static struct qnetd_algorithm qnetd_algo_lms = {
+       .init                           = qnetd_algo_lms_client_init,
+       .config_node_list_received      = qnetd_algo_lms_config_node_list_received,
+       .membership_node_list_received  = qnetd_algo_lms_membership_node_list_received,
+       .quorum_node_list_received      = qnetd_algo_lms_quorum_node_list_received,
+       .client_disconnect              = qnetd_algo_lms_client_disconnect,
+       .ask_for_vote_received          = qnetd_algo_lms_ask_for_vote_received,
+       .vote_info_reply_received       = qnetd_algo_lms_vote_info_reply_received,
+       .heuristics_change_received     = qnetd_algo_lms_heuristics_change_received,
+       .timer_callback                 = qnetd_algo_lms_timer_callback,
+};
+
+enum tlv_reply_error_code qnetd_algo_lms_register()
+{
+       return qnetd_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_LMS, &qnetd_algo_lms);
+}
diff --git a/qdevices/qnetd-algo-lms.h b/qdevices/qnetd-algo-lms.h
new file mode 100644 (file)
index 0000000..19901c6
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_ALGO_LMS_H_
+#define _QNETD_ALGO_LMS_H_
+
+#include "qnetd-algorithm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern enum tlv_reply_error_code       qnetd_algo_lms_client_init(struct qnetd_client *client);
+
+extern enum tlv_reply_error_code       qnetd_algo_lms_config_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set,
+    uint64_t config_version, const struct node_list *nodes, int initial,
+    enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_lms_membership_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    const struct tlv_ring_id *ring_id,
+    const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_lms_quorum_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote);
+
+extern void                            qnetd_algo_lms_client_disconnect(
+    struct qnetd_client *client, int server_going_down);
+
+extern enum tlv_reply_error_code       qnetd_algo_lms_ask_for_vote_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_lms_vote_info_reply_received(
+    struct qnetd_client *client, uint32_t msg_seq_num);
+
+extern enum tlv_reply_error_code       qnetd_algo_lms_heuristics_change_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_lms_timer_callback(
+    struct qnetd_client *client, int *reschedule_timer,
+    int *send_vote, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code qnetd_algo_lms_register(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_ALGO_LMS_H_ */
diff --git a/qdevices/qnetd-algo-test.c b/qdevices/qnetd-algo-test.c
new file mode 100644 (file)
index 0000000..4276cf7
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "qnetd-algo-test.h"
+#include "qnetd-log.h"
+#include "qnetd-cluster-list.h"
+#include "qnetd-client-send.h"
+#include "qnetd-log-debug.h"
+#include "qnetd-client-algo-timer.h"
+#include "utils.h"
+
+/*
+ * Called right after client sent init message. This happens after initial accept of client,
+ * tls handshake and sending basic information about cluster/client.
+ * Known information:
+ * - client->cluster_name (client->cluster_name_len)
+ * - client->node_id (client->node_id_set = 1)
+ * - client->decision_algorithm
+ * - client->cluster
+ * - client->last_ring_id
+ *
+ * Callback is designed mainly for allocating client->algorithm_data. It's also already
+ * part of the cluster, so can access (alloc) client->cluster->algorithm_data.
+ *
+ * client is initialized qnetd_client structure.
+ *
+ * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_*
+ * on failure (error is sent back to client)
+ */
+enum tlv_reply_error_code
+qnetd_algo_test_client_init(struct qnetd_client *client)
+{
+       int *algo_data;
+
+       qnetd_log(LOG_WARNING, "algo-test: Client %s (cluster = '%s', node_id = "
+           UTILS_PRI_NODE_ID") initiated test algorithm. It's not recommended to use test "
+           "algorithm because it can create multiple quorate partitions!", client->addr_str,
+           client->cluster_name, client->node_id);
+
+       qnetd_log(LOG_INFO, "algo-test: client_init");
+
+       client->algorithm_data = malloc(sizeof(int));
+       if (client->algorithm_data == NULL) {
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       algo_data = client->algorithm_data;
+       *algo_data = 42;
+
+       if (qnetd_cluster_size(client->cluster) == 1) {
+               /*
+                * First client in the cluster
+                */
+               qnetd_log(LOG_INFO, "algo-test: Initializing cluster->algorithm data");
+
+               client->cluster->algorithm_data = malloc(sizeof(int));
+               if (client->cluster->algorithm_data == NULL) {
+                       return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+               }
+
+               algo_data = client->cluster->algorithm_data;
+               *algo_data = 42;
+       }
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+
+/*
+ * Called after client sent configuration node list
+ * All client fields are already set. Nodes is actual node list, initial is used
+ * for distrinquish between initial node list and changed node list.
+ * msg_seq_num is 32-bit number set by client. If client sent config file version,
+ * config_version_set is set to 1 and config_version contains valid config file version.
+ *
+ * Function has to return result_vote. This can be one of ack/nack, ask_later (client
+ * should ask later for a vote) or wait_for_reply (client should wait for reply).
+ *
+ * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_*
+ * on failure (error is send back to client)
+ */
+enum tlv_reply_error_code
+qnetd_algo_test_config_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, int config_version_set, uint64_t config_version,
+    const struct node_list *nodes, int initial, enum tlv_vote *result_vote)
+{
+
+       qnetd_log(LOG_INFO, "algo-test: node_list_received");
+
+       *result_vote = TLV_VOTE_NO_CHANGE;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * Called after client sent membership node list.
+ * All client fields are already set. Nodes is actual node list.
+ * msg_seq_num is 32-bit number set by client.
+ * ring_id is copied from client votequorum callback.
+ * heuristics is result of client heuristics (or TLV_HEURISTICS_UNDEFINED if heuristics
+ *  are disabled or not supported by client)
+ *
+ * Function has to return result_vote. This can be one of ack/nack, ask_later (client
+ * should ask later for a vote) or wait_for_reply (client should wait for reply).
+ *
+ * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_*
+ * on failure (error is sent back to client)
+ */
+
+enum tlv_reply_error_code
+qnetd_algo_test_membership_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, const struct tlv_ring_id *ring_id,
+    const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote)
+{
+
+       qnetd_log(LOG_INFO, "algo-test: membership_node_list_received");
+
+       *result_vote = TLV_VOTE_ACK;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * Called after client sent quorum node list.
+ * All client fields are already set. Nodes is actual node list.
+ * msg_seq_num is 32-bit number set by client.
+ * quorate is copied from client votequorum callback.
+ * Function is just informative. If client vote is required to change, it's possible
+ * to use qnetd_client_send_vote_info.
+ *
+ * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_*
+ * on failure (error is sent back to client)
+ */
+enum tlv_reply_error_code
+qnetd_algo_test_quorum_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes,
+    enum tlv_vote *result_vote)
+{
+
+       qnetd_log(LOG_INFO, "algo-test: quorum_node_list_received");
+
+       *result_vote = TLV_VOTE_NO_CHANGE;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * Called after client disconnect. Client structure is still existing (and it's part
+ * of a client->cluster), but it is destroyed (and removed from cluster) right after
+ * this callback finishes. Callback is used mainly for destroing client->algorithm_data.
+ */
+void
+qnetd_algo_test_client_disconnect(struct qnetd_client *client, int server_going_down)
+{
+
+       qnetd_log(LOG_INFO, "algo-test: client_disconnect");
+
+       free(client->algorithm_data);
+
+       if (qnetd_cluster_size(client->cluster) == 1) {
+               /*
+                * Last client in the cluster
+                */
+               qnetd_log(LOG_INFO, "algo-test: Finalizing cluster->algorithm data");
+
+               free(client->cluster->algorithm_data);
+       }
+}
+
+/*
+ * Called after client sent ask for vote message. This is usually happening after server
+ * replied TLV_VOTE_ASK_LATER.
+ */
+enum tlv_reply_error_code
+qnetd_algo_test_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_vote *result_vote)
+{
+
+       qnetd_log(LOG_INFO, "algo-test: ask_for_vote_received");
+
+       *result_vote = TLV_VOTE_ACK;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+enum tlv_reply_error_code
+qnetd_algo_test_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num)
+{
+
+       qnetd_log(LOG_INFO, "algo-test: vote_info_reply_received");
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * Called after client sent heuristics change message.
+ * heuristics is result of client regular heuristics (cannot be TLV_HEURISTICS_UNDEFINED)
+ * Variables client->last_regular_heuristics and client->last_heuristics are updated after
+ * the call.
+ */
+enum tlv_reply_error_code
+qnetd_algo_test_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics, enum tlv_vote *result_vote)
+{
+
+       qnetd_log(LOG_INFO, "algo-test: heuristics_change_received");
+
+       *result_vote = TLV_VOTE_NO_CHANGE;
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+/*
+ * Called as a result of qnetd_client_algo_timer_schedule function call after timeout expires.
+ *
+ * If send_vote is set by callback to non zero value, result_vote must also be set and such vote is
+ * sent to client. Result_vote is ignored if send_vote = 0 (default).
+ *
+ * If reschedule timer (default value = 0) is set to non zero value, callback is called again later
+ * with same timeout as originaly created.
+ *
+ * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_*
+ * on failure (error is sent back to client)
+ */
+enum tlv_reply_error_code
+qnetd_algo_test_timer_callback(struct qnetd_client *client, int *reschedule_timer,
+    int *send_vote, enum tlv_vote *result_vote)
+{
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+static struct qnetd_algorithm qnetd_algo_test = {
+       .init                           = qnetd_algo_test_client_init,
+       .config_node_list_received      = qnetd_algo_test_config_node_list_received,
+       .membership_node_list_received  = qnetd_algo_test_membership_node_list_received,
+       .quorum_node_list_received      = qnetd_algo_test_quorum_node_list_received,
+       .client_disconnect              = qnetd_algo_test_client_disconnect,
+       .ask_for_vote_received          = qnetd_algo_test_ask_for_vote_received,
+       .vote_info_reply_received       = qnetd_algo_test_vote_info_reply_received,
+       .heuristics_change_received     = qnetd_algo_test_heuristics_change_received,
+       .timer_callback                 = qnetd_algo_test_timer_callback,
+};
+
+enum tlv_reply_error_code qnetd_algo_test_register()
+{
+
+       return (qnetd_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_TEST, &qnetd_algo_test));
+}
diff --git a/qdevices/qnetd-algo-test.h b/qdevices/qnetd-algo-test.h
new file mode 100644 (file)
index 0000000..f2d3084
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_ALGO_TEST_H_
+#define _QNETD_ALGO_TEST_H_
+
+#include "qnetd-algorithm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern enum tlv_reply_error_code       qnetd_algo_test_client_init(struct qnetd_client *client);
+
+extern enum tlv_reply_error_code       qnetd_algo_test_config_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set,
+    uint64_t config_version, const struct node_list *nodes, int initial,
+    enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_test_membership_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    const struct tlv_ring_id *ring_id,
+    const struct node_list *nodes, enum tlv_heuristics heuristics,
+    enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_test_quorum_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote);
+
+extern void                            qnetd_algo_test_client_disconnect(
+    struct qnetd_client *client, int server_going_down);
+
+extern enum tlv_reply_error_code       qnetd_algo_test_ask_for_vote_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_test_vote_info_reply_received(
+    struct qnetd_client *client, uint32_t msg_seq_num);
+
+extern enum tlv_reply_error_code       qnetd_algo_test_heuristics_change_received(
+    struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algo_test_timer_callback(
+    struct qnetd_client *client, int *reschedule_timer,
+    int *send_vote, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code qnetd_algo_test_register(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_ALGO_TEST_H_ */
diff --git a/qdevices/qnetd-algo-utils.c b/qdevices/qnetd-algo-utils.c
new file mode 100644 (file)
index 0000000..b4c8d92
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Christine Caulfield (ccaulfie@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/types.h>
+#include <string.h>
+
+#include "qnetd-log.h"
+#include "qnetd-cluster-list.h"
+#include "qnetd-algo-utils.h"
+#include "utils.h"
+
+/*
+ * Returns -1 if any node that is supposedly in the same cluster partition
+ * as us has a different ring_id.
+ * If this happens it simply means that qnetd does not yet have the full current view
+ * of the cluster and should wait until all of the ring_ids in this membership list match up
+ */
+int
+qnetd_algo_all_ring_ids_match(struct qnetd_client *client, const struct tlv_ring_id *ring_id)
+{
+       struct node_list_entry *node_info;
+       struct qnetd_client *other_client;
+
+       TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) {
+               int in_our_partition = 0;
+
+               if (other_client == client) {
+                       continue; /* We've seen our membership list */
+               }
+               qnetd_log(LOG_DEBUG, "algo-util: all_ring_ids_match: seen nodeid %d (client %p) ring_id (" UTILS_PRI_RING_ID ")", other_client->node_id, other_client, other_client->last_ring_id.node_id, other_client->last_ring_id.seq);
+
+               /* Look down our node list and see if this client is known to us */
+               TAILQ_FOREACH(node_info, &client->last_membership_node_list, entries) {
+                       if (node_info->node_id == other_client->node_id) {
+                               in_our_partition = 1;
+                       }
+               }
+
+               if (in_our_partition == 0) {
+                       /*
+                        * Also try to look from the other side to see if we are
+                        * not in the other node's membership list.
+                        * Because if so it may mean the membership lists are not equal
+                        */
+                       TAILQ_FOREACH(node_info, &other_client->last_membership_node_list, entries) {
+                               if (node_info->node_id == client->node_id) {
+                                       in_our_partition = 1;
+                               }
+                       }
+               }
+
+               /*
+                * If the other nodes on our side of a partition have a different ring ID then
+                * we need to wait until they have all caught up before making a decision
+                */
+               if (in_our_partition && !tlv_ring_id_eq(ring_id, &other_client->last_ring_id)) {
+                       qnetd_log(LOG_DEBUG, "algo-util: nodeid %d in our partition has different ring_id (" UTILS_PRI_RING_ID ") to us (" UTILS_PRI_RING_ID ")", other_client->node_id, other_client->last_ring_id.node_id, other_client->last_ring_id.seq, ring_id->node_id, ring_id->seq);
+                       return (-1); /* ring IDs don't match */
+               }
+       }
+
+       return (0);
+}
+
+struct qnetd_algo_partition *
+qnetd_algo_find_partition(partitions_list_t *partitions_list, const struct tlv_ring_id *ring_id)
+{
+       struct qnetd_algo_partition *cur_partition;
+
+       TAILQ_FOREACH(cur_partition, partitions_list, entries) {
+               if (tlv_ring_id_eq(&cur_partition->ring_id, ring_id)) {
+                       return (cur_partition);
+               }
+       }
+
+       return (NULL);
+}
+
+int
+qnetd_algo_create_partitions(struct qnetd_client *client, partitions_list_t *partitions_list, const struct tlv_ring_id *ring_id)
+{
+       struct qnetd_client *other_client;
+       int num_partitions = 0;
+
+       TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) {
+               struct qnetd_algo_partition *partition;
+
+               if (other_client->last_ring_id.seq == 0){
+                       continue; /* not initialised yet */
+               }
+               partition = qnetd_algo_find_partition(partitions_list, &other_client->last_ring_id);
+               if (!partition) {
+                       partition = malloc(sizeof(struct qnetd_algo_partition));
+                       if (!partition) {
+                               return (-1);
+                       }
+                       partition->num_nodes = 0;
+                       partition->score = 0;
+                       memcpy(&partition->ring_id, &other_client->last_ring_id, sizeof(*ring_id));
+                       num_partitions++;
+                       TAILQ_INSERT_TAIL(partitions_list, partition, entries);
+               }
+               partition->num_nodes++;
+
+               /*
+                * Score is computer similar way as in the ffsplit algorithm
+                */
+               partition->score++;
+               if (other_client->last_heuristics == TLV_HEURISTICS_PASS) {
+                       partition->score++;
+               } else if (other_client->last_heuristics == TLV_HEURISTICS_FAIL) {
+                       partition->score--;
+               }
+
+       }
+
+       return (num_partitions);
+}
+
+
+void
+qnetd_algo_free_partitions(partitions_list_t *partitions_list)
+{
+       struct qnetd_algo_partition *cur_partition;
+       struct qnetd_algo_partition *partition_next;
+
+       cur_partition = TAILQ_FIRST(partitions_list);
+
+       while (cur_partition != NULL) {
+               partition_next = TAILQ_NEXT(cur_partition, entries);
+
+               free(cur_partition);
+
+               cur_partition = partition_next;
+       }
+
+       TAILQ_INIT(partitions_list);
+}
+
+void
+qnetd_algo_dump_partitions(partitions_list_t *partitions_list)
+{
+       struct qnetd_algo_partition *partition;
+
+       TAILQ_FOREACH(partition, partitions_list, entries) {
+               qnetd_log(LOG_DEBUG, "algo-util: partition (" UTILS_PRI_RING_ID ") (%p) has %d nodes",
+                         partition->ring_id.node_id, partition->ring_id.seq, partition, partition->num_nodes);
+       }
+}
diff --git a/qdevices/qnetd-algo-utils.h b/qdevices/qnetd-algo-utils.h
new file mode 100644 (file)
index 0000000..83f4101
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Christine Caulfield (ccaulfie@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_ALGO_UTILS_H_
+#define _QNETD_ALGO_UTILS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qnetd_algo_partition {
+       struct tlv_ring_id ring_id;
+       int num_nodes;
+       int score;
+       TAILQ_ENTRY(qnetd_algo_partition) entries;
+};
+
+typedef TAILQ_HEAD(, qnetd_algo_partition) partitions_list_t;
+
+extern int                              qnetd_algo_all_ring_ids_match(struct qnetd_client *client,
+    const struct tlv_ring_id *ring_id);
+
+extern struct qnetd_algo_partition     *qnetd_algo_find_partition(partitions_list_t *partitions,
+    const struct tlv_ring_id *ring_id);
+
+extern int                              qnetd_algo_create_partitions(struct qnetd_client *client,
+    partitions_list_t *partitions, const struct tlv_ring_id *ring_id);
+
+extern void                             qnetd_algo_free_partitions(partitions_list_t *partitions);
+
+extern void                             qnetd_algo_dump_partitions(partitions_list_t *partitions);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_ALGO_UTILS_H_ */
diff --git a/qdevices/qnetd-algorithm.c b/qdevices/qnetd-algorithm.c
new file mode 100644 (file)
index 0000000..e88d44d
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include "qnet-config.h"
+#include "qnetd-algorithm.h"
+#include "qnetd-algo-test.h"
+#include "qnetd-algo-ffsplit.h"
+#include "qnetd-algo-2nodelms.h"
+#include "qnetd-algo-lms.h"
+#include "qnetd-log.h"
+
+static struct qnetd_algorithm *qnetd_algorithm_array[QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE];
+
+enum tlv_reply_error_code
+qnetd_algorithm_client_init(struct qnetd_client *client)
+{
+       if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qnetd_algorithm_array[client->decision_algorithm] == NULL) {
+               qnetd_log(LOG_CRIT, "qnetd_algorithm_client_init unhandled decision algorithm");
+
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       return (qnetd_algorithm_array[client->decision_algorithm]->init(client));
+}
+
+enum tlv_reply_error_code
+qnetd_algorithm_config_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, int config_version_set, uint64_t config_version,
+    const struct node_list *nodes, int initial, enum tlv_vote *result_vote)
+{
+       if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qnetd_algorithm_array[client->decision_algorithm] == NULL) {
+               qnetd_log(LOG_CRIT, "qnetd_algorithm_config_node_list_received unhandled "
+                   "decision algorithm");
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       return (qnetd_algorithm_array[client->decision_algorithm]->config_node_list_received(
+               client, msg_seq_num,
+               config_version_set, config_version, nodes, initial, result_vote));
+}
+
+enum tlv_reply_error_code
+qnetd_algorithm_membership_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, const struct tlv_ring_id *ring_id,
+    const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote)
+{
+
+       if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qnetd_algorithm_array[client->decision_algorithm] == NULL) {
+               qnetd_log(LOG_CRIT, "qnetd_algorithm_membership_node_list_received unhandled "
+                   "decision algorithm");
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       return (qnetd_algorithm_array[client->decision_algorithm]->membership_node_list_received(
+               client, msg_seq_num,
+               ring_id, nodes, heuristics, result_vote));
+}
+
+enum tlv_reply_error_code
+qnetd_algorithm_quorum_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, enum tlv_quorate quorate,
+    const struct node_list *nodes, enum tlv_vote *result_vote)
+{
+
+       if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qnetd_algorithm_array[client->decision_algorithm] == NULL) {
+               qnetd_log(LOG_CRIT, "algorithm_quorum_node_list_received unhandled "
+                   "decision algorithm");
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       return (qnetd_algorithm_array[client->decision_algorithm]->quorum_node_list_received(
+               client, msg_seq_num, quorate, nodes, result_vote));
+}
+
+void
+qnetd_algorithm_client_disconnect(struct qnetd_client *client, int server_going_down)
+{
+
+       if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qnetd_algorithm_array[client->decision_algorithm] == NULL) {
+               qnetd_log(LOG_CRIT, "qnetd_algorithm_client_disconnect unhandled decision "
+                   "algorithm");
+               return;
+       }
+
+       qnetd_algorithm_array[client->decision_algorithm]->client_disconnect(client, server_going_down);
+}
+
+enum tlv_reply_error_code
+qnetd_algorithm_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_vote *result_vote)
+{
+
+       if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qnetd_algorithm_array[client->decision_algorithm] == NULL) {
+               qnetd_log(LOG_CRIT, "qnetd_algorithm_ask_for_vote_received unhandled "
+                   "decision algorithm");
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       return (qnetd_algorithm_array[client->decision_algorithm]->ask_for_vote_received(
+               client, msg_seq_num, result_vote));
+}
+
+enum tlv_reply_error_code
+qnetd_algorithm_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num)
+{
+
+       if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qnetd_algorithm_array[client->decision_algorithm] == NULL) {
+               qnetd_log(LOG_CRIT, "qnetd_algorithm_vote_info_reply_received unhandled decision algorithm");
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       return (qnetd_algorithm_array[client->decision_algorithm]->vote_info_reply_received(
+               client, msg_seq_num));
+
+}
+
+enum tlv_reply_error_code
+qnetd_algorithm_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics, enum tlv_vote *result_vote)
+{
+
+       if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qnetd_algorithm_array[client->decision_algorithm] == NULL) {
+               qnetd_log(LOG_CRIT, "qnetd_algorithm_ask_for_vote_received unhandled "
+                   "decision algorithm");
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       return (qnetd_algorithm_array[client->decision_algorithm]->heuristics_change_received(
+               client, msg_seq_num, heuristics, result_vote));
+}
+
+enum tlv_reply_error_code
+qnetd_algorithm_timer_callback(struct qnetd_client *client, int *reschedule_timer,
+    int *send_vote, enum tlv_vote *result_vote)
+{
+
+       if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE ||
+           qnetd_algorithm_array[client->decision_algorithm] == NULL) {
+               qnetd_log(LOG_CRIT, "qnetd_algorithm_timer_callback unhandled decision algorithm");
+               return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR);
+       }
+
+       return (qnetd_algorithm_array[client->decision_algorithm]->timer_callback(
+               client, reschedule_timer, send_vote, result_vote));
+}
+
+int
+qnetd_algorithm_register(enum tlv_decision_algorithm_type algorithm_number,
+    struct qnetd_algorithm *algorithm)
+{
+
+       if (algorithm_number >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE) {
+               qnetd_log(LOG_CRIT, "Failed to register unsupported decision algorithm %u",
+                   algorithm_number);
+               return (-1);
+       }
+
+       if (qnetd_algorithm_array[algorithm_number] != NULL) {
+               qnetd_log(LOG_CRIT, "Failed to register decision algorithm %u, "
+               "it's already registered.", algorithm_number);
+               return (-1);
+       }
+
+       qnetd_algorithm_array[algorithm_number] = algorithm;
+
+       return (0);
+}
+
+int
+qnetd_algorithm_register_all(void)
+{
+
+       if (qnetd_algo_test_register() != 0) {
+               qnetd_log(LOG_CRIT, "Failed to register decision algorithm 'test'");
+               return (-1);
+       }
+       if (qnetd_algo_ffsplit_register() != 0) {
+               qnetd_log(LOG_CRIT, "Failed to register decision algorithm 'ffsplit'");
+               return (-1);
+       }
+       if (qnetd_algo_2nodelms_register() != 0) {
+               qnetd_log(LOG_CRIT, "Failed to register decision algorithm '2nodelms'");
+               return (-1);
+       }
+       if (qnetd_algo_lms_register() != 0) {
+               qnetd_log(LOG_CRIT, "Failed to register decision algorithm 'lms'");
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qnetd-algorithm.h b/qdevices/qnetd-algorithm.h
new file mode 100644 (file)
index 0000000..a1f19bb
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_ALGORITHM_H_
+#define _QNETD_ALGORITHM_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include "tlv.h"
+#include "qnetd-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern enum tlv_reply_error_code       qnetd_algorithm_client_init(struct qnetd_client *client);
+
+extern enum tlv_reply_error_code       qnetd_algorithm_config_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set,
+    uint64_t config_version, const struct node_list *nodes, int initial,
+    enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algorithm_membership_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, const struct tlv_ring_id *ring_id,
+     const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algorithm_quorum_node_list_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_quorate quorate,
+    const struct node_list *nodes, enum tlv_vote *result_vote);
+
+extern void                            qnetd_algorithm_client_disconnect(
+    struct qnetd_client *client, int server_going_down);
+
+extern enum tlv_reply_error_code       qnetd_algorithm_ask_for_vote_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algorithm_vote_info_reply_received(
+    struct qnetd_client *client, uint32_t msg_seq_num);
+
+extern enum tlv_reply_error_code       qnetd_algorithm_heuristics_change_received(
+    struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_heuristics heuristics,
+    enum tlv_vote *result_vote);
+
+extern enum tlv_reply_error_code       qnetd_algorithm_timer_callback(
+    struct qnetd_client *client, int *reschedule_timer, int *send_vote, enum tlv_vote *result_vote);
+
+struct qnetd_algorithm {
+       enum tlv_reply_error_code (*init)(struct qnetd_client *client);
+
+       void (*client_disconnect)(struct qnetd_client *client, int server_going_down);
+
+       enum tlv_reply_error_code (*membership_node_list_received)(
+           struct qnetd_client *client, uint32_t msg_seq_num,
+           const struct tlv_ring_id *ring_id,
+           const struct node_list *nodes, enum tlv_heuristics, enum tlv_vote *result_vote);
+
+       enum tlv_reply_error_code (*quorum_node_list_received)(
+           struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_quorate quorate,
+           const struct node_list *nodes, enum tlv_vote *result_vote);
+
+       enum tlv_reply_error_code (*config_node_list_received)(
+           struct qnetd_client *client,
+           uint32_t msg_seq_num, int config_version_set, uint64_t config_version,
+           const struct node_list *nodes, int initial, enum tlv_vote *result_vote);
+
+       enum tlv_reply_error_code (*ask_for_vote_received)(
+           struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote);
+
+       enum tlv_reply_error_code (*vote_info_reply_received)(struct qnetd_client *client,
+           uint32_t msg_seq_num);
+
+       enum tlv_reply_error_code (*heuristics_change_received)(
+           struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_heuristics heuristics,
+           enum tlv_vote *result_vote);
+
+        enum tlv_reply_error_code (*timer_callback)(struct qnetd_client *client,
+           int *reschedule_timer, int *send_vote, enum tlv_vote *result_vote);
+};
+
+extern int                             qnetd_algorithm_register(
+       enum tlv_decision_algorithm_type algorithm_number, struct qnetd_algorithm *algorithm);
+
+extern int qnetd_algorithm_register_all(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_ALGORITHM_H_ */
diff --git a/qdevices/qnetd-client-algo-timer.c b/qdevices/qnetd-client-algo-timer.c
new file mode 100644 (file)
index 0000000..35336f0
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qnetd-log.h"
+#include "qnetd-client-algo-timer.h"
+#include "qnetd-client-send.h"
+#include "qnetd-algorithm.h"
+#include "timer-list.h"
+
+static int
+qnetd_client_algo_timer_callback(void *data1, void *data2)
+{
+       struct qnetd_client *client;
+       enum tlv_vote result_vote;
+       int send_vote;
+       int reschedule_timer;
+       enum tlv_reply_error_code reply_error_code;
+
+       client = (struct qnetd_client *)data1;
+
+       result_vote = TLV_VOTE_WAIT_FOR_REPLY;
+       send_vote = 0;
+       reschedule_timer = 0;
+
+       reply_error_code = qnetd_algorithm_timer_callback(client, &reschedule_timer,
+           &send_vote, &result_vote);
+
+       if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               qnetd_log(LOG_ERR, "Algorithm for client %s returned error code. "
+                   "Sending error reply.", client->addr_str);
+
+               if (qnetd_client_send_err(client, 0, 0, reply_error_code) != 0) {
+                       client->schedule_disconnect = 1;
+                       return (0);
+               }
+
+               return (0);
+       } else {
+               qnetd_log(LOG_DEBUG, "Algorithm for client %s decided to %s timer and %s vote "
+                   "with value %s", client->addr_str,
+                   (reschedule_timer ? "reschedule" : "not reschedule"),
+                   (send_vote ? "send" : "not send"),
+                   tlv_vote_to_str(result_vote));
+       }
+
+       if (send_vote) {
+               client->algo_timer_vote_info_msq_seq_number++;
+
+               if (qnetd_client_send_vote_info(client,
+                   client->algo_timer_vote_info_msq_seq_number, &client->last_ring_id,
+                   result_vote) != 0) {
+                       client->schedule_disconnect = 1;
+                       return (0);
+               }
+       }
+
+       if (reschedule_timer) {
+               /*
+                * Timer list makes sure to schedule callback again
+                */
+               return (-1);
+       }
+
+       client->algo_timer = NULL;
+       return (0);
+}
+
+int
+qnetd_client_algo_timer_is_scheduled(struct qnetd_client *client)
+{
+
+       return (client->algo_timer != NULL);
+}
+
+int
+qnetd_client_algo_timer_schedule_timeout(struct qnetd_client *client, uint32_t timeout)
+{
+
+       if (qnetd_client_algo_timer_is_scheduled(client)) {
+               if (qnetd_client_algo_timer_abort(client) != 0) {
+                       qnetd_log(LOG_ERR, "Can't abort algo timer");
+
+                       return (-1);
+               }
+       }
+
+       client->algo_timer = timer_list_add(client->main_timer_list, timeout,
+           qnetd_client_algo_timer_callback, (void *)client, NULL);
+       if (client->algo_timer == NULL) {
+               qnetd_log(LOG_ERR, "Can't schedule algo timer");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qnetd_client_algo_timer_schedule(struct qnetd_client *client)
+{
+
+       return (qnetd_client_algo_timer_schedule_timeout(client, client->heartbeat_interval / 4));
+}
+
+int
+qnetd_client_algo_timer_abort(struct qnetd_client *client)
+{
+
+       if (qnetd_client_algo_timer_is_scheduled(client)) {
+               timer_list_delete(client->main_timer_list, client->algo_timer);
+               client->algo_timer = NULL;
+       }
+
+       return (0);
+}
diff --git a/qdevices/qnetd-client-algo-timer.h b/qdevices/qnetd-client-algo-timer.h
new file mode 100644 (file)
index 0000000..59a6178
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLIENT_ALGO_TIMER_H_
+#define _QNETD_CLIENT_ALGO_TIMER_H_
+
+#include "qnetd-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qnetd_client_algo_timer_is_scheduled(struct qnetd_client *client);
+
+extern int     qnetd_client_algo_timer_schedule_timeout(struct qnetd_client *client,
+    uint32_t timeout);
+
+extern int     qnetd_client_algo_timer_schedule(struct qnetd_client *client);
+
+extern int     qnetd_client_algo_timer_abort(struct qnetd_client *client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLIENT_ALGO_TIMER_H_ */
diff --git a/qdevices/qnetd-client-list.c b/qdevices/qnetd-client-list.c
new file mode 100644 (file)
index 0000000..993bb71
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "qnetd-client-list.h"
+
+void
+qnetd_client_list_init(struct qnetd_client_list *client_list)
+{
+
+       TAILQ_INIT(client_list);
+}
+
+struct qnetd_client *
+qnetd_client_list_add(struct qnetd_client_list *client_list, PRFileDesc *sock, PRNetAddr *addr,
+    char *addr_str,
+    size_t max_receive_size, size_t max_send_buffers, size_t max_send_size,
+    struct timer_list *main_timer_list)
+{
+       struct qnetd_client *client;
+
+       client = (struct qnetd_client *)malloc(sizeof(*client));
+       if (client == NULL) {
+               return (NULL);
+       }
+
+       qnetd_client_init(client, sock, addr, addr_str, max_receive_size, max_send_buffers,
+           max_send_size, main_timer_list);
+
+       TAILQ_INSERT_TAIL(client_list, client, entries);
+
+       return (client);
+}
+
+void
+qnetd_client_list_free(struct qnetd_client_list *client_list)
+{
+       struct qnetd_client *client;
+       struct qnetd_client *client_next;
+
+       client = TAILQ_FIRST(client_list);
+
+       while (client != NULL) {
+               client_next = TAILQ_NEXT(client, entries);
+
+               qnetd_client_destroy(client);
+               free(client);
+
+               client = client_next;
+       }
+
+       TAILQ_INIT(client_list);
+}
+
+void
+qnetd_client_list_del(struct qnetd_client_list *client_list, struct qnetd_client *client)
+{
+
+       TAILQ_REMOVE(client_list, client, entries);
+
+       qnetd_client_destroy(client);
+       free(client);
+}
+
+size_t
+qnetd_client_list_no_clients(struct qnetd_client_list *client_list)
+{
+       size_t res;
+       struct qnetd_client *client;
+
+       res = 0;
+
+       TAILQ_FOREACH(client, client_list, entries) {
+               res++;
+       }
+
+       return (res);
+}
diff --git a/qdevices/qnetd-client-list.h b/qdevices/qnetd-client-list.h
new file mode 100644 (file)
index 0000000..d44014b
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLIENT_LIST_H_
+#define _QNETD_CLIENT_LIST_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include "qnetd-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+TAILQ_HEAD(qnetd_client_list, qnetd_client);
+
+extern void                     qnetd_client_list_init(struct qnetd_client_list *client_list);
+
+extern struct qnetd_client     *qnetd_client_list_add(struct qnetd_client_list *client_list,
+    PRFileDesc *sock, PRNetAddr *addr, char *addr_str, size_t max_receive_size,
+    size_t max_send_buffers, size_t max_send_size, struct timer_list *main_timer_list);
+
+extern void                     qnetd_client_list_free(struct qnetd_client_list *client_list);
+
+extern void                     qnetd_client_list_del(struct qnetd_client_list *client_list,
+    struct qnetd_client *client);
+
+extern size_t                   qnetd_client_list_no_clients(
+    struct qnetd_client_list *client_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLIENT_LIST_H_ */
diff --git a/qdevices/qnetd-client-msg-received.c b/qdevices/qnetd-client-msg-received.c
new file mode 100644 (file)
index 0000000..65711e1
--- /dev/null
@@ -0,0 +1,1253 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include "qnetd-algorithm.h"
+#include "qnetd-instance.h"
+#include "qnetd-log.h"
+#include "qnetd-log-debug.h"
+#include "qnetd-client-send.h"
+#include "msg.h"
+#include "nss-sock.h"
+
+#include "qnetd-client-msg-received.h"
+
+/*
+ *  0 - Success
+ * -1 - Disconnect client
+ * -2 - Error reply sent, but no need to disconnect client
+ */
+static int
+qnetd_client_msg_received_check_tls(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+       int check_certificate;
+       int tls_required;
+       CERTCertificate *peer_cert;
+       int case_processed;
+
+       check_certificate = 0;
+       tls_required = 0;
+
+       case_processed = 0;
+
+       switch (instance->tls_supported) {
+       case TLV_TLS_UNSUPPORTED:
+               case_processed = 1;
+               tls_required = 0;
+               check_certificate = 0;
+               break;
+       case TLV_TLS_SUPPORTED:
+               case_processed = 1;
+               tls_required = 0;
+
+               if (client->tls_started && instance->tls_client_cert_required &&
+                   !client->tls_peer_certificate_verified) {
+                       check_certificate = 1;
+               }
+               break;
+       case TLV_TLS_REQUIRED:
+               case_processed = 1;
+               tls_required = 1;
+
+               if (instance->tls_client_cert_required && !client->tls_peer_certificate_verified) {
+                       check_certificate = 1;
+               }
+               break;
+       /*
+        * Default is not defined intentionally. Compiler shows warning when new
+        * tls supported is added
+        */
+       }
+
+       if (!case_processed) {
+               qnetd_log(LOG_ERR, "Unhandled instance tls supported %u", instance->tls_supported);
+               exit(1);
+       }
+
+       if (tls_required && !client->tls_started) {
+               qnetd_log(LOG_ERR, "TLS is required but doesn't started yet. "
+                   "Sending back error message");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_TLS_REQUIRED) != 0) {
+                       return (-1);
+               }
+
+               return (-2);
+       }
+
+       if (check_certificate) {
+               peer_cert = SSL_PeerCertificate(client->socket);
+
+               if (peer_cert == NULL) {
+                       qnetd_log(LOG_ERR, "Client doesn't sent valid certificate. "
+                           "Disconnecting client");
+
+                       return (-1);
+               }
+
+               if (CERT_VerifyCertName(peer_cert, client->cluster_name) != SECSuccess) {
+                       qnetd_log(LOG_ERR, "Client doesn't sent certificate with valid CN. "
+                           "Disconnecting client");
+
+                       CERT_DestroyCertificate(peer_cert);
+
+                       return (-1);
+               }
+
+               CERT_DestroyCertificate(peer_cert);
+
+               client->tls_peer_certificate_verified = 1;
+       }
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_preinit(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+       struct send_buffer_list_entry *send_buffer;
+
+       if (msg->cluster_name == NULL) {
+               qnetd_log(LOG_ERR, "Received preinit message without cluster name. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       client->cluster_name = malloc(msg->cluster_name_len + 1);
+       if (client->cluster_name == NULL) {
+               qnetd_log(LOG_ERR, "Can't allocate cluster name. Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_INTERNAL_ERROR) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+       memset(client->cluster_name, 0, msg->cluster_name_len + 1);
+       memcpy(client->cluster_name, msg->cluster_name, msg->cluster_name_len);
+
+       client->cluster_name_len = msg->cluster_name_len;
+       client->preinit_received = 1;
+
+       send_buffer = send_buffer_list_get_new(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc preinit reply msg from list. "
+                   "Disconnecting client connection.");
+
+               return (-1);
+       }
+
+       if (msg_create_preinit_reply(&send_buffer->buffer, msg->seq_number_set, msg->seq_number,
+           instance->tls_supported, instance->tls_client_cert_required) == 0) {
+               qnetd_log(LOG_ERR, "Can't alloc preinit reply msg. "
+                   "Disconnecting client connection.");
+
+               send_buffer_list_discard_new(&client->send_buffer_list, send_buffer);
+
+               return (-1);
+       };
+
+       send_buffer_list_put(&client->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_unexpected_msg(struct qnetd_client *client,
+    const struct msg_decoded *msg, const char *msg_str)
+{
+
+       qnetd_log(LOG_ERR, "Received %s message. Sending back error message", msg_str);
+
+       if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+           TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE) != 0) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_preinit_reply(struct qnetd_instance *instance,
+    struct qnetd_client *client, const struct msg_decoded *msg)
+{
+
+       return (qnetd_client_msg_received_unexpected_msg(client, msg, "preinit reply"));
+}
+
+static int
+qnetd_client_msg_received_starttls(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+       PRFileDesc *new_pr_fd;
+
+       if (!client->preinit_received) {
+               qnetd_log(LOG_ERR, "Received starttls before preinit message. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_PREINIT_REQUIRED) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       if ((new_pr_fd = nss_sock_start_ssl_as_server(client->socket, instance->server.cert,
+           instance->server.private_key, instance->tls_client_cert_required, 0, NULL)) == NULL) {
+               qnetd_log_nss(LOG_ERR, "Can't start TLS. Disconnecting client.");
+
+               return (-1);
+       }
+
+       client->tls_started = 1;
+       client->tls_peer_certificate_verified = 0;
+       client->socket = new_pr_fd;
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_server_error(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+
+       return (qnetd_client_msg_received_unexpected_msg(client, msg, "server error"));
+}
+
+/*
+ * Checks if new client send information are valid. It means:
+ * - in cluster is no duplicate node with same nodeid
+ * - it has same tie_breaker as other nodes in cluster
+ * - it has same algorithm as other nodes in cluster
+ */
+static enum tlv_reply_error_code
+qnetd_client_msg_received_init_check_new_client(struct qnetd_instance *instance,
+    struct qnetd_client *new_client)
+{
+       struct qnetd_cluster *cluster;
+       struct qnetd_client *client;
+
+       cluster = qnetd_cluster_list_find_by_name(&instance->clusters, new_client->cluster_name,
+           new_client->cluster_name_len);
+
+       if (cluster == NULL) {
+               return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+       }
+
+       TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) {
+               if (!tlv_tie_breaker_eq(&new_client->tie_breaker, &client->tie_breaker)) {
+                       qnetd_log(LOG_ERR, "Received init message contains tie-breaker which "
+                           "differs from rest of cluster. Sending error reply");
+
+                       return (TLV_REPLY_ERROR_CODE_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES);
+               }
+
+               if (new_client->decision_algorithm != client->decision_algorithm) {
+                       qnetd_log(LOG_ERR, "Received init message contains algorithm which "
+                           "differs from rest of cluster. Sending error reply");
+
+                       return (TLV_REPLY_ERROR_CODE_ALGORITHM_DIFFERS_FROM_OTHER_NODES);
+               }
+
+               if (new_client->node_id == client->node_id) {
+                       qnetd_log(LOG_ERR, "Received init message contains node id which is "
+                           "duplicate of other node in cluster. Sending error reply");
+
+                       return (TLV_REPLY_ERROR_CODE_DUPLICATE_NODE_ID);
+               }
+       }
+
+       return (TLV_REPLY_ERROR_CODE_NO_ERROR);
+}
+
+static int
+qnetd_client_msg_received_init(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+       int res;
+       size_t zi;
+       enum msg_type *supported_msgs;
+       size_t no_supported_msgs;
+       enum tlv_opt_type *supported_opts;
+       size_t no_supported_opts;
+       struct send_buffer_list_entry *send_buffer;
+       enum tlv_reply_error_code reply_error_code;
+       struct qnetd_cluster *cluster;
+
+       supported_msgs = NULL;
+       supported_opts = NULL;
+       no_supported_msgs = 0;
+       no_supported_opts = 0;
+
+       reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR;
+
+       if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) {
+               return (res == -1 ? -1 : 0);
+       }
+
+       if (!client->preinit_received) {
+               qnetd_log(LOG_ERR, "Received init before preinit message. Sending error reply.");
+
+               reply_error_code = TLV_REPLY_ERROR_CODE_PREINIT_REQUIRED;
+       }
+
+       if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->node_id_set) {
+               qnetd_log(LOG_ERR, "Received init message without node id set. "
+                   "Sending error reply.");
+
+               reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION;
+       } else {
+               client->node_id_set = 1;
+               client->node_id = msg->node_id;
+       }
+
+       if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->ring_id_set) {
+               qnetd_log(LOG_ERR, "Received init message without ring id set. "
+                   "Sending error reply.");
+
+               reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION;
+       } else {
+               memcpy(&client->last_ring_id, &msg->ring_id, sizeof(struct tlv_ring_id));
+       }
+
+       if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->heartbeat_interval_set) {
+               qnetd_log(LOG_ERR, "Received init message without heartbeat interval set. "
+                   "Sending error reply.");
+
+               reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION;
+       } else {
+               if (msg->heartbeat_interval < instance->advanced_settings->heartbeat_interval_min ||
+                   msg->heartbeat_interval > instance->advanced_settings->heartbeat_interval_max) {
+                       qnetd_log(LOG_ERR, "Client requested invalid heartbeat interval %u. "
+                           "Sending error reply.", msg->heartbeat_interval);
+
+                       reply_error_code = TLV_REPLY_ERROR_CODE_INVALID_HEARTBEAT_INTERVAL;
+               } else {
+                       client->heartbeat_interval = msg->heartbeat_interval;
+               }
+       }
+
+       if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->tie_breaker_set) {
+               qnetd_log(LOG_ERR, "Received init message without tie-breaker set. "
+                   "Sending error reply.");
+
+               reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION;
+       } else {
+               memcpy(&client->tie_breaker, &msg->tie_breaker, sizeof(msg->tie_breaker));
+       }
+
+       if (msg->supported_messages != NULL) {
+               /*
+                * Client sent supported messages. For now this is ignored but in the future
+                * this may be used to ensure backward compatibility.
+                */
+/*
+               for (i = 0; i < msg->no_supported_messages; i++) {
+                       qnetd_log(LOG_DEBUG, "Client supports %u message",
+                           (int)msg->supported_messages[i]);
+               }
+*/
+
+               /*
+                * Sent back supported messages
+                */
+               msg_get_supported_messages(&supported_msgs, &no_supported_msgs);
+       }
+
+       if (msg->supported_options != NULL) {
+               /*
+                * Client sent supported options. For now this is ignored but in the future
+                * this may be used to ensure backward compatibility.
+                */
+/*
+               for (i = 0; i < msg->no_supported_options; i++) {
+                       qnetd_log(LOG_DEBUG, "Client supports %u option",
+                           (int)msg->supported_messages[i]);
+               }
+*/
+
+               /*
+                * Send back supported options
+                */
+               tlv_get_supported_options(&supported_opts, &no_supported_opts);
+       }
+
+       if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->decision_algorithm_set) {
+               qnetd_log(LOG_ERR, "Received init message without decision algorithm. "
+                   "Sending error reply.");
+
+               reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION;
+       } else {
+               /*
+                * Check if decision algorithm requested by client is supported
+                */
+               res = 0;
+
+               for (zi = 0; zi < QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE && !res; zi++) {
+                       if (qnetd_static_supported_decision_algorithms[zi] ==
+                           msg->decision_algorithm) {
+                               res = 1;
+                       }
+               }
+
+               if (!res) {
+                       qnetd_log(LOG_ERR, "Client requested unsupported decision algorithm %u. "
+                           "Sending error reply.", msg->decision_algorithm);
+
+                       reply_error_code = TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM;
+               }
+
+               client->decision_algorithm = msg->decision_algorithm;
+       }
+
+       if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               reply_error_code = qnetd_client_msg_received_init_check_new_client(instance,
+                   client);
+       }
+
+       if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               cluster = qnetd_cluster_list_add_client(&instance->clusters, client);
+               if (cluster == NULL) {
+                       qnetd_log(LOG_ERR, "Can't add client to cluster list. "
+                           "Sending error reply.");
+
+                       reply_error_code = TLV_REPLY_ERROR_CODE_INTERNAL_ERROR;
+               } else {
+                       client->cluster = cluster;
+                       client->cluster_list = &instance->clusters;
+               }
+       }
+
+       if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               qnetd_log_debug_new_client_connected(client);
+
+               reply_error_code = qnetd_algorithm_client_init(client);
+       }
+
+       if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               /*
+                * Correct init received
+                */
+               client->init_received = 1;
+       } else {
+               qnetd_log(LOG_ERR, "Algorithm returned error code. Sending error reply.");
+       }
+
+       send_buffer = send_buffer_list_get_new(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc init reply msg from list. "
+                   "Disconnecting client connection.");
+
+               return (-1);
+       }
+
+       if (msg_create_init_reply(&send_buffer->buffer, msg->seq_number_set, msg->seq_number,
+           reply_error_code,
+           supported_msgs, no_supported_msgs, supported_opts, no_supported_opts,
+           instance->advanced_settings->max_client_receive_size,
+           instance->advanced_settings->max_client_send_size,
+           qnetd_static_supported_decision_algorithms,
+           QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE) == 0) {
+               qnetd_log(LOG_ERR, "Can't alloc init reply msg. Disconnecting client connection.");
+
+               send_buffer_list_discard_new(&client->send_buffer_list, send_buffer);
+
+               return (-1);
+       }
+
+       send_buffer_list_put(&client->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_init_reply(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+
+       return (qnetd_client_msg_received_unexpected_msg(client, msg, "init reply"));
+}
+
+static int
+qnetd_client_msg_received_set_option_reply(struct qnetd_instance *instance,
+    struct qnetd_client *client, const struct msg_decoded *msg)
+{
+
+       return (qnetd_client_msg_received_unexpected_msg(client, msg, "set option reply"));
+}
+
+static int
+qnetd_client_msg_received_set_option(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+       int res;
+       struct send_buffer_list_entry *send_buffer;
+
+       if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) {
+               return (res == -1 ? -1 : 0);
+       }
+
+       if (!client->init_received) {
+               qnetd_log(LOG_ERR, "Received set option message before init message. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       if (msg->heartbeat_interval_set) {
+               /*
+                * Check if heartbeat interval is valid
+                */
+               if (msg->heartbeat_interval < instance->advanced_settings->heartbeat_interval_min ||
+                   msg->heartbeat_interval > instance->advanced_settings->heartbeat_interval_max) {
+                       qnetd_log(LOG_ERR, "Client requested invalid heartbeat interval %u. "
+                           "Sending error reply.", msg->heartbeat_interval);
+
+                       if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                           TLV_REPLY_ERROR_CODE_INVALID_HEARTBEAT_INTERVAL) != 0) {
+                               return (-1);
+                       }
+
+                       return (0);
+               }
+
+               client->heartbeat_interval = msg->heartbeat_interval;
+       }
+
+       send_buffer = send_buffer_list_get_new(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc set option reply msg from list. "
+                   "Disconnecting client connection.");
+
+               return (-1);
+       }
+
+       if (msg_create_set_option_reply(&send_buffer->buffer, msg->seq_number_set, msg->seq_number,
+           client->heartbeat_interval) == 0) {
+               qnetd_log(LOG_ERR, "Can't alloc set option reply msg. "
+                   "Disconnecting client connection.");
+
+               send_buffer_list_discard_new(&client->send_buffer_list, send_buffer);
+
+               return (-1);
+       }
+
+       send_buffer_list_put(&client->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_echo_reply(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+
+       return (qnetd_client_msg_received_unexpected_msg(client, msg, "echo reply"));
+}
+
+static int
+qnetd_client_msg_received_echo_request(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg, const struct dynar *msg_orig)
+{
+       int res;
+       struct send_buffer_list_entry *send_buffer;
+
+       if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) {
+               return (res == -1 ? -1 : 0);
+       }
+
+       if (!client->init_received) {
+               qnetd_log(LOG_ERR, "Received echo request before init message. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       send_buffer = send_buffer_list_get_new(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc echo reply msg from list. "
+                   "Disconnecting client connection.");
+
+               return (-1);
+       }
+
+       if (msg_create_echo_reply(&send_buffer->buffer, msg_orig) == 0) {
+               qnetd_log(LOG_ERR, "Can't alloc echo reply msg. Disconnecting client connection.");
+
+               send_buffer_list_discard_new(&client->send_buffer_list, send_buffer);
+
+               return (-1);
+       }
+
+       send_buffer_list_put(&client->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_node_list(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+       int res;
+       struct send_buffer_list_entry *send_buffer;
+       enum tlv_reply_error_code reply_error_code;
+       enum tlv_vote result_vote;
+       int case_processed;
+
+       reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR;
+
+       if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) {
+               return (res == -1 ? -1 : 0);
+       }
+
+       if (!client->init_received) {
+               qnetd_log(LOG_ERR, "Received node list message before init message. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       if (!msg->node_list_type_set) {
+               qnetd_log(LOG_ERR, "Received node list message without node list type set. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       if (!msg->seq_number_set) {
+               qnetd_log(LOG_ERR, "Received node list message without seq number set. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       result_vote = TLV_VOTE_NO_CHANGE;
+
+       case_processed = 0;
+       switch (msg->node_list_type) {
+       case TLV_NODE_LIST_TYPE_INITIAL_CONFIG:
+       case TLV_NODE_LIST_TYPE_CHANGED_CONFIG:
+               case_processed = 1;
+               qnetd_log_debug_config_node_list_received(client, msg->seq_number,
+                   msg->config_version_set, msg->config_version, &msg->nodes,
+                   (msg->node_list_type == TLV_NODE_LIST_TYPE_INITIAL_CONFIG));
+
+               reply_error_code = qnetd_algorithm_config_node_list_received(client,
+                   msg->seq_number, msg->config_version_set, msg->config_version,
+                   &msg->nodes,
+                   (msg->node_list_type == TLV_NODE_LIST_TYPE_INITIAL_CONFIG),
+                   &result_vote);
+               break;
+       case TLV_NODE_LIST_TYPE_MEMBERSHIP:
+               case_processed = 1;
+               if (!msg->ring_id_set) {
+                       qnetd_log(LOG_ERR, "Received node list message without ring id number set. "
+                           "Sending error reply.");
+
+                       if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                           TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+                               return (-1);
+                       }
+
+                       return (0);
+               }
+
+               qnetd_log_debug_membership_node_list_received(client, msg->seq_number, &msg->ring_id,
+                   msg->heuristics, &msg->nodes);
+
+               reply_error_code = qnetd_algorithm_membership_node_list_received(client,
+                   msg->seq_number, &msg->ring_id, &msg->nodes, msg->heuristics, &result_vote);
+               break;
+       case TLV_NODE_LIST_TYPE_QUORUM:
+               case_processed = 1;
+               if (!msg->quorate_set) {
+                       qnetd_log(LOG_ERR, "Received quorum list message without quorate set. "
+                           "Sending error reply.");
+
+                       if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                           TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+                               return (-1);
+                       }
+
+                       return (0);
+               }
+
+               qnetd_log_debug_quorum_node_list_received(client, msg->seq_number,msg->quorate,
+                   &msg->nodes);
+
+               reply_error_code = qnetd_algorithm_quorum_node_list_received(client,
+                   msg->seq_number,msg->quorate, &msg->nodes, &result_vote);
+               break;
+       /*
+        * Default is not defined intentionally. Compiler shows warning when new
+        * node list type is added
+        */
+       }
+
+       if (!case_processed) {
+               qnetd_log(LOG_ERR, "qnetd_client_msg_received_node_list fatal error. "
+                   "Unhandled node_list_type");
+               exit(1);
+       }
+
+       if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               qnetd_log(LOG_ERR, "Algorithm returned error code. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   reply_error_code) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       } else {
+               qnetd_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote));
+       }
+
+       /*
+        * Store node list for future use
+        */
+       case_processed = 0;
+       switch (msg->node_list_type) {
+       case TLV_NODE_LIST_TYPE_INITIAL_CONFIG:
+       case TLV_NODE_LIST_TYPE_CHANGED_CONFIG:
+               case_processed = 1;
+               node_list_free(&client->configuration_node_list);
+               if (node_list_clone(&client->configuration_node_list, &msg->nodes) == -1) {
+                       qnetd_log(LOG_ERR, "Can't alloc config node list clone. "
+                           "Disconnecting client connection.");
+
+                       return (-1);
+               }
+               client->config_version_set = msg->config_version_set;
+               client->config_version = msg->config_version;
+
+               break;
+       case TLV_NODE_LIST_TYPE_MEMBERSHIP:
+               case_processed = 1;
+               node_list_free(&client->last_membership_node_list);
+               if (node_list_clone(&client->last_membership_node_list, &msg->nodes) == -1) {
+                       qnetd_log(LOG_ERR, "Can't alloc membership node list clone. "
+                           "Disconnecting client connection.");
+
+                       return (-1);
+               }
+               memcpy(&client->last_ring_id, &msg->ring_id, sizeof(struct tlv_ring_id));
+               client->last_membership_heuristics = msg->heuristics;
+               client->last_heuristics = msg->heuristics;
+               break;
+       case TLV_NODE_LIST_TYPE_QUORUM:
+               case_processed = 1;
+               node_list_free(&client->last_quorum_node_list);
+               if (node_list_clone(&client->last_quorum_node_list, &msg->nodes) == -1) {
+                       qnetd_log(LOG_ERR, "Can't alloc quorum node list clone. "
+                           "Disconnecting client connection.");
+
+                       return (-1);
+               }
+               break;
+       /*
+        * Default is not defined intentionally. Compiler shows warning when new
+        * node list type is added
+        */
+       }
+
+       if (!case_processed) {
+               qnetd_log(LOG_ERR, "qnetd_client_msg_received_node_list fatal error. "
+                   "Unhandled node_list_type");
+               exit(1);
+       }
+
+       /*
+        * Store result vote
+        */
+       client->last_sent_vote = result_vote;
+       if (result_vote == TLV_VOTE_ACK || result_vote == TLV_VOTE_NACK) {
+               client->last_sent_ack_nack_vote = result_vote;
+       }
+
+       send_buffer = send_buffer_list_get_new(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc node list reply msg from list. "
+                   "Disconnecting client connection.");
+
+               return (-1);
+       }
+
+       if (msg_create_node_list_reply(&send_buffer->buffer, msg->seq_number, msg->node_list_type,
+           &client->last_ring_id, result_vote) == 0) {
+               qnetd_log(LOG_ERR, "Can't alloc node list reply msg. "
+                   "Disconnecting client connection.");
+
+               send_buffer_list_discard_new(&client->send_buffer_list, send_buffer);
+
+               return (-1);
+       }
+
+       send_buffer_list_put(&client->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_node_list_reply(struct qnetd_instance *instance,
+    struct qnetd_client *client, const struct msg_decoded *msg)
+{
+
+       return (qnetd_client_msg_received_unexpected_msg(client, msg, "node list reply"));
+}
+
+static int
+qnetd_client_msg_received_ask_for_vote(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+       int res;
+       struct send_buffer_list_entry *send_buffer;
+       enum tlv_reply_error_code reply_error_code;
+       enum tlv_vote result_vote;
+
+       reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR;
+
+       if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) {
+               return (res == -1 ? -1 : 0);
+       }
+
+       if (!client->init_received) {
+               qnetd_log(LOG_ERR, "Received ask for vote message before init message. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       if (!msg->seq_number_set) {
+               qnetd_log(LOG_ERR, "Received ask for vote message without seq number set. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       qnetd_log_debug_ask_for_vote_received(client, msg->seq_number);
+
+       reply_error_code = qnetd_algorithm_ask_for_vote_received(client, msg->seq_number,
+           &result_vote);
+
+       if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               qnetd_log(LOG_ERR, "Algorithm returned error code. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   reply_error_code) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       } else {
+               qnetd_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote));
+       }
+
+       /*
+        * Store result vote
+        */
+       client->last_sent_vote = result_vote;
+       if (result_vote == TLV_VOTE_ACK || result_vote == TLV_VOTE_NACK) {
+               client->last_sent_ack_nack_vote = result_vote;
+       }
+
+       send_buffer = send_buffer_list_get_new(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc ask for vote reply msg from list. "
+                   "Disconnecting client connection.");
+
+               return (-1);
+       }
+
+       if (msg_create_ask_for_vote_reply(&send_buffer->buffer, msg->seq_number,
+           &client->last_ring_id, result_vote) == 0) {
+               qnetd_log(LOG_ERR, "Can't alloc ask for vote reply msg. "
+                   "Disconnecting client connection.");
+
+               send_buffer_list_discard_new(&client->send_buffer_list, send_buffer);
+
+               return (-1);
+       }
+
+       send_buffer_list_put(&client->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_ask_for_vote_reply(struct qnetd_instance *instance,
+    struct qnetd_client *client, const struct msg_decoded *msg)
+{
+
+       return (qnetd_client_msg_received_unexpected_msg(client, msg, "ask for vote reply"));
+}
+
+static int
+qnetd_client_msg_received_vote_info(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+
+       return (qnetd_client_msg_received_unexpected_msg(client, msg, "vote info"));
+}
+
+static int
+qnetd_client_msg_received_vote_info_reply(struct qnetd_instance *instance,
+    struct qnetd_client *client, const struct msg_decoded *msg)
+{
+       int res;
+       enum tlv_reply_error_code reply_error_code;
+
+       reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR;
+
+       if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) {
+               return (res == -1 ? -1 : 0);
+       }
+
+       if (!client->init_received) {
+               qnetd_log(LOG_ERR, "Received vote info reply before init message. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       if (!msg->seq_number_set) {
+               qnetd_log(LOG_ERR, "Received vote info reply message without seq number set. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       qnetd_log_debug_vote_info_reply_received(client, msg->seq_number);
+
+       reply_error_code = qnetd_algorithm_vote_info_reply_received(client, msg->seq_number);
+
+       if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               qnetd_log(LOG_ERR, "Algorithm returned error code. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   reply_error_code) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_heuristics_change(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+       int res;
+       struct send_buffer_list_entry *send_buffer;
+       enum tlv_reply_error_code reply_error_code;
+       enum tlv_vote result_vote;
+
+       reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR;
+
+       if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) {
+               return (res == -1 ? -1 : 0);
+       }
+
+       if (!client->init_received) {
+               qnetd_log(LOG_ERR, "Received heuristics change message before init message. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       if (!msg->seq_number_set || msg->heuristics == TLV_HEURISTICS_UNDEFINED) {
+               qnetd_log(LOG_ERR, "Received heuristics change message without seq number set or "
+                   "with undefined heuristics. Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       qnetd_log_debug_heuristics_change_received(client, msg->seq_number, msg->heuristics);
+
+       reply_error_code = qnetd_algorithm_heuristics_change_received(client, msg->seq_number,
+           msg->heuristics, &result_vote);
+
+       if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) {
+               qnetd_log(LOG_ERR, "Algorithm returned error code. "
+                   "Sending error reply.");
+
+               if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+                   reply_error_code) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       } else {
+               qnetd_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote));
+       }
+
+       /*
+        * Store result vote and heuristics result
+        */
+       client->last_sent_vote = result_vote;
+       if (result_vote == TLV_VOTE_ACK || result_vote == TLV_VOTE_NACK) {
+               client->last_sent_ack_nack_vote = result_vote;
+       }
+       client->last_regular_heuristics = msg->heuristics;
+       client->last_heuristics = msg->heuristics;
+
+       send_buffer = send_buffer_list_get_new(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc heuristics change reply msg from list. "
+                   "Disconnecting client connection.");
+
+               return (-1);
+       }
+
+       if (msg_create_heuristics_change_reply(&send_buffer->buffer, msg->seq_number,
+           &client->last_ring_id, msg->heuristics, result_vote) == 0) {
+               qnetd_log(LOG_ERR, "Can't alloc heuristics change reply msg. "
+                   "Disconnecting client connection.");
+
+               send_buffer_list_discard_new(&client->send_buffer_list, send_buffer);
+
+               return (-1);
+       }
+
+       send_buffer_list_put(&client->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+static int
+qnetd_client_msg_received_heuristics_change_reply(struct qnetd_instance *instance, struct qnetd_client *client,
+    const struct msg_decoded *msg)
+{
+
+       return (qnetd_client_msg_received_unexpected_msg(client, msg, "heuristics change reply"));
+}
+
+int
+qnetd_client_msg_received(struct qnetd_instance *instance, struct qnetd_client *client)
+{
+       struct msg_decoded msg;
+       int res;
+       int ret_val;
+       int msg_processed;
+
+       client->dpd_msg_received_since_last_check = 1;
+
+       msg_decoded_init(&msg);
+
+       res = msg_decode(&client->receive_buffer, &msg);
+       if (res != 0) {
+               /*
+                * Error occurred. Send server error.
+                */
+               qnetd_log_msg_decode_error(res);
+               qnetd_log(LOG_INFO, "Sending back error message");
+
+               if (qnetd_client_send_err(client, msg.seq_number_set, msg.seq_number,
+                   TLV_REPLY_ERROR_CODE_ERROR_DECODING_MSG) != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       ret_val = 0;
+
+       msg_processed = 0;
+       switch (msg.type) {
+       case MSG_TYPE_PREINIT:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_preinit(instance, client, &msg);
+               break;
+       case MSG_TYPE_PREINIT_REPLY:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_preinit_reply(instance, client, &msg);
+               break;
+       case MSG_TYPE_STARTTLS:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_starttls(instance, client, &msg);
+               break;
+       case MSG_TYPE_INIT:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_init(instance, client, &msg);
+               break;
+       case MSG_TYPE_INIT_REPLY:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_init_reply(instance, client, &msg);
+               break;
+       case MSG_TYPE_SERVER_ERROR:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_server_error(instance, client, &msg);
+               break;
+       case MSG_TYPE_SET_OPTION:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_set_option(instance, client, &msg);
+               break;
+       case MSG_TYPE_SET_OPTION_REPLY:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_set_option_reply(instance, client, &msg);
+               break;
+       case MSG_TYPE_ECHO_REQUEST:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_echo_request(instance, client, &msg,
+                   &client->receive_buffer);
+               break;
+       case MSG_TYPE_ECHO_REPLY:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_echo_reply(instance, client, &msg);
+               break;
+       case MSG_TYPE_NODE_LIST:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_node_list(instance, client, &msg);
+               break;
+       case MSG_TYPE_NODE_LIST_REPLY:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_node_list_reply(instance, client, &msg);
+               break;
+       case MSG_TYPE_ASK_FOR_VOTE:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_ask_for_vote(instance, client, &msg);
+               break;
+       case MSG_TYPE_ASK_FOR_VOTE_REPLY:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_ask_for_vote_reply(instance, client, &msg);
+               break;
+       case MSG_TYPE_VOTE_INFO:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_vote_info(instance, client, &msg);
+               break;
+       case MSG_TYPE_VOTE_INFO_REPLY:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_vote_info_reply(instance, client, &msg);
+               break;
+       case MSG_TYPE_HEURISTICS_CHANGE:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_heuristics_change(instance, client, &msg);
+               break;
+       case MSG_TYPE_HEURISTICS_CHANGE_REPLY:
+               msg_processed = 1;
+               ret_val = qnetd_client_msg_received_heuristics_change_reply(instance, client,
+                   &msg);
+               break;
+       /*
+        * Default is not defined intentionally. Compiler shows warning when new
+        * msg type is added.
+        */
+       }
+
+       if (!msg_processed) {
+               qnetd_log(LOG_ERR, "Unsupported message %u received from client. "
+                   "Sending back error message", msg.type);
+
+               if (qnetd_client_send_err(client, msg.seq_number_set, msg.seq_number,
+                   TLV_REPLY_ERROR_CODE_UNSUPPORTED_MESSAGE) != 0) {
+                       ret_val = -1;
+               }
+       }
+
+       msg_decoded_destroy(&msg);
+
+       return (ret_val);
+}
diff --git a/qdevices/qnetd-client-msg-received.h b/qdevices/qnetd-client-msg-received.h
new file mode 100644 (file)
index 0000000..072a77a
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLIENT_MSG_RECEIVED_H_
+#define _QNETD_CLIENT_MSG_RECEIVED_H_
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qnetd_client_msg_received(struct qnetd_instance *instance,
+    struct qnetd_client *client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLIENT_MSG_RECEIVED_H_ */
diff --git a/qdevices/qnetd-client-net.c b/qdevices/qnetd-client-net.c
new file mode 100644 (file)
index 0000000..93ee50e
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include "msgio.h"
+#include "msg.h"
+#include "nss-sock.h"
+#include "qnetd-log.h"
+#include "qnetd-client-net.h"
+#include "qnetd-client-send.h"
+#include "qnetd-client-msg-received.h"
+
+#define CLIENT_ADDR_STR_LEN_COLON_PORT (1 + 5 + 1)
+#define CLIENT_ADDR_STR_LEN            (INET6_ADDRSTRLEN + CLIENT_ADDR_STR_LEN_COLON_PORT)
+
+static int
+qnetd_client_net_write_finished(struct qnetd_instance *instance, struct qnetd_client *client)
+{
+
+       /*
+        * Callback is currently unused
+        */
+
+       return (0);
+}
+
+int
+qnetd_client_net_write(struct qnetd_instance *instance, struct qnetd_client *client)
+{
+       int res;
+       struct send_buffer_list_entry *send_buffer;
+
+       send_buffer = send_buffer_list_get_active(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log_nss(LOG_CRIT, "send_buffer_list_get_active returned NULL");
+
+               return (-1);
+       }
+
+       res = msgio_write(client->socket, &send_buffer->buffer,
+           &send_buffer->msg_already_sent_bytes);
+
+       if (res == 1) {
+               send_buffer_list_delete(&client->send_buffer_list, send_buffer);
+
+               if (qnetd_client_net_write_finished(instance, client) == -1) {
+                       return (-1);
+               }
+       }
+
+       if (res == -1) {
+               qnetd_log_nss(LOG_CRIT, "PR_Send returned 0");
+
+               return (-1);
+       }
+
+       if (res == -2) {
+               qnetd_log_nss(LOG_ERR, "Unhandled error when sending message to client");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+
+/*
+ * -1 means end of connection (EOF) or some other unhandled error. 0 = success
+ */
+int
+qnetd_client_net_read(struct qnetd_instance *instance, struct qnetd_client *client)
+{
+       int res;
+       int ret_val;
+       int orig_skipping_msg;
+
+       orig_skipping_msg = client->skipping_msg;
+
+       res = msgio_read(client->socket, &client->receive_buffer,
+           &client->msg_already_received_bytes, &client->skipping_msg);
+
+       if (!orig_skipping_msg && client->skipping_msg) {
+               qnetd_log(LOG_DEBUG, "msgio_read set skipping_msg");
+       }
+
+       ret_val = 0;
+
+       switch (res) {
+       case 0:
+               /*
+                * Partial read
+                */
+               break;
+       case -1:
+               qnetd_log(LOG_DEBUG, "Client closed connection");
+               ret_val = -1;
+               break;
+       case -2:
+               qnetd_log_nss(LOG_ERR, "Unhandled error when reading from client. "
+                   "Disconnecting client");
+               ret_val = -1;
+               break;
+       case -3:
+               qnetd_log(LOG_ERR, "Can't store message header from client. Disconnecting client");
+               ret_val = -1;
+               break;
+       case -4:
+               qnetd_log(LOG_ERR, "Can't store message from client. Skipping message");
+               client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_ERROR_DECODING_MSG;
+               break;
+       case -5:
+               qnetd_log(LOG_WARNING, "Client sent unsupported msg type %u. Skipping message",
+                           msg_get_type(&client->receive_buffer));
+               client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_UNSUPPORTED_MESSAGE;
+               break;
+       case -6:
+               qnetd_log(LOG_WARNING,
+                   "Client wants to send too long message %u bytes. Skipping message",
+                   msg_get_len(&client->receive_buffer));
+               client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_MESSAGE_TOO_LONG;
+               break;
+       case 1:
+               /*
+                * Full message received / skipped
+                */
+               if (!client->skipping_msg) {
+                       if (qnetd_client_msg_received(instance, client) == -1) {
+                               ret_val = -1;
+                       }
+               } else {
+                       if (qnetd_client_send_err(client, 0, 0, client->skipping_msg_reason) != 0) {
+                               ret_val = -1;
+                       }
+               }
+
+               client->skipping_msg = 0;
+               client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_NO_ERROR;
+               client->msg_already_received_bytes = 0;
+               dynar_clean(&client->receive_buffer);
+               break;
+       default:
+               qnetd_log(LOG_ERR, "Unhandled msgio_read error %d\n", res);
+               exit(1);
+               break;
+       }
+
+       return (ret_val);
+}
+
+int
+qnetd_client_net_accept(struct qnetd_instance *instance)
+{
+       PRNetAddr client_addr;
+       PRFileDesc *client_socket;
+       struct qnetd_client *client;
+       char *client_addr_str;
+       int res_err;
+
+       client_addr_str = NULL;
+
+       res_err = -1;
+
+       if ((client_socket = PR_Accept(instance->server.socket, &client_addr,
+           PR_INTERVAL_NO_TIMEOUT)) == NULL) {
+               qnetd_log_nss(LOG_ERR, "Can't accept connection");
+               return (-1);
+       }
+
+       if (nss_sock_set_non_blocking(client_socket) != 0) {
+               qnetd_log_nss(LOG_ERR, "Can't set client socket to non blocking mode");
+               goto exit_close;
+       }
+
+       if (instance->max_clients != 0 &&
+           qnetd_client_list_no_clients(&instance->clients) >= instance->max_clients) {
+               qnetd_log(LOG_ERR, "Maximum clients reached. Not accepting connection");
+               goto exit_close;
+       }
+
+       client_addr_str = malloc(CLIENT_ADDR_STR_LEN);
+       if (client_addr_str == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc client addr str memory. Not accepting connection");
+               goto exit_close;
+       }
+
+       if (PR_NetAddrToString(&client_addr, client_addr_str, CLIENT_ADDR_STR_LEN) != PR_SUCCESS) {
+               qnetd_log_nss(LOG_ERR, "Can't convert client address to string. Not accepting connection");
+               goto exit_close;
+       }
+
+       if (snprintf(client_addr_str + strlen(client_addr_str),
+           CLIENT_ADDR_STR_LEN_COLON_PORT, ":%"PRIu16,
+           ntohs(client_addr.ipv6.port)) >= CLIENT_ADDR_STR_LEN_COLON_PORT) {
+               qnetd_log(LOG_ERR, "Can't store port to client addr str. Not accepting connection");
+               goto exit_close;
+       }
+
+       client = qnetd_client_list_add(&instance->clients, client_socket, &client_addr,
+           client_addr_str,
+           instance->advanced_settings->max_client_receive_size,
+           instance->advanced_settings->max_client_send_buffers,
+           instance->advanced_settings->max_client_send_size, &instance->main_timer_list);
+       if (client == NULL) {
+               qnetd_log(LOG_ERR, "Can't add client to list");
+               res_err = -2;
+               goto exit_close;
+       }
+
+       return (0);
+
+exit_close:
+       free(client_addr_str);
+       PR_Close(client_socket);
+
+       return (res_err);
+}
diff --git a/qdevices/qnetd-client-net.h b/qdevices/qnetd-client-net.h
new file mode 100644 (file)
index 0000000..b9a2230
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLIENT_NET_H_
+#define _QNETD_CLIENT_NET_H_
+
+#include <sys/types.h>
+
+#include "qnetd-client.h"
+#include "qnetd-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qnetd_client_net_write(struct qnetd_instance *instance,
+    struct qnetd_client *client);
+
+extern int             qnetd_client_net_read(struct qnetd_instance *instance,
+    struct qnetd_client *client);
+
+extern int             qnetd_client_net_accept(struct qnetd_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLIENT_NET_H_ */
diff --git a/qdevices/qnetd-client-send.c b/qdevices/qnetd-client-send.c
new file mode 100644 (file)
index 0000000..072e467
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "qnetd-client-send.h"
+#include "qnetd-log.h"
+#include "qnetd-log-debug.h"
+#include "msg.h"
+
+int
+qnetd_client_send_err(struct qnetd_client *client, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_reply_error_code reply)
+{
+       struct send_buffer_list_entry *send_buffer;
+
+       send_buffer = send_buffer_list_get_new(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc server error msg from list. "
+                   "Disconnecting client connection.");
+
+               return (-1);
+       }
+
+       if (msg_create_server_error(&send_buffer->buffer, add_msg_seq_number,
+           msg_seq_number, reply) == 0) {
+               qnetd_log(LOG_ERR, "Can't alloc server error msg. "
+                   "Disconnecting client connection.");
+
+               send_buffer_list_discard_new(&client->send_buffer_list, send_buffer);
+               return (-1);
+       };
+
+       send_buffer_list_put(&client->send_buffer_list, send_buffer);
+
+       return (0);
+}
+
+int
+qnetd_client_send_vote_info(struct qnetd_client *client, uint32_t msg_seq_number,
+    const struct tlv_ring_id *ring_id, enum tlv_vote vote)
+{
+       struct send_buffer_list_entry *send_buffer;
+
+       /*
+        * Store result vote
+        */
+       client->last_sent_vote = vote;
+       if (vote == TLV_VOTE_ACK || vote == TLV_VOTE_NACK) {
+               client->last_sent_ack_nack_vote = vote;
+       }
+
+       qnetd_log_debug_send_vote_info(client, msg_seq_number, vote);
+
+       send_buffer = send_buffer_list_get_new(&client->send_buffer_list);
+       if (send_buffer == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc vote info msg from list. "
+                   "Disconnecting client connection.");
+
+               return (-1);
+       }
+
+       if (msg_create_vote_info(&send_buffer->buffer, msg_seq_number, ring_id, vote) == 0) {
+               qnetd_log(LOG_ERR, "Can't alloc vote info msg. "
+                   "Disconnecting client connection.");
+
+               send_buffer_list_discard_new(&client->send_buffer_list, send_buffer);
+               return (-1);
+       };
+
+       send_buffer_list_put(&client->send_buffer_list, send_buffer);
+
+       return (0);
+}
diff --git a/qdevices/qnetd-client-send.h b/qdevices/qnetd-client-send.h
new file mode 100644 (file)
index 0000000..229f8a0
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLIENT_SEND_H_
+#define _QNETD_CLIENT_SEND_H_
+
+#include <sys/types.h>
+
+#include "qnetd-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qnetd_client_send_err(struct qnetd_client *client,
+    int add_msg_seq_number, uint32_t msg_seq_number, enum tlv_reply_error_code reply);
+
+extern int             qnetd_client_send_vote_info(struct qnetd_client *client,
+    uint32_t msg_seq_number, const struct tlv_ring_id *ring_id, enum tlv_vote vote);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLIENT_SEND_H_ */
diff --git a/qdevices/qnetd-client.c b/qdevices/qnetd-client.c
new file mode 100644 (file)
index 0000000..229123e
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "qnet-config.h"
+#include "qnetd-client.h"
+
+void
+qnetd_client_init(struct qnetd_client *client, PRFileDesc *sock, PRNetAddr *addr,
+    char *addr_str,
+    size_t max_receive_size, size_t max_send_buffers, size_t max_send_size,
+    struct timer_list *main_timer_list)
+{
+
+       memset(client, 0, sizeof(*client));
+       client->socket = sock;
+       client->addr_str = addr_str;
+       memcpy(&client->addr, addr, sizeof(*addr));
+       dynar_init(&client->receive_buffer, max_receive_size);
+       send_buffer_list_init(&client->send_buffer_list, max_send_buffers, max_send_size);
+       node_list_init(&client->configuration_node_list);
+       node_list_init(&client->last_membership_node_list);
+       node_list_init(&client->last_quorum_node_list);
+       client->main_timer_list = main_timer_list;
+}
+
+void
+qnetd_client_destroy(struct qnetd_client *client)
+{
+
+       free(client->cluster_name);
+       free(client->addr_str);
+       node_list_free(&client->last_quorum_node_list);
+       node_list_free(&client->last_membership_node_list);
+       node_list_free(&client->configuration_node_list);
+       send_buffer_list_free(&client->send_buffer_list);
+       dynar_destroy(&client->receive_buffer);
+}
diff --git a/qdevices/qnetd-client.h b/qdevices/qnetd-client.h
new file mode 100644 (file)
index 0000000..4930d71
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLIENT_H_
+#define _QNETD_CLIENT_H_
+
+#include <sys/types.h>
+
+#include <sys/queue.h>
+#include <inttypes.h>
+
+#include <nspr.h>
+#include "dynar.h"
+#include "tlv.h"
+#include "send-buffer-list.h"
+#include "node-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qnetd_client {
+       PRFileDesc *socket;
+       PRNetAddr addr;
+       char *addr_str;
+       struct dynar receive_buffer;
+       struct send_buffer_list send_buffer_list;
+       size_t msg_already_received_bytes;
+       int skipping_msg;       /* When incorrect message was received skip it */
+       int tls_started;        /* Set after TLS started */
+       int tls_peer_certificate_verified;      /* Certificate is verified only once */
+       int preinit_received;
+       int init_received;
+       char *cluster_name;
+       size_t cluster_name_len;
+       uint8_t node_id_set;
+       uint32_t node_id;
+       enum tlv_decision_algorithm_type decision_algorithm;
+       struct tlv_tie_breaker tie_breaker;
+       uint32_t heartbeat_interval;
+       enum tlv_reply_error_code skipping_msg_reason;
+       void *algorithm_data;
+       struct node_list configuration_node_list;
+       uint8_t config_version_set;
+       uint64_t config_version;
+       struct node_list last_membership_node_list;
+       struct node_list last_quorum_node_list;
+       struct tlv_ring_id last_ring_id;
+       struct qnetd_cluster *cluster;
+       struct qnetd_cluster_list *cluster_list;
+       struct timer_list *main_timer_list;
+       struct timer_list_entry *algo_timer;
+       uint32_t algo_timer_vote_info_msq_seq_number;
+       int schedule_disconnect;
+       uint32_t dpd_time_since_last_check;
+       uint32_t dpd_msg_received_since_last_check;
+       enum tlv_vote last_sent_vote;
+       enum tlv_vote last_sent_ack_nack_vote;
+       enum tlv_heuristics last_membership_heuristics; /* Passed in membership node list */
+       enum tlv_heuristics last_regular_heuristics; /* Passed in heuristics change callback */
+       enum tlv_heuristics last_heuristics; /* Latest heuristics both membership and regular */
+       TAILQ_ENTRY(qnetd_client) entries;
+       TAILQ_ENTRY(qnetd_client) cluster_entries;
+};
+
+extern void            qnetd_client_init(struct qnetd_client *client, PRFileDesc *sock,
+    PRNetAddr *addr, char *addr_str, size_t max_receive_size, size_t max_send_buffers,
+    size_t max_send_size, struct timer_list *main_timer_list);
+
+extern void            qnetd_client_destroy(struct qnetd_client *client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLIENT_H_ */
diff --git a/qdevices/qnetd-cluster-list.c b/qdevices/qnetd-cluster-list.c
new file mode 100644 (file)
index 0000000..8739bb3
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "qnetd-cluster-list.h"
+
+void
+qnetd_cluster_list_init(struct qnetd_cluster_list *list)
+{
+
+       TAILQ_INIT(list);
+}
+
+struct qnetd_cluster *
+qnetd_cluster_list_find_by_name(struct qnetd_cluster_list *list,
+    const char *cluster_name, size_t cluster_name_len)
+{
+       struct qnetd_cluster *cluster;
+
+       TAILQ_FOREACH(cluster, list, entries) {
+               if (cluster->cluster_name_len == cluster_name_len &&
+                   memcmp(cluster->cluster_name, cluster_name, cluster_name_len) == 0) {
+                       return (cluster);
+               }
+       }
+
+       return (NULL);
+}
+
+struct qnetd_cluster *
+qnetd_cluster_list_add_client(struct qnetd_cluster_list *list, struct qnetd_client *client)
+{
+       struct qnetd_cluster *cluster;
+
+       cluster = qnetd_cluster_list_find_by_name(list, client->cluster_name,
+           client->cluster_name_len);
+       if (cluster == NULL) {
+               cluster = (struct qnetd_cluster *)malloc(sizeof(*cluster));
+               if (cluster == NULL) {
+                       return (NULL);
+               }
+
+               if (qnetd_cluster_init(cluster, client->cluster_name,
+                   client->cluster_name_len) != 0) {
+                       free(cluster);
+
+                       return (NULL);
+               }
+
+               TAILQ_INSERT_TAIL(list, cluster, entries);
+       }
+
+       TAILQ_INSERT_TAIL(&cluster->client_list, client, cluster_entries);
+
+       return (cluster);
+}
+
+void
+qnetd_cluster_list_del_client(struct qnetd_cluster_list *list, struct qnetd_cluster *cluster,
+    struct qnetd_client *client)
+{
+
+       TAILQ_REMOVE(&cluster->client_list, client, cluster_entries);
+
+       if (TAILQ_EMPTY(&cluster->client_list)) {
+               TAILQ_REMOVE(list, cluster, entries);
+
+               qnetd_cluster_destroy(cluster);
+               free(cluster);
+       }
+}
+
+void
+qnetd_cluster_list_free(struct qnetd_cluster_list *list)
+{
+       struct qnetd_cluster *cluster;
+       struct qnetd_cluster *cluster_next;
+
+       cluster = TAILQ_FIRST(list);
+       while (cluster != NULL) {
+               cluster_next = TAILQ_NEXT(cluster, entries);
+
+               qnetd_cluster_destroy(cluster);
+               free(cluster);
+
+               cluster = cluster_next;
+       }
+
+       TAILQ_INIT(list);
+}
+
+size_t
+qnetd_cluster_list_size(const struct qnetd_cluster_list *list)
+{
+       size_t res;
+       struct qnetd_cluster *cluster;
+
+       res = 0;
+
+       TAILQ_FOREACH(cluster, list, entries) {
+               res++;
+       }
+
+       return (res);
+}
diff --git a/qdevices/qnetd-cluster-list.h b/qdevices/qnetd-cluster-list.h
new file mode 100644 (file)
index 0000000..45f49df
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLUSTER_LIST_H_
+#define _QNETD_CLUSTER_LIST_H_
+
+#include <sys/types.h>
+
+#include <sys/queue.h>
+#include <inttypes.h>
+
+#include "qnetd-client-list.h"
+#include "qnetd-cluster.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+TAILQ_HEAD(qnetd_cluster_list, qnetd_cluster);
+
+extern void                             qnetd_cluster_list_init(struct qnetd_cluster_list *list);
+
+extern struct qnetd_cluster            *qnetd_cluster_list_find_by_name(
+    struct qnetd_cluster_list *list, const char *cluster_name, size_t cluster_name_len);
+
+extern struct qnetd_cluster            *qnetd_cluster_list_add_client(
+    struct qnetd_cluster_list *list, struct qnetd_client *client);
+
+extern void                             qnetd_cluster_list_del_client(
+    struct qnetd_cluster_list *list, struct qnetd_cluster *cluster, struct qnetd_client *client);
+
+extern void                             qnetd_cluster_list_free(struct qnetd_cluster_list *list);
+
+extern size_t                           qnetd_cluster_list_size(
+    const struct qnetd_cluster_list *list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLUSTER_LIST_H_ */
diff --git a/qdevices/qnetd-cluster.c b/qdevices/qnetd-cluster.c
new file mode 100644 (file)
index 0000000..32c2d3b
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "qnetd-cluster.h"
+
+int
+qnetd_cluster_init(struct qnetd_cluster *cluster, const char *cluster_name, size_t cluster_name_len)
+{
+
+       memset(cluster, 0, sizeof(*cluster));
+
+       cluster->cluster_name = malloc(cluster_name_len + 1);
+       if (cluster->cluster_name == NULL) {
+               return (-1);
+       }
+       memset(cluster->cluster_name, 0, cluster_name_len + 1);
+       memcpy(cluster->cluster_name, cluster_name, cluster_name_len);
+
+       cluster->cluster_name_len = cluster_name_len;
+       TAILQ_INIT(&cluster->client_list);
+
+       return (0);
+}
+
+void
+qnetd_cluster_destroy(struct qnetd_cluster *cluster)
+{
+
+       free(cluster->cluster_name);
+       cluster->cluster_name = NULL;
+}
+
+size_t
+qnetd_cluster_size(const struct qnetd_cluster *cluster)
+{
+       size_t res;
+       struct qnetd_client *client;
+
+       res = 0;
+
+       TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) {
+               res++;
+       }
+
+       return (res);
+}
+
+struct qnetd_client *
+qnetd_cluster_find_client_by_node_id(const struct qnetd_cluster *cluster, uint32_t node_id)
+{
+       struct qnetd_client *client;
+
+       TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) {
+               if (client->node_id == node_id) {
+                       return (client);
+               }
+       }
+
+       return (NULL);
+}
diff --git a/qdevices/qnetd-cluster.h b/qdevices/qnetd-cluster.h
new file mode 100644 (file)
index 0000000..98d6614
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLUSTER_H_
+#define _QNETD_CLUSTER_H_
+
+#include <sys/types.h>
+
+#include <sys/queue.h>
+#include <inttypes.h>
+
+#include "tlv.h"
+#include "qnetd-client-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qnetd_cluster {
+       char *cluster_name;
+       size_t cluster_name_len;
+       void *algorithm_data;
+       struct qnetd_client_list client_list;
+       TAILQ_ENTRY(qnetd_cluster) entries;
+};
+
+extern int                     qnetd_cluster_init(struct qnetd_cluster *cluster,
+    const char *cluster_name, size_t cluster_name_len);
+
+extern void                    qnetd_cluster_destroy(struct qnetd_cluster *cluster);
+
+extern size_t                  qnetd_cluster_size(const struct qnetd_cluster *cluster);
+
+extern struct qnetd_client     *qnetd_cluster_find_client_by_node_id(
+    const struct qnetd_cluster *cluster, uint32_t node_id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLUSTER_H_ */
diff --git a/qdevices/qnetd-dpd-timer.c b/qdevices/qnetd-dpd-timer.c
new file mode 100644 (file)
index 0000000..73d569a
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qnetd-dpd-timer.h"
+#include "qnetd-log.h"
+
+static int
+qnetd_dpd_timer_cb(void *data1, void *data2)
+{
+       struct qnetd_instance *instance;
+       struct qnetd_client *client;
+
+       instance = (struct qnetd_instance *)data1;
+
+       TAILQ_FOREACH(client, &instance->clients, entries) {
+               if (!client->init_received) {
+                       continue;
+               }
+
+               client->dpd_time_since_last_check += instance->advanced_settings->dpd_interval;
+
+               if (client->dpd_time_since_last_check > (client->heartbeat_interval * 2)) {
+                       if (!client->dpd_msg_received_since_last_check) {
+                               qnetd_log(LOG_WARNING, "Client %s doesn't sent any message during "
+                                   "%"PRIu32"ms. Disconnecting",
+                                   client->addr_str, client->dpd_time_since_last_check);
+
+                               client->schedule_disconnect = 1;
+                       } else {
+                               client->dpd_time_since_last_check = 0;
+                               client->dpd_msg_received_since_last_check = 0;
+                       }
+               }
+       }
+
+       return (-1);
+}
+
+int
+qnetd_dpd_timer_init(struct qnetd_instance *instance)
+{
+
+       if (!instance->advanced_settings->dpd_enabled) {
+               return (0);
+       }
+
+       instance->dpd_timer = timer_list_add(&instance->main_timer_list,
+           instance->advanced_settings->dpd_interval,
+           qnetd_dpd_timer_cb, (void *)instance, NULL);
+       if (instance->dpd_timer == NULL) {
+               qnetd_log(LOG_ERR, "Can't initialize dpd timer");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+void
+qnetd_dpd_timer_destroy(struct qnetd_instance *instance)
+{
+
+       if (instance->dpd_timer != NULL) {
+               timer_list_delete(&instance->main_timer_list, instance->dpd_timer);
+               instance->dpd_timer = NULL;
+       }
+}
diff --git a/qdevices/qnetd-dpd-timer.h b/qdevices/qnetd-dpd-timer.h
new file mode 100644 (file)
index 0000000..ddd8fac
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_DPD_TIMER_H_
+#define _QNETD_DPD_TIMER_H_
+
+#include "qnetd-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             qnetd_dpd_timer_init(struct qnetd_instance *instance);
+
+extern void            qnetd_dpd_timer_destroy(struct qnetd_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_DPD_TIMER_H_ */
diff --git a/qdevices/qnetd-instance.c b/qdevices/qnetd-instance.c
new file mode 100644 (file)
index 0000000..931b18a
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <pk11func.h>
+#include "qnetd-instance.h"
+#include "qnetd-client.h"
+#include "qnetd-algorithm.h"
+#include "qnetd-log-debug.h"
+#include "qnetd-dpd-timer.h"
+#include "qnetd-poll-array-user-data.h"
+#include "qnetd-client-algo-timer.h"
+
+int
+qnetd_instance_init(struct qnetd_instance *instance,
+    enum tlv_tls_supported tls_supported, int tls_client_cert_required, size_t max_clients,
+    const struct qnetd_advanced_settings *advanced_settings)
+{
+
+       memset(instance, 0, sizeof(*instance));
+
+       instance->advanced_settings = advanced_settings;
+
+       pr_poll_array_init(&instance->poll_array, sizeof(struct qnetd_poll_array_user_data));
+       qnetd_client_list_init(&instance->clients);
+       qnetd_cluster_list_init(&instance->clusters);
+
+       instance->tls_supported = tls_supported;
+       instance->tls_client_cert_required = tls_client_cert_required;
+
+       instance->max_clients = max_clients;
+
+       timer_list_init(&instance->main_timer_list);
+
+       if (qnetd_dpd_timer_init(instance) != 0) {
+               return (0);
+       }
+
+       return (0);
+}
+
+int
+qnetd_instance_destroy(struct qnetd_instance *instance)
+{
+       struct qnetd_client *client;
+       struct qnetd_client *client_next;
+
+       qnetd_dpd_timer_destroy(instance);
+
+       client = TAILQ_FIRST(&instance->clients);
+       while (client != NULL) {
+               client_next = TAILQ_NEXT(client, entries);
+
+               qnetd_instance_client_disconnect(instance, client, 1);
+
+               client = client_next;
+       }
+
+       pr_poll_array_destroy(&instance->poll_array);
+       qnetd_cluster_list_free(&instance->clusters);
+       qnetd_client_list_free(&instance->clients);
+       timer_list_free(&instance->main_timer_list);
+
+       return (0);
+}
+
+void
+qnetd_instance_client_disconnect(struct qnetd_instance *instance, struct qnetd_client *client,
+    int server_going_down)
+{
+
+       qnetd_log_debug_client_disconnect(client, server_going_down);
+
+       if (client->init_received) {
+               qnetd_algorithm_client_disconnect(client, server_going_down);
+       }
+
+       PR_Close(client->socket);
+       if (client->cluster != NULL) {
+               qnetd_cluster_list_del_client(&instance->clusters, client->cluster, client);
+       }
+       qnetd_client_algo_timer_abort(client);
+       qnetd_client_list_del(&instance->clients, client);
+}
+
+int
+qnetd_instance_init_certs(struct qnetd_instance *instance)
+{
+
+       instance->server.cert = PK11_FindCertFromNickname(
+           instance->advanced_settings->cert_nickname, NULL);
+       if (instance->server.cert == NULL) {
+               return (-1);
+       }
+
+       instance->server.private_key = PK11_FindKeyByAnyCert(instance->server.cert, NULL);
+       if (instance->server.private_key == NULL) {
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/qnetd-instance.h b/qdevices/qnetd-instance.h
new file mode 100644 (file)
index 0000000..d2c4733
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_INSTANCE_H_
+#define _QNETD_INSTANCE_H_
+
+#include <sys/types.h>
+
+#include <certt.h>
+#include <keyhi.h>
+#include <sys/queue.h>
+
+#include "qnetd-client-list.h"
+#include "qnetd-cluster-list.h"
+#include "pr-poll-array.h"
+#include "qnet-config.h"
+#include "timer-list.h"
+#include "unix-socket-ipc.h"
+#include "qnetd-advanced-settings.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qnetd_instance {
+       struct {
+               PRFileDesc *socket;
+               CERTCertificate *cert;
+               SECKEYPrivateKey *private_key;
+       } server;
+       size_t max_clients;
+       struct qnetd_client_list clients;
+       struct qnetd_cluster_list clusters;
+       struct pr_poll_array poll_array;
+       enum tlv_tls_supported tls_supported;
+       int tls_client_cert_required;
+       const char *host_addr;
+       uint16_t host_port;
+       struct timer_list main_timer_list;
+       struct timer_list_entry *dpd_timer;             /* Dead peer detection timer */
+       struct unix_socket_ipc local_ipc;
+       PRFileDesc *ipc_socket_poll_fd;
+       const struct qnetd_advanced_settings *advanced_settings;
+};
+
+extern int             qnetd_instance_init(struct qnetd_instance *instance,
+    enum tlv_tls_supported tls_supported, int tls_client_cert_required, size_t max_clients,
+    const struct qnetd_advanced_settings *advanced_settings);
+
+extern int             qnetd_instance_destroy(struct qnetd_instance *instance);
+
+extern void            qnetd_instance_client_disconnect(struct qnetd_instance *instance,
+    struct qnetd_client *client, int server_going_down);
+
+extern int             qnetd_instance_init_certs(struct qnetd_instance *instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_INSTANCE_H_ */
diff --git a/qdevices/qnetd-ipc-cmd.c b/qdevices/qnetd-ipc-cmd.c
new file mode 100644 (file)
index 0000000..3c44f34
--- /dev/null
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dynar-str.h"
+#include "qnetd-ipc-cmd.h"
+#include "qnetd-log.h"
+#include "utils.h"
+
+int
+qnetd_ipc_cmd_status(struct qnetd_instance *instance, struct dynar *outbuf, int verbose)
+{
+
+       if (dynar_str_catf(outbuf, "QNetd address:\t\t\t%s:%"PRIu16"\n",
+           (instance->host_addr != NULL ? instance->host_addr : "*"), instance->host_port) == -1) {
+               return (-1);
+       }
+
+       if (dynar_str_catf(outbuf, "TLS:\t\t\t\t%s%s\n",
+           tlv_tls_supported_to_str(instance->tls_supported),
+           ((instance->tls_supported != TLV_TLS_UNSUPPORTED && instance->tls_client_cert_required) ?
+           " (client certificate required)" : "")) == -1) {
+               return (-1);
+       }
+
+       if (dynar_str_catf(outbuf, "Connected clients:\t\t%zu\n",
+           qnetd_client_list_no_clients(&instance->clients)) == -1) {
+               return (-1);
+       }
+
+       if (dynar_str_catf(outbuf, "Connected clusters:\t\t%zu\n",
+           qnetd_cluster_list_size(&instance->clusters)) == -1) {
+               return (-1);
+       }
+
+       if (!verbose) {
+               return (0);
+       }
+
+       if (instance->max_clients != 0) {
+               if (dynar_str_catf(outbuf, "Maximum allowed clients:\t%zu\n",
+                   instance->max_clients) == -1) {
+                       return (-1);
+               }
+       }
+
+       if (dynar_str_catf(outbuf, "Maximum send/receive size:\t%zu/%zu bytes\n",
+           instance->advanced_settings->max_client_send_size,
+           instance->advanced_settings->max_client_receive_size) == -1) {
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+qnetd_ipc_cmd_add_tie_breaker(const struct qnetd_client *client,
+    struct dynar *outbuf)
+{
+
+       if (dynar_str_catf(outbuf, "    Tie-breaker:\t") == -1) {
+               return (0);
+       }
+
+       switch (client->tie_breaker.mode) {
+       case TLV_TIE_BREAKER_MODE_LOWEST:
+               if (dynar_str_catf(outbuf, "Node with lowest node ID") == -1) {
+                       return (0);
+               }
+               break;
+       case TLV_TIE_BREAKER_MODE_HIGHEST:
+               if (dynar_str_catf(outbuf, "Node with highest node ID") == -1) {
+                       return (0);
+               }
+               break;
+       case TLV_TIE_BREAKER_MODE_NODE_ID:
+               if (dynar_str_catf(outbuf, "Node with node ID "UTILS_PRI_NODE_ID,
+                   client->tie_breaker.node_id) == -1) {
+                       return (0);
+               }
+               break;
+       }
+
+       return (dynar_str_catf(outbuf, "\n") != -1);
+}
+
+static int
+qnetd_ipc_cmd_list_add_node_list(struct dynar *outbuf, int verbose, const struct node_list *nlist)
+{
+       struct node_list_entry *node_info;
+       int i;
+
+       i = 0;
+
+       TAILQ_FOREACH(node_info, nlist, entries) {
+               if (i != 0) {
+                       if (dynar_str_catf(outbuf, ", ") == -1) {
+                               return (-1);
+                       }
+               }
+
+               if (dynar_str_catf(outbuf, UTILS_PRI_NODE_ID, node_info->node_id) == -1) {
+                       return (-1);
+               }
+
+               if (node_info->data_center_id != 0) {
+                       if (dynar_str_catf(outbuf, " (" UTILS_PRI_DATACENTER_ID ")",
+                           node_info->data_center_id) == -1) {
+                               return (-1);
+                       }
+
+               }
+
+               i++;
+       }
+
+       return (0);
+}
+
+static int
+qnetd_ipc_cmd_list_add_client_info(const struct qnetd_client *client, struct dynar *outbuf,
+    int verbose, size_t client_no)
+{
+
+       if (dynar_str_catf(outbuf, "    Node ID "UTILS_PRI_NODE_ID":\n",
+           client->node_id) == -1) {
+               return (-1);
+       }
+
+       if (dynar_str_catf(outbuf, "        Client address:\t\t%s\n",
+           client->addr_str) == -1) {
+               return (-1);
+       }
+
+       if (verbose) {
+               if (dynar_str_catf(outbuf, "        HB interval:\t\t%"PRIu32"ms\n",
+                   client->heartbeat_interval) == -1) {
+                       return (-1);
+               }
+       }
+
+       if (client->config_version_set) {
+               if (dynar_str_catf(outbuf, "        Configuration version:\t"
+                   UTILS_PRI_CONFIG_VERSION"\n", client->config_version) == -1) {
+                       return (-1);
+               }
+       }
+
+       if (!node_list_is_empty(&client->configuration_node_list)) {
+               if ((dynar_str_catf(outbuf, "        Configured node list:\t") == -1) ||
+                   (qnetd_ipc_cmd_list_add_node_list(outbuf, verbose,
+                   &client->configuration_node_list) == -1) ||
+                   (dynar_str_catf(outbuf, "\n") == -1)) {
+                       return (-1);
+               }
+       }
+
+       if (verbose) {
+               if (dynar_str_catf(outbuf, "        Ring ID:\t\t"UTILS_PRI_RING_ID"\n",
+                   client->last_ring_id.node_id, client->last_ring_id.seq) == -1) {
+                       return (-1);
+               }
+       }
+
+       if (!node_list_is_empty(&client->last_membership_node_list)) {
+               if ((dynar_str_catf(outbuf, "        Membership node list:\t") == -1) ||
+                   (qnetd_ipc_cmd_list_add_node_list(outbuf, verbose,
+                   &client->last_membership_node_list) == -1) ||
+                   (dynar_str_catf(outbuf, "\n") == -1)) {
+                       return (-1);
+               }
+       }
+
+       if (client->last_heuristics != TLV_HEURISTICS_UNDEFINED || verbose) {
+               if (dynar_str_catf(outbuf, "        Heuristics:\t\t%s",
+                   tlv_heuristics_to_str(client->last_heuristics)) == -1) {
+                       return (-1);
+               }
+
+               if (verbose) {
+                       if (dynar_str_catf(outbuf, " (membership: %s, regular: %s)",
+                           tlv_heuristics_to_str(client->last_membership_heuristics),
+                           tlv_heuristics_to_str(client->last_regular_heuristics)) == -1) {
+                               return (-1);
+                       }
+               }
+
+               if (dynar_str_catf(outbuf, "\n") == -1) {
+                       return (-1);
+               }
+       }
+
+       if (verbose) {
+               if (dynar_str_catf(outbuf, "        TLS active:\t\t%s",
+                   (client->tls_started ? "Yes" : "No")) == -1) {
+                       return (-1);
+               }
+
+               if (client->tls_started && client->tls_peer_certificate_verified) {
+                       if (dynar_str_catf(outbuf, " (client certificate verified)") == -1) {
+                               return (-1);
+                       }
+               }
+
+               if (dynar_str_catf(outbuf, "\n") == -1) {
+                       return (-1);
+               }
+       }
+
+       if (client->last_sent_vote != TLV_VOTE_UNDEFINED) {
+               if (dynar_str_catf(outbuf, "        Vote:\t\t\t%s",
+                   tlv_vote_to_str(client->last_sent_vote)) == -1) {
+                       return (-1);
+               }
+
+               if (client->last_sent_ack_nack_vote != TLV_VOTE_UNDEFINED) {
+                       if (dynar_str_catf(outbuf, " (%s)",
+                           tlv_vote_to_str(client->last_sent_ack_nack_vote)) == -1) {
+                               return (-1);
+                       }
+               }
+
+               if (dynar_str_catf(outbuf, "\n") == -1) {
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+int
+qnetd_ipc_cmd_list(struct qnetd_instance *instance, struct dynar *outbuf, int verbose,
+    const char *cluster_name)
+{
+       struct qnetd_cluster *cluster;
+       struct qnetd_client *client;
+       size_t cluster_no, client_no;
+
+       cluster_no = 0;
+       TAILQ_FOREACH(cluster, &instance->clusters, entries) {
+               if (cluster_name != NULL && strcmp(cluster_name, "") != 0 &&
+                   strcmp(cluster_name, cluster->cluster_name) != 0) {
+                       continue;
+               }
+
+               if (dynar_str_catf(outbuf, "Cluster \"%s\":\n", cluster->cluster_name) == -1) {
+                       return (-1);
+               }
+
+               client_no = 0;
+
+               TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) {
+                       if (client_no == 0) {
+                               if (dynar_str_catf(outbuf, "    Algorithm:\t\t%s\n",
+                                   tlv_decision_algorithm_type_to_str(
+                                   client->decision_algorithm)) == -1) {
+                                       return (-1);
+                               }
+
+                               if (!qnetd_ipc_cmd_add_tie_breaker(client, outbuf)) {
+                                       return (-1);
+                               }
+                       }
+
+                       if (qnetd_ipc_cmd_list_add_client_info(client, outbuf, verbose,
+                           client_no) == -1) {
+                               return (-1);
+                       }
+
+                       client_no++;
+                }
+
+                cluster_no++;
+       }
+
+       return (0);
+}
diff --git a/qdevices/qnetd-ipc-cmd.h b/qdevices/qnetd-ipc-cmd.h
new file mode 100644 (file)
index 0000000..dbbf705
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_IPC_CMD_H_
+#define _QNETD_IPC_CMD_H_
+
+#include "dynar.h"
+#include "qnetd-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int     qnetd_ipc_cmd_status(struct qnetd_instance *instance,
+    struct dynar *outbuf, int verbose);
+
+extern int     qnetd_ipc_cmd_list(struct qnetd_instance *instance,
+    struct dynar *outbuf, int verbose, const char *cluster_name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_IPC_CMD_H_ */
diff --git a/qdevices/qnetd-ipc.c b/qdevices/qnetd-ipc.c
new file mode 100644 (file)
index 0000000..0d40275
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qnet-config.h"
+#include "qnetd-ipc.h"
+#include "qnetd-ipc-cmd.h"
+#include "qnetd-log.h"
+#include "unix-socket-ipc.h"
+#include "dynar-simple-lex.h"
+#include "dynar-str.h"
+
+/*
+ * Needed for creating nspr handle from unix fd
+ */
+#include <private/pprio.h>
+
+int
+qnetd_ipc_init(struct qnetd_instance *instance)
+{
+
+       if (unix_socket_ipc_init(&instance->local_ipc,
+           instance->advanced_settings->local_socket_file,
+           instance->advanced_settings->local_socket_backlog,
+           instance->advanced_settings->ipc_max_clients,
+           instance->advanced_settings->ipc_max_receive_size,
+           instance->advanced_settings->ipc_max_send_size) != 0) {
+               qnetd_log_err(LOG_ERR, "Can't create unix socket");
+
+               return (-1);
+       }
+
+       if ((instance->ipc_socket_poll_fd = PR_CreateSocketPollFd(instance->local_ipc.socket)) == NULL) {
+               qnetd_log_nss(LOG_CRIT, "Can't create NSPR IPC socket poll fd");
+
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+qnetd_ipc_close(struct qnetd_instance *instance)
+{
+       int res;
+
+       res = unix_socket_ipc_close(&instance->local_ipc);
+       if (res != 0) {
+               qnetd_log_err(LOG_WARNING, "Can't close local IPC");
+       }
+
+       return (res);
+}
+
+int
+qnetd_ipc_is_closed(struct qnetd_instance *instance)
+{
+
+       return (unix_socket_ipc_is_closed(&instance->local_ipc));
+}
+
+int
+qnetd_ipc_destroy(struct qnetd_instance *instance)
+{
+       int res;
+       struct unix_socket_client *client;
+       const struct unix_socket_client_list *ipc_client_list;
+
+       ipc_client_list = &instance->local_ipc.clients;
+
+       TAILQ_FOREACH(client, ipc_client_list, entries) {
+               free(client->user_data);
+       }
+
+       if (PR_DestroySocketPollFd(instance->ipc_socket_poll_fd) != PR_SUCCESS) {
+               qnetd_log_nss(LOG_WARNING, "Unable to destroy IPC poll socket fd");
+       }
+
+       res = unix_socket_ipc_destroy(&instance->local_ipc);
+       if (res != 0) {
+               qnetd_log_err(LOG_WARNING, "Can't destroy local IPC");
+       }
+
+       return (res);
+}
+
+int
+qnetd_ipc_accept(struct qnetd_instance *instance, struct unix_socket_client **res_client)
+{
+       int res;
+       int accept_res;
+       PRFileDesc *prfd;
+
+       accept_res = unix_socket_ipc_accept(&instance->local_ipc, res_client);
+
+       switch (accept_res) {
+       case -1:
+               qnetd_log_err(LOG_ERR, "Can't accept local IPC connection");
+               res = -1;
+               goto return_res;
+               break;
+       case -2:
+               qnetd_log(LOG_ERR, "Maximum IPC clients reached. Not accepting connection");
+               res = -1;
+               goto return_res;
+               break;
+       case -3:
+               qnetd_log(LOG_ERR, "Can't add client to list");
+               res = -1;
+               goto return_res;
+               break;
+       default:
+               unix_socket_client_read_line(*res_client, 1);
+               res = 0;
+               break;
+       }
+
+       (*res_client)->user_data = malloc(sizeof(struct qnetd_ipc_user_data));
+       if ((*res_client)->user_data == NULL) {
+               qnetd_log(LOG_ERR, "Can't alloc IPC client user data");
+               res = -1;
+               qnetd_ipc_client_disconnect(instance, *res_client);
+
+               goto return_res;
+       }
+       memset((*res_client)->user_data, 0, sizeof(struct qnetd_ipc_user_data));
+
+       prfd = PR_CreateSocketPollFd((*res_client)->socket);
+       if (prfd == NULL) {
+               qnetd_log_nss(LOG_CRIT, "Can't create NSPR poll fd for IPC client. Disconnecting client");
+               qnetd_ipc_client_disconnect(instance, *res_client);
+               res = -1;
+
+               goto return_res;
+       }
+
+       ((struct qnetd_ipc_user_data *)(*res_client)->user_data)->nspr_poll_fd = prfd;
+
+return_res:
+       return (res);
+}
+
+void
+qnetd_ipc_client_disconnect(struct qnetd_instance *instance, struct unix_socket_client *client)
+{
+
+       if (PR_DestroySocketPollFd(
+           ((struct qnetd_ipc_user_data *)(client)->user_data)->nspr_poll_fd) != PR_SUCCESS) {
+               qnetd_log_nss(LOG_WARNING, "Unable to destroy client IPC poll socket fd");
+       }
+
+       free(client->user_data);
+       unix_socket_ipc_client_disconnect(&instance->local_ipc, client);
+}
+
+int
+qnetd_ipc_send_error(struct qnetd_instance *instance, struct unix_socket_client *client,
+    const char *error_fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, error_fmt);
+       res = ((dynar_str_cpy(&client->send_buffer, "Error\n") == 0) &&
+           (dynar_str_vcatf(&client->send_buffer, error_fmt, ap) > 0) &&
+           (dynar_str_cat(&client->send_buffer, "\n") == 0));
+
+       va_end(ap);
+
+       if (res) {
+               unix_socket_client_write_buffer(client, 1);
+       } else {
+               qnetd_log(LOG_ERR, "Can't send ipc error to client (buffer too small)");
+       }
+
+       return (res ? 0 : -1);
+}
+
+int
+qnetd_ipc_send_buffer(struct qnetd_instance *instance, struct unix_socket_client *client)
+{
+
+       if (dynar_str_prepend(&client->send_buffer, "OK\n") != 0) {
+               qnetd_log(LOG_ERR, "Can't send ipc message to client (buffer too small)");
+
+               if (qnetd_ipc_send_error(instance, client, "Internal IPC buffer too small") != 0) {
+                       return (-1);
+               }
+
+               return (0);
+       }
+
+       unix_socket_client_write_buffer(client, 1);
+
+       return (0);
+}
+
+static void
+qnetd_ipc_parse_line(struct qnetd_instance *instance, struct unix_socket_client *client)
+{
+       struct dynar_simple_lex lex;
+       struct dynar *token;
+       char *str;
+       struct qnetd_ipc_user_data *ipc_user_data;
+       int verbose;
+       char *cluster_name;
+
+       ipc_user_data = (struct qnetd_ipc_user_data *)client->user_data;
+
+       dynar_simple_lex_init(&lex, &client->receive_buffer, DYNAR_SIMPLE_LEX_TYPE_QUOTE);
+       token = dynar_simple_lex_token_next(&lex);
+
+       verbose = 0;
+       cluster_name = NULL;
+
+       if (token == NULL) {
+               goto exit_err_low_mem;
+       }
+
+       str = dynar_data(token);
+       if (strcasecmp(str, "") == 0) {
+               qnetd_log(LOG_DEBUG, "IPC client doesn't send command");
+               if (qnetd_ipc_send_error(instance, client, "No command specified") != 0) {
+                       client->schedule_disconnect = 1;
+               }
+       } else if (strcasecmp(str, "shutdown") == 0) {
+               qnetd_log(LOG_DEBUG, "IPC client requested shutdown");
+
+               ipc_user_data->shutdown_requested = 1;
+
+               if (qnetd_ipc_send_buffer(instance, client) != 0) {
+                       client->schedule_disconnect = 1;
+               }
+       } else if (strcasecmp(str, "status") == 0) {
+               token = dynar_simple_lex_token_next(&lex);
+               if (token == NULL) {
+                       goto exit_err_low_mem;
+               }
+
+               str = dynar_data(token);
+
+               if (token != NULL && strcmp(str, "") != 0) {
+                       if (strcasecmp(str, "verbose") == 0) {
+                               verbose = 1;
+                       }
+               }
+
+               if (qnetd_ipc_cmd_status(instance, &client->send_buffer, verbose) != 0) {
+                       if (qnetd_ipc_send_error(instance, client, "Can't get QNetd status") != 0) {
+                               client->schedule_disconnect = 1;
+                       }
+               } else {
+                       if (qnetd_ipc_send_buffer(instance, client) != 0) {
+                               client->schedule_disconnect = 1;
+                       }
+               }
+       } else if (strcasecmp(str, "list") == 0) {
+               while (((token = dynar_simple_lex_token_next(&lex)) != NULL) &&
+                   (str = dynar_data(token), strcmp(str, "") != 0)) {
+                       if (strcasecmp(str, "verbose") == 0) {
+                               verbose = 1;
+                       } else if (strcasecmp(str, "cluster") == 0) {
+                               token = dynar_simple_lex_token_next(&lex);
+                               if (token == NULL) {
+                                       goto exit_err_low_mem;
+                               }
+
+                               free(cluster_name); cluster_name = NULL;
+                               if ((cluster_name = strdup(dynar_data(token))) == NULL) {
+                                       goto exit_err_low_mem;
+                               }
+                       } else {
+                               break;
+                       }
+               }
+
+               if (qnetd_ipc_cmd_list(instance, &client->send_buffer, verbose, cluster_name) != 0) {
+                       if (qnetd_ipc_send_error(instance, client, "Can't get QNetd cluster list") != 0) {
+                               client->schedule_disconnect = 1;
+                       }
+               } else {
+                       if (qnetd_ipc_send_buffer(instance, client) != 0) {
+                               client->schedule_disconnect = 1;
+                       }
+               }
+
+               free(cluster_name); cluster_name = NULL;
+       } else {
+               qnetd_log(LOG_DEBUG, "IPC client sent unknown command");
+               if (qnetd_ipc_send_error(instance, client, "Unknown command '%s'", str) != 0) {
+                       client->schedule_disconnect = 1;
+               }
+       }
+
+       dynar_simple_lex_destroy(&lex);
+
+       return ;
+
+exit_err_low_mem:
+       free(cluster_name); cluster_name = NULL;
+
+       qnetd_log(LOG_ERR, "Can't alloc memory for simple lex");
+
+       if (qnetd_ipc_send_error(instance, client, "Command too long") != 0) {
+               client->schedule_disconnect = 1;
+       }
+}
+
+void
+qnetd_ipc_io_read(struct qnetd_instance *instance, struct unix_socket_client *client)
+{
+       int res;
+
+       res = unix_socket_client_io_read(client);
+
+       switch (res) {
+       case 0:
+               /*
+                * Partial read
+                */
+               break;
+       case -1:
+               qnetd_log(LOG_DEBUG, "IPC client closed connection");
+               client->schedule_disconnect = 1;
+               break;
+       case -2:
+               qnetd_log(LOG_ERR, "Can't store message from IPC client. Disconnecting client.");
+               client->schedule_disconnect = 1;
+               break;
+       case -3:
+               qnetd_log_err(LOG_ERR, "Can't receive message from IPC client. Disconnecting client.");
+               client->schedule_disconnect = 1;
+               break;
+       case 1:
+               /*
+                * Full message received
+                */
+               unix_socket_client_read_line(client, 0);
+
+               qnetd_ipc_parse_line(instance, client);
+               break;
+       }
+}
+
+void
+qnetd_ipc_io_write(struct qnetd_instance *instance, struct unix_socket_client *client)
+{
+       int res;
+       struct qnetd_ipc_user_data *ipc_user_data;
+
+       ipc_user_data = (struct qnetd_ipc_user_data *)client->user_data;
+
+       res = unix_socket_client_io_write(client);
+
+       switch (res) {
+       case 0:
+               /*
+                * Partial send
+                */
+               break;
+       case -1:
+               qnetd_log(LOG_DEBUG, "IPC client closed connection");
+               client->schedule_disconnect = 1;
+               break;
+       case -2:
+               qnetd_log_err(LOG_ERR, "Can't send message to IPC client. Disconnecting client");
+               client->schedule_disconnect = 1;
+               break;
+       case 1:
+               /*
+                * Full message sent
+                */
+               unix_socket_client_write_buffer(client, 0);
+               client->schedule_disconnect = 1;
+
+               if (ipc_user_data->shutdown_requested) {
+                       qnetd_ipc_close(instance);
+               }
+
+               break;
+       }
+}
diff --git a/qdevices/qnetd-ipc.h b/qdevices/qnetd-ipc.h
new file mode 100644 (file)
index 0000000..b8fbdd8
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_IPC_H_
+#define _QNETD_IPC_H_
+
+#include "qnetd-instance.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qnetd_ipc_user_data {
+       int shutdown_requested;
+       PRFileDesc *nspr_poll_fd;
+};
+
+extern int             qnetd_ipc_init(struct qnetd_instance *instance);
+
+extern int             qnetd_ipc_close(struct qnetd_instance *instance);
+
+extern int             qnetd_ipc_is_closed(struct qnetd_instance *instance);
+
+extern int             qnetd_ipc_destroy(struct qnetd_instance *instance);
+
+extern int             qnetd_ipc_accept(struct qnetd_instance *instance,
+    struct unix_socket_client **res_client);
+
+extern void            qnetd_ipc_client_disconnect(struct qnetd_instance *instance,
+    struct unix_socket_client *client);
+
+extern void            qnetd_ipc_io_read(struct qnetd_instance *instance,
+    struct unix_socket_client *client);
+
+extern void            qnetd_ipc_io_write(struct qnetd_instance *instance,
+    struct unix_socket_client *client);
+
+extern int             qnetd_ipc_send_error(struct qnetd_instance *instance,
+    struct unix_socket_client *client, const char *error_fmt, ...)
+    __attribute__((__format__(__printf__, 3, 4)));
+
+extern int             qnetd_ipc_send_buffer(struct qnetd_instance *instance,
+    struct unix_socket_client *client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_IPC_H_ */
diff --git a/qdevices/qnetd-log-debug.c b/qdevices/qnetd-log-debug.c
new file mode 100644 (file)
index 0000000..64b0bb5
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qnetd-log.h"
+#include "qnetd-log-debug.h"
+#include "utils.h"
+
+void
+qnetd_log_debug_dump_cluster(struct qnetd_cluster *cluster)
+{
+       struct qnetd_client *client;
+
+       qnetd_log(LOG_DEBUG, "  cluster dump:");
+       TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) {
+               qnetd_log(LOG_DEBUG, "    client = %s, node_id = "UTILS_PRI_NODE_ID,
+                   client->addr_str, client->node_id);
+       }
+}
+
+void
+qnetd_log_debug_new_client_connected(struct qnetd_client *client)
+{
+
+       qnetd_log(LOG_DEBUG, "New client connected");
+       qnetd_log(LOG_DEBUG, "  cluster name = %s", client->cluster_name);
+       qnetd_log(LOG_DEBUG, "  tls started = %u", client->tls_started);
+       qnetd_log(LOG_DEBUG, "  tls peer certificate verified = %u",
+           client->tls_peer_certificate_verified);
+       qnetd_log(LOG_DEBUG, "  node_id = "UTILS_PRI_NODE_ID, client->node_id);
+       qnetd_log(LOG_DEBUG, "  pointer = %p", client);
+       qnetd_log(LOG_DEBUG, "  addr_str = %s", client->addr_str);
+       qnetd_log(LOG_DEBUG, "  ring id = (" UTILS_PRI_RING_ID ")", client->last_ring_id.node_id,
+           client->last_ring_id.seq);
+
+       qnetd_log_debug_dump_cluster(client->cluster);
+}
+
+void
+qnetd_log_debug_dump_node_list(struct qnetd_client *client, const struct node_list *nodes)
+{
+       struct node_list_entry *node_info;
+
+       qnetd_log(LOG_DEBUG, "  node list:");
+       TAILQ_FOREACH(node_info, nodes, entries) {
+               qnetd_log(LOG_DEBUG, "    node_id = "UTILS_PRI_NODE_ID", "
+                   "data_center_id = "UTILS_PRI_DATACENTER_ID", "
+                   "node_state = %s", node_info->node_id, node_info->data_center_id,
+                   tlv_node_state_to_str(node_info->node_state));
+       }
+}
+
+void
+qnetd_log_debug_config_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, int config_version_set, uint64_t config_version,
+    const struct node_list *nodes, int initial)
+{
+
+       qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "sent %s node list.", client->addr_str, client->cluster_name, client->node_id,
+           (initial ? "initial" : "changed"));
+
+       qnetd_log(LOG_DEBUG, "  msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num);
+
+       if (config_version_set) {
+               qnetd_log(LOG_DEBUG, "  config version = " UTILS_PRI_CONFIG_VERSION, config_version);
+       }
+
+       qnetd_log_debug_dump_node_list(client, nodes);
+}
+
+void
+qnetd_log_debug_membership_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, const struct tlv_ring_id *ring_id,
+    enum tlv_heuristics heuristics, const struct node_list *nodes)
+{
+       qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "sent membership node list.", client->addr_str, client->cluster_name, client->node_id);
+
+       qnetd_log(LOG_DEBUG, "  msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num);
+
+       qnetd_log(LOG_DEBUG, "  ring id = (" UTILS_PRI_RING_ID ")", ring_id->node_id, ring_id->seq);
+
+       qnetd_log(LOG_DEBUG, "  heuristics = %s ", tlv_heuristics_to_str(heuristics));
+
+       qnetd_log_debug_dump_node_list(client, nodes);
+}
+
+void
+qnetd_log_debug_quorum_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes)
+{
+
+       qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "sent quorum node list.", client->addr_str, client->cluster_name, client->node_id);
+
+       qnetd_log(LOG_DEBUG, "  msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num);
+       qnetd_log(LOG_DEBUG, "  quorate = %u", quorate);
+
+       qnetd_log_debug_dump_node_list(client, nodes);
+}
+
+void
+qnetd_log_debug_client_disconnect(struct qnetd_client *client, int server_going_down)
+{
+
+       qnetd_log(LOG_DEBUG, "Client %s (init_received %u, cluster %s, node_id "
+           UTILS_PRI_NODE_ID") disconnect%s", client->addr_str, client->init_received,
+           client->cluster_name, client->node_id,
+           (server_going_down ? " (server is going down)" : ""));
+}
+
+void
+qnetd_log_debug_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num)
+{
+
+       qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "asked for a vote", client->addr_str, client->cluster_name, client->node_id);
+       qnetd_log(LOG_DEBUG, "  msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num);
+}
+
+void
+qnetd_log_debug_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num)
+{
+
+       qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "replied back to vote info message", client->addr_str, client->cluster_name,
+           client->node_id);
+       qnetd_log(LOG_DEBUG, "  msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num);
+}
+
+void
+qnetd_log_debug_send_vote_info(struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote vote)
+{
+
+       qnetd_log(LOG_DEBUG, "Sending vote info to client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") ",
+           client->addr_str, client->cluster_name, client->node_id);
+       qnetd_log(LOG_DEBUG, "  msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num);
+       qnetd_log(LOG_DEBUG, "  vote = %s", tlv_vote_to_str(vote));
+}
+
+void
+qnetd_log_debug_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num,
+    enum tlv_heuristics heuristics)
+{
+
+       qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") "
+           "sent heuristics change", client->addr_str, client->cluster_name, client->node_id);
+       qnetd_log(LOG_DEBUG, "  msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num);
+       qnetd_log(LOG_DEBUG, "  heuristics = %s", tlv_heuristics_to_str(heuristics));
+}
diff --git a/qdevices/qnetd-log-debug.h b/qdevices/qnetd-log-debug.h
new file mode 100644 (file)
index 0000000..a6d22e3
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_LOG_DEBUG_H_
+#define _QNETD_LOG_DEBUG_H_
+
+#include "qnetd-client.h"
+#include "qnetd-cluster-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void            qnetd_log_debug_dump_cluster(struct qnetd_cluster *cluster);
+
+extern void            qnetd_log_debug_new_client_connected(struct qnetd_client *client);
+
+extern void            qnetd_log_debug_dump_node_list(struct qnetd_client *client,
+    const struct node_list *nodes);
+
+extern void            qnetd_log_debug_config_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, int config_version_set, uint64_t config_version,
+    const struct node_list *nodes, int initial);
+
+extern void            qnetd_log_debug_membership_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, const struct tlv_ring_id *ring_id,
+    enum tlv_heuristics heuristics, const struct node_list *nodes);
+
+extern void            qnetd_log_debug_quorum_node_list_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes);
+
+extern void            qnetd_log_debug_client_disconnect(struct qnetd_client *client,
+    int server_going_down);
+
+extern void            qnetd_log_debug_ask_for_vote_received(struct qnetd_client *client,
+    uint32_t msg_seq_num);
+
+extern void            qnetd_log_debug_vote_info_reply_received(struct qnetd_client *client,
+    uint32_t msg_seq_num);
+
+extern void            qnetd_log_debug_send_vote_info(struct qnetd_client *client,
+    uint32_t msg_seq_num, enum tlv_vote vote);
+
+extern void            qnetd_log_debug_heuristics_change_received(struct qnetd_client *client,
+    uint32_t msg_seq_num, enum tlv_heuristics heuristics);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_LOG_DEBUG_H_ */
diff --git a/qdevices/qnetd-log.c b/qdevices/qnetd-log.c
new file mode 100644 (file)
index 0000000..6f225ae
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <syslog.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "qnet-config.h"
+#include "qnetd-log.h"
+
+static int qnetd_log_config_target = 0;
+static int qnetd_log_config_debug = 0;
+static int qnetd_log_config_priority_bump = 0;
+
+static const char qnetd_log_month_str[][4] = {
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+struct qnetd_log_syslog_prio_to_str_item {
+       int priority;
+       const char *priority_str;
+};
+
+static struct qnetd_log_syslog_prio_to_str_item qnetd_syslog_prio_to_str_array[] = {
+    {LOG_EMERG,                "emerg"},
+    {LOG_ALERT,                "alert"},
+    {LOG_CRIT,         "crit"},
+    {LOG_ERR,          "error"},
+    {LOG_WARNING,      "warning"},
+    {LOG_NOTICE,       "notice"},
+    {LOG_INFO,         "info"},
+    {LOG_DEBUG,                "debug"},
+    {-1, NULL}};
+
+void
+qnetd_log_init(int target)
+{
+
+       qnetd_log_config_target = target;
+
+       if (qnetd_log_config_target & QNETD_LOG_TARGET_SYSLOG) {
+               openlog(QNETD_PROGRAM_NAME, LOG_PID, LOG_DAEMON);
+       }
+}
+
+static const char *
+qnetd_log_syslog_prio_to_str(int priority)
+{
+
+       if (priority >= LOG_EMERG && priority <= LOG_DEBUG) {
+               return (qnetd_syslog_prio_to_str_array[priority].priority_str);
+       } else {
+               return ("none");
+       }
+}
+
+void
+qnetd_log_vprintf(int priority, const char *format, va_list ap)
+{
+       time_t current_time;
+       struct tm tm_res;
+       int final_priority;
+       va_list ap_copy;
+
+       if (priority != LOG_DEBUG || (qnetd_log_config_debug)) {
+               if (qnetd_log_config_target & QNETD_LOG_TARGET_STDERR) {
+                       current_time = time(NULL);
+                       localtime_r(&current_time, &tm_res);
+
+                       fprintf(stderr, "%s %02d %02d:%02d:%02d ",
+                           qnetd_log_month_str[tm_res.tm_mon], tm_res.tm_mday, tm_res.tm_hour,
+                           tm_res.tm_min, tm_res.tm_sec);
+
+                       fprintf(stderr, "%-7s ", qnetd_log_syslog_prio_to_str(priority));
+
+                       va_copy(ap_copy, ap);
+                       vfprintf(stderr, format, ap_copy);
+                       va_end(ap_copy);
+                       fprintf(stderr, "\n");
+               }
+
+               if (qnetd_log_config_target & QNETD_LOG_TARGET_SYSLOG) {
+                       final_priority = priority;
+                       if (qnetd_log_config_priority_bump && priority > LOG_INFO) {
+                               final_priority = LOG_INFO;
+                       }
+
+                       va_copy(ap_copy, ap);
+                       vsyslog(final_priority, format, ap);
+                       va_end(ap_copy);
+               }
+       }
+}
+
+void
+qnetd_log_printf(int priority, const char *format, ...)
+{
+       va_list ap;
+
+       va_start(ap, format);
+
+       qnetd_log_vprintf(priority, format, ap);
+
+       va_end(ap);
+}
+
+void
+qnetd_log_close(void)
+{
+
+       if (qnetd_log_config_target & QNETD_LOG_TARGET_SYSLOG) {
+               closelog();
+       }
+}
+
+void
+qnetd_log_set_debug(int enabled)
+{
+
+       qnetd_log_config_debug = enabled;
+}
+
+void
+qnetd_log_set_priority_bump(int enabled)
+{
+
+       qnetd_log_config_priority_bump = enabled;
+}
+
+void
+qnetd_log_msg_decode_error(int ret)
+{
+
+       switch (ret) {
+       case -1:
+               qnetd_log(LOG_WARNING, "Received message with option with invalid length");
+               break;
+       case -2:
+               qnetd_log(LOG_CRIT, "Can't allocate memory");
+               break;
+       case -3:
+               qnetd_log(LOG_WARNING, "Received inconsistent msg (tlv len > msg size)");
+               break;
+       case -4:
+               qnetd_log(LOG_WARNING, "Received message with option with invalid value");
+               break;
+       default:
+               qnetd_log(LOG_ERR, "Unknown error occurred when decoding message");
+               break;
+       }
+}
diff --git a/qdevices/qnetd-log.h b/qdevices/qnetd-log.h
new file mode 100644 (file)
index 0000000..6d94775
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_LOG_H_
+#define _QNETD_LOG_H_
+
+#include <syslog.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define QNETD_LOG_TARGET_STDERR                1
+#define QNETD_LOG_TARGET_SYSLOG                2
+
+#define qnetd_log(...) qnetd_log_printf(__VA_ARGS__)
+#define qnetd_log_nss(priority, str) qnetd_log_printf(priority, "%s (%d): %s", \
+    str, PR_GetError(), PR_ErrorToString(PR_GetError(), PR_LANGUAGE_I_DEFAULT));
+
+#define qnetd_log_err(priority, str) qnetd_log_printf(priority, "%s (%d): %s", \
+    str, errno, strerror(errno))
+
+extern void            qnetd_log_init(int target);
+
+extern void            qnetd_log_printf(int priority, const char *format, ...)
+    __attribute__((__format__(__printf__, 2, 3)));
+
+extern void            qnetd_log_vprintf(int priority, const char *format, va_list ap)
+    __attribute__((__format__(__printf__, 2, 0)));
+
+extern void            qnetd_log_close(void);
+
+extern void            qnetd_log_set_debug(int enabled);
+
+extern void            qnetd_log_set_priority_bump(int enabled);
+
+extern void            qnetd_log_msg_decode_error(int ret);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_LOG_H_ */
diff --git a/qdevices/qnetd-poll-array-user-data.h b/qdevices/qnetd-poll-array-user-data.h
new file mode 100644 (file)
index 0000000..336e41f
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_POLL_ARRAY_USER_DATA_H_
+#define _QNETD_POLL_ARRAY_USER_DATA_H_
+
+#include "qnetd-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum qnetd_poll_array_user_data_type {
+       QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET,
+       QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT,
+       QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET,
+       QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT,
+};
+
+struct qnetd_poll_array_user_data {
+       enum qnetd_poll_array_user_data_type type;
+       struct qnetd_client *client;
+       struct unix_socket_client *ipc_client;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_POLL_ARRAY_USER_DATA_H_ */
diff --git a/qdevices/send-buffer-list.c b/qdevices/send-buffer-list.c
new file mode 100644 (file)
index 0000000..4ff3261
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "send-buffer-list.h"
+
+void
+send_buffer_list_init(struct send_buffer_list *sblist, size_t max_list_entries,
+    size_t max_buffer_size)
+{
+
+       memset(sblist, 0, sizeof(*sblist));
+
+       sblist->max_list_entries = max_list_entries;
+       sblist->allocated_list_entries = 0;
+       sblist->max_buffer_size = max_buffer_size;
+       TAILQ_INIT(&sblist->list);
+       TAILQ_INIT(&sblist->free_list);
+}
+
+struct send_buffer_list_entry *
+send_buffer_list_get_new(struct send_buffer_list *sblist)
+{
+       struct send_buffer_list_entry *entry;
+
+       if (!TAILQ_EMPTY(&sblist->free_list)) {
+               /*
+                * Use free list entry
+                */
+               entry = TAILQ_FIRST(&sblist->free_list);
+               TAILQ_REMOVE(&sblist->free_list, entry, entries);
+
+               dynar_clean(&entry->buffer);
+               dynar_set_max_size(&entry->buffer, sblist->max_buffer_size);
+       } else {
+               if (sblist->allocated_list_entries + 1 > sblist->max_list_entries) {
+                       return (NULL);
+               }
+
+               sblist->allocated_list_entries++;
+
+               /*
+                * Alloc new entry
+                */
+               entry = malloc(sizeof(*entry));
+               if (entry == NULL) {
+                       return (NULL);
+               }
+
+               dynar_init(&entry->buffer, sblist->max_buffer_size);
+       }
+
+       entry->msg_already_sent_bytes = 0;
+
+       return (entry);
+}
+
+void
+send_buffer_list_put(struct send_buffer_list *sblist, struct send_buffer_list_entry *sblist_entry)
+{
+
+       TAILQ_INSERT_TAIL(&sblist->list, sblist_entry, entries);
+}
+
+void
+send_buffer_list_discard_new(struct send_buffer_list *sblist, struct send_buffer_list_entry *sblist_entry)
+{
+
+       TAILQ_INSERT_HEAD(&sblist->free_list, sblist_entry, entries);
+}
+
+struct send_buffer_list_entry *
+send_buffer_list_get_active(const struct send_buffer_list *sblist)
+{
+       struct send_buffer_list_entry *entry;
+
+       entry = TAILQ_FIRST(&sblist->list);
+
+       return (entry);
+}
+
+void
+send_buffer_list_delete(struct send_buffer_list *sblist,
+    struct send_buffer_list_entry *sblist_entry)
+{
+
+       /*
+        * Move item to free list
+        */
+       TAILQ_REMOVE(&sblist->list, sblist_entry, entries);
+       TAILQ_INSERT_HEAD(&sblist->free_list, sblist_entry, entries);
+}
+
+int
+send_buffer_list_empty(const struct send_buffer_list *sblist)
+{
+
+       return (TAILQ_EMPTY(&sblist->list));
+}
+
+void
+send_buffer_list_free(struct send_buffer_list *sblist)
+{
+       struct send_buffer_list_entry *entry;
+       struct send_buffer_list_entry *entry_next;
+
+       entry = TAILQ_FIRST(&sblist->list);
+
+       while (entry != NULL) {
+               entry_next = TAILQ_NEXT(entry, entries);
+
+               dynar_destroy(&entry->buffer);
+               free(entry);
+
+               entry = entry_next;
+       }
+
+       entry = TAILQ_FIRST(&sblist->free_list);
+
+       while (entry != NULL) {
+               entry_next = TAILQ_NEXT(entry, entries);
+
+               dynar_destroy(&entry->buffer);
+               free(entry);
+
+               entry = entry_next;
+       }
+
+       sblist->allocated_list_entries = 0;
+       TAILQ_INIT(&sblist->list);
+       TAILQ_INIT(&sblist->free_list);
+}
+
+void
+send_buffer_list_set_max_buffer_size(struct send_buffer_list *sblist, size_t max_buffer_size)
+{
+
+       sblist->max_buffer_size = max_buffer_size;
+}
+
+void
+send_buffer_list_set_max_list_entries(struct send_buffer_list *sblist, size_t max_list_entries)
+{
+
+       sblist->max_list_entries = max_list_entries;
+}
diff --git a/qdevices/send-buffer-list.h b/qdevices/send-buffer-list.h
new file mode 100644 (file)
index 0000000..f204b9d
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SEND_BUFFER_LIST_H_
+#define _SEND_BUFFER_LIST_H_
+
+#include <sys/queue.h>
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct send_buffer_list_entry {
+       struct dynar buffer;
+       size_t msg_already_sent_bytes;
+
+       TAILQ_ENTRY(send_buffer_list_entry) entries;
+};
+
+struct send_buffer_list {
+       size_t max_list_entries;
+       size_t allocated_list_entries;
+
+       size_t max_buffer_size;
+
+       TAILQ_HEAD(, send_buffer_list_entry) list;
+       TAILQ_HEAD(, send_buffer_list_entry) free_list;
+};
+
+extern void                             send_buffer_list_init(struct send_buffer_list *sblist,
+    size_t max_list_entries, size_t max_buffer_size);
+
+extern struct send_buffer_list_entry   *send_buffer_list_get_new(struct send_buffer_list *sblist);
+
+extern void                             send_buffer_list_put(struct send_buffer_list *sblist,
+    struct send_buffer_list_entry *sblist_entry);
+
+extern void                             send_buffer_list_discard_new(
+    struct send_buffer_list *sblist, struct send_buffer_list_entry *sblist_entry);
+
+extern struct send_buffer_list_entry   *send_buffer_list_get_active(
+    const struct send_buffer_list *sblist);
+
+extern void                             send_buffer_list_delete(struct send_buffer_list *sblist,
+    struct send_buffer_list_entry *sblist_entry);
+
+extern int                              send_buffer_list_empty(
+    const struct send_buffer_list *sblist);
+
+extern void                             send_buffer_list_free(struct send_buffer_list *sblist);
+
+extern void                             send_buffer_list_set_max_buffer_size(
+    struct send_buffer_list *sblist, size_t max_buffer_size);
+
+extern void                             send_buffer_list_set_max_list_entries(
+    struct send_buffer_list *sblist, size_t max_list_entries);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SEND_BUFFER_LIST_H_ */
diff --git a/qdevices/test-dynar-getopt-lex.c b/qdevices/test-dynar-getopt-lex.c
new file mode 100644 (file)
index 0000000..9c702da
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "dynar-getopt-lex.h"
+
+int
+main(void)
+{
+       struct dynar input_str;
+       struct dynar_getopt_lex lex;
+
+       dynar_init(&input_str, 128);
+
+       assert(dynar_str_catf(&input_str, "option=value") != -1);
+       dynar_getopt_lex_init(&lex, &input_str);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "option") == 0);
+       assert(strcmp(dynar_data(&lex.value), "value") == 0);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "") == 0);
+       assert(strcmp(dynar_data(&lex.value), "") == 0);
+       dynar_getopt_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "option1=value1,option2=value2") != -1);
+       dynar_getopt_lex_init(&lex, &input_str);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "option1") == 0);
+       assert(strcmp(dynar_data(&lex.value), "value1") == 0);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "option2") == 0);
+       assert(strcmp(dynar_data(&lex.value), "value2") == 0);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "") == 0);
+       assert(strcmp(dynar_data(&lex.value), "") == 0);
+       dynar_getopt_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "option1=value1,option2=value2,option3=value3") != -1);
+       dynar_getopt_lex_init(&lex, &input_str);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "option1") == 0);
+       assert(strcmp(dynar_data(&lex.value), "value1") == 0);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "option2") == 0);
+       assert(strcmp(dynar_data(&lex.value), "value2") == 0);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "option3") == 0);
+       assert(strcmp(dynar_data(&lex.value), "value3") == 0);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "") == 0);
+       assert(strcmp(dynar_data(&lex.value), "") == 0);
+       dynar_getopt_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "option1,option2=value2") != -1);
+       dynar_getopt_lex_init(&lex, &input_str);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "option1") == 0);
+       assert(strcmp(dynar_data(&lex.value), "") == 0);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "option2") == 0);
+       assert(strcmp(dynar_data(&lex.value), "value2") == 0);
+       assert(dynar_getopt_lex_token_next(&lex) == 0);
+       assert(strcmp(dynar_data(&lex.option), "") == 0);
+       assert(strcmp(dynar_data(&lex.value), "") == 0);
+       dynar_getopt_lex_destroy(&lex);
+
+       dynar_destroy(&input_str);
+
+       return (0);
+}
diff --git a/qdevices/test-dynar-simple-lex.c b/qdevices/test-dynar-simple-lex.c
new file mode 100644 (file)
index 0000000..bbccf79
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include "dynar.h"
+#include "dynar-str.h"
+#include "dynar-simple-lex.h"
+
+int
+main(void)
+{
+       struct dynar input_str;
+       struct dynar_simple_lex lex;
+       struct dynar *output_str_ptr;
+       struct dynar output_str;
+       const char *cstr;
+
+       dynar_init(&input_str, 128);
+
+       assert(dynar_str_catf(&input_str, "token1 token2") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "token1") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "token2") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "      token1                    token2               ") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "token1") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "token2") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "      token1                 token2          \ntoken3") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "token1") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "token2") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "\\ab\\cd e\\fg\\ h i\\") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "\\ab\\cd") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "e\\fg\\") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "h") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "i\\") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, " a b\rc") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "a") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "b") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "\\ab\\\\cd e\\fg\\ h i\\") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_BACKSLASH);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "ab\\cd") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "efg h") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "i") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "ab\\\\cd e\\fg\\ h ij\\\na") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_BACKSLASH);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "ab\\cd") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "efg h") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "ij") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, " a b\\\rc") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_BACKSLASH);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "a") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "b") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "abc def \"ghi\" jkl \"m n    o\"") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "abc") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "def") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "ghi") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "jkl") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "m n  o") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "a\\bc \"d\\e \\\"f\\\\  \\\"  \"g hij") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "abc") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "d\\e \"f\\  \"  g") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "hij") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "abc \"d e  \r\n") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "abc") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "d e  ") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       assert(dynar_str_cpy(&input_str, "") == 0);
+       assert(dynar_str_catf(&input_str, "abc \"d e  \\\"\\\r\n") != -1);
+       dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "abc") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "d e  \"") == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       dynar_init(&output_str, 128);
+       assert(dynar_str_quote_cpy(&output_str, "abcd") == 0);
+       assert(dynar_size(&output_str) == 6);
+       assert(memcmp(dynar_data(&output_str), "\"abcd\"", dynar_size(&output_str)) == 0);
+       assert(dynar_str_cat(&output_str, " ") == 0);
+       assert(dynar_str_quote_cat(&output_str, "abcd") == 0);
+       assert(dynar_size(&output_str) == 13);
+       assert(memcmp(dynar_data(&output_str), "\"abcd\" \"abcd\"", dynar_size(&output_str)) == 0);
+
+       assert(dynar_str_quote_cpy(&output_str, "ab\\cd") == 0);
+       assert(dynar_size(&output_str) == 8);
+       assert(memcmp(dynar_data(&output_str), "\"ab\\\\cd\"", dynar_size(&output_str)) == 0);
+
+       assert(dynar_str_quote_cpy(&output_str, "ab\\\\cd") == 0);
+       assert(dynar_size(&output_str) == 10);
+       assert(memcmp(dynar_data(&output_str), "\"ab\\\\\\\\cd\"", dynar_size(&output_str)) == 0);
+
+       assert(dynar_str_quote_cpy(&output_str, "ab cd \\\"e") == 0);
+       assert(dynar_size(&output_str) == 13);
+       assert(memcmp(dynar_data(&output_str), "\"ab cd \\\\\\\"e\"", dynar_size(&output_str)) == 0);
+
+       cstr = "ab cd \\ ef\\g h\"i";
+       assert(dynar_str_quote_cpy(&output_str, cstr) == 0);
+       dynar_simple_lex_init(&lex, &output_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strlen(cstr) == dynar_size(output_str_ptr) - 1);
+       assert(memcmp(cstr, dynar_data(output_str_ptr), strlen(cstr)) == 0);
+       assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL);
+       assert(strcmp(dynar_data(output_str_ptr), "") == 0);
+       dynar_simple_lex_destroy(&lex);
+
+       dynar_destroy(&input_str);
+       dynar_destroy(&output_str);
+
+       return (0);
+}
diff --git a/qdevices/test-dynar.c b/qdevices/test-dynar.c
new file mode 100644 (file)
index 0000000..be11639
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include "dynar.h"
+#include "dynar-str.h"
+
+int
+main(void)
+{
+       struct dynar str;
+
+       dynar_init(&str, 3);
+       assert(dynar_cat(&str, "a", 1) == 0);
+       assert(dynar_size(&str) == 1);
+       assert(dynar_cat(&str, "b", 1) == 0);
+       assert(dynar_size(&str) == 2);
+       assert(dynar_cat(&str, "c", 1) == 0);
+       assert(dynar_size(&str) == 3);
+       assert(dynar_cat(&str, "d", 1) != 0);
+       assert(memcmp(dynar_data(&str), "abc", 3) == 0);
+
+       dynar_clean(&str);
+       assert(dynar_size(&str) == 0);
+       dynar_set_max_size(&str, 4);
+       assert(dynar_cat(&str, "a", 1) == 0);
+       assert(dynar_size(&str) == 1);
+       assert(memcmp(dynar_data(&str), "a", 1) == 0);
+       assert(dynar_cat(&str, "b", 1) == 0);
+       assert(dynar_size(&str) == 2);
+       assert(dynar_cat(&str, "c", 1) == 0);
+       assert(dynar_size(&str) == 3);
+       assert(dynar_cat(&str, "d", 1) == 0);
+       assert(dynar_size(&str) == 4);
+       assert(memcmp(dynar_data(&str), "abcd", 4) == 0);
+
+       assert(dynar_str_cpy(&str, "e") == 0);
+       assert(dynar_size(&str) == 1);
+       assert(memcmp(dynar_data(&str), "e", 1) == 0);
+
+       assert(dynar_str_cpy(&str, "fgh") == 0);
+       assert(dynar_size(&str) == 3);
+       assert(memcmp(dynar_data(&str), "fgh", 3) == 0);
+
+       assert(dynar_str_cpy(&str, "fghi") == 0);
+       assert(dynar_size(&str) == 4);
+       assert(memcmp(dynar_data(&str), "fghi", 4) == 0);
+
+       assert(dynar_str_cpy(&str, "fghij") != 0);
+       assert(dynar_size(&str) == 4);
+       assert(dynar_str_cat(&str, "a") != 0);
+
+       assert(dynar_str_cpy(&str, "") == 0);
+       assert(dynar_size(&str) == 0);
+
+       assert(dynar_str_cat(&str, "a") == 0);
+       assert(dynar_size(&str) == 1);
+       assert(memcmp(dynar_data(&str), "a", 1) == 0);
+
+       assert(dynar_str_cat(&str, "b") == 0);
+       assert(dynar_size(&str) == 2);
+       assert(memcmp(dynar_data(&str), "ab", 2) == 0);
+
+       assert(dynar_str_cat(&str, "cd") == 0);
+       assert(dynar_size(&str) == 4);
+       assert(memcmp(dynar_data(&str), "abcb", 1) == 0);
+
+       assert(dynar_str_cpy(&str, "") == 0);
+       assert(dynar_str_catf(&str, "%s", "a") == 1);
+       assert(memcmp(dynar_data(&str), "a", 1) == 0);
+       assert(dynar_str_catf(&str, "%s", "ab") == 2);
+       assert(memcmp(dynar_data(&str), "aab", 3) == 0);
+       assert(dynar_str_cpy(&str, "") == 0);
+       assert(dynar_str_catf(&str, "%s", "abc") == 3);
+       assert(dynar_str_cpy(&str, "") == 0);
+       assert(dynar_str_catf(&str, "%s", "abcd") == -1);
+       assert(dynar_str_cpy(&str, "a") == 0);
+       assert(dynar_str_catf(&str, "%s", "") == 0);
+       assert(memcmp(dynar_data(&str), "a", 1) == 0);
+
+       dynar_destroy(&str);
+       dynar_init(&str, 5);
+       assert(dynar_str_catf(&str, "%s", "abcd") == 4);
+       dynar_destroy(&str);
+       dynar_init(&str, 5);
+       assert(dynar_str_catf(&str, "%s", "") == 0);
+       assert(dynar_str_catf(&str, "%s", "abc") == 3);
+       assert(dynar_str_catf(&str, "%s", "d") == 1);
+       assert(memcmp(dynar_data(&str), "abcd", 4) == 0);
+       dynar_destroy(&str);
+
+       dynar_init(&str, 10);
+       assert(dynar_str_cat(&str, "abcd") == 0);
+       assert(memcmp(dynar_data(&str), "abcd", 4) == 0);
+       assert(dynar_str_prepend(&str, "e") == 0);
+       assert(dynar_size(&str) == 5);
+       assert(memcmp(dynar_data(&str), "eabcd", 5) == 0);
+       assert(dynar_str_prepend(&str, "fgh") == 0);
+       assert(dynar_size(&str) == 8);
+       assert(memcmp(dynar_data(&str), "fgheabcd", 8) == 0);
+       assert(dynar_str_prepend(&str, "ijk") != 0);
+       assert(dynar_size(&str) == 8);
+       assert(dynar_str_prepend(&str, "ij") == 0);
+       assert(dynar_size(&str) == 10);
+       assert(memcmp(dynar_data(&str), "ijfgheabcd", 10) == 0);
+       dynar_destroy(&str);
+
+       dynar_init(&str, 10);
+       assert(dynar_str_cat(&str, "abcd") == 0);
+       assert(memcmp(dynar_data(&str), "abcd", 4) == 0);
+       assert(dynar_str_prepend(&str, "ef") == 0);
+       assert(dynar_size(&str) == 6);
+       assert(memcmp(dynar_data(&str), "efabcd", 6) == 0);
+       assert(dynar_str_cat(&str, "ij") == 0);
+       assert(dynar_size(&str) == 8);
+       assert(memcmp(dynar_data(&str), "efabcdij", 8) == 0);
+       assert(dynar_str_prepend(&str, "k") == 0);
+       assert(dynar_size(&str) == 9);
+       assert(memcmp(dynar_data(&str), "kefabcdij", 9) == 0);
+       assert(dynar_str_cat(&str, "l") == 0);
+       assert(dynar_size(&str) == 10);
+       assert(memcmp(dynar_data(&str), "kefabcdijl", 10) == 0);
+       dynar_destroy(&str);
+
+       return (0);
+}
diff --git a/qdevices/test-process-list.c b/qdevices/test-process-list.c
new file mode 100644 (file)
index 0000000..57c667f
--- /dev/null
@@ -0,0 +1,517 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <poll.h>
+#include <signal.h>
+
+#include "process-list.h"
+
+static int no_executed;
+static int no_finished;
+
+static void
+signal_handlers_register(void)
+{
+       struct sigaction act;
+
+       act.sa_handler = SIG_DFL;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGCHLD, &act, NULL);
+
+       act.sa_handler = SIG_IGN;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = SA_RESTART;
+
+       sigaction(SIGPIPE, &act, NULL);
+}
+
+static void
+plist_notify(enum process_list_notify_reason reason, const struct process_list_entry *entry,
+    void *user_data)
+{
+
+       assert(user_data == (void *)0x42);
+
+       switch (reason) {
+       case PROCESS_LIST_NOTIFY_REASON_EXECUTED:
+               no_executed++;
+               break;
+       case PROCESS_LIST_NOTIFY_REASON_FINISHED:
+               no_finished++;
+               break;
+       }
+}
+
+int
+main(void)
+{
+       struct process_list plist;
+       struct process_list_entry *plist_entry;
+       int i;
+       int timeout;
+       int no_repeats;
+
+       signal_handlers_register();
+
+       process_list_init(&plist, 10, 1, plist_notify, (void *)0x42);
+       plist_entry = process_list_add(&plist, "test name", "command");
+       assert(plist_entry != NULL);
+       assert(strcmp(plist_entry->name, "test name") == 0);
+       assert(plist_entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED);
+       assert(plist_entry->exec_argc == 1);
+       assert(plist_entry->exec_argv[0] != NULL && strcmp(plist_entry->exec_argv[0], "command") == 0);
+       assert(plist_entry->exec_argv[1] == NULL);
+
+       plist_entry = process_list_add(&plist, "test name", "/bin/ping -c \"host wit\\\"h  space\"   notaspace");
+       assert(plist_entry != NULL);
+       assert(strcmp(plist_entry->name, "test name") == 0);
+       assert(plist_entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED);
+       assert(plist_entry->exec_argc == 4);
+       assert(plist_entry->exec_argv[0] != NULL && strcmp(plist_entry->exec_argv[0], "/bin/ping") == 0);
+       assert(plist_entry->exec_argv[1] != NULL && strcmp(plist_entry->exec_argv[1], "-c") == 0);
+       assert(plist_entry->exec_argv[2] != NULL && strcmp(plist_entry->exec_argv[2], "host wit\"h  space") == 0);
+       assert(plist_entry->exec_argv[3] != NULL && strcmp(plist_entry->exec_argv[3], "notaspace") == 0);
+       assert(plist_entry->exec_argv[4] == NULL);
+
+       process_list_free(&plist);
+
+       /*
+        * Test no process
+        */
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 0);
+       assert(process_list_get_no_running(&plist) == 0);
+
+       /*
+        * Wait to exit
+        */
+       no_repeats = 10;
+       timeout = 1000 / no_repeats;
+       for (i = 0; i < no_repeats; i++) {
+               assert(process_list_waitpid(&plist) == 0);
+               if (process_list_get_no_running(&plist) > 0) {
+                       poll(NULL, 0, timeout);
+               }
+       }
+
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_no_running(&plist) == 0);
+       assert(no_finished == 0);
+       assert(process_list_get_summary_result(&plist) == 0);
+       assert(process_list_get_summary_result_short(&plist) == 0);
+
+       process_list_free(&plist);
+
+       /*
+        * Test two processes. /bin/true and /bin/false. Accumulated result should be fail
+        */
+       plist_entry = process_list_add(&plist, "true", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "false", "/bin/false");
+       assert(plist_entry != NULL);
+
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 2);
+       assert(process_list_get_no_running(&plist) == 2);
+
+       /*
+        * Wait to exit
+        */
+       no_repeats = 10;
+       timeout = 1000 / no_repeats;
+       for (i = 0; i < no_repeats; i++) {
+               assert(process_list_waitpid(&plist) == 0);
+               if (process_list_get_no_running(&plist) > 0) {
+                       poll(NULL, 0, timeout);
+               }
+       }
+
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_no_running(&plist) == 0);
+       assert(no_finished == 2);
+       assert(process_list_get_summary_result(&plist) == 1);
+       assert(process_list_get_summary_result_short(&plist) == 1);
+
+       process_list_free(&plist);
+
+       /*
+        * Test two processes. /bin/true and one non-existing. Accumulated result should be fail
+        */
+       plist_entry = process_list_add(&plist, "true", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "false", "/nonexistingdir/nonexistingfile");
+       assert(plist_entry != NULL);
+
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 2);
+       assert(process_list_get_no_running(&plist) == 2);
+
+       /*
+        * Wait to exit
+        */
+       no_repeats = 10;
+       timeout = 1000 / no_repeats;
+       for (i = 0; i < no_repeats; i++) {
+               assert(process_list_waitpid(&plist) == 0);
+               if (process_list_get_no_running(&plist) > 0) {
+                       poll(NULL, 0, timeout);
+               }
+       }
+
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_no_running(&plist) == 0);
+       assert(no_finished == 2);
+       assert(process_list_get_summary_result(&plist) == 1);
+       assert(process_list_get_summary_result_short(&plist) == 1);
+
+       process_list_free(&plist);
+
+       /*
+        * Test three processes /bin/true. Accumulated result should be success.
+        */
+       plist_entry = process_list_add(&plist, "true", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true2", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true3", "/bin/true");
+       assert(plist_entry != NULL);
+
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 3);
+       assert(process_list_get_no_running(&plist) == 3);
+
+       /*
+        * Wait to exit
+        */
+       no_repeats = 10;
+       timeout = 1000 / no_repeats;
+       for (i = 0; i < no_repeats; i++) {
+               assert(process_list_waitpid(&plist) == 0);
+               if (process_list_get_no_running(&plist) > 0) {
+                       poll(NULL, 0, timeout);
+               }
+       }
+
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_no_running(&plist) == 0);
+       assert(no_finished == 3);
+       assert(process_list_get_summary_result(&plist) == 0);
+       assert(process_list_get_summary_result_short(&plist) == 0);
+
+       process_list_free(&plist);
+
+       /*
+        * Test two processes. /bin/true and cat. Waiting for maximum of 2 sec
+        */
+       plist_entry = process_list_add(&plist, "true", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "cat", "/bin/cat /dev/zero");
+       assert(plist_entry != NULL);
+
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 2);
+       assert(process_list_get_no_running(&plist) == 2);
+
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+
+       assert(process_list_get_no_running(&plist) == 1);
+       assert(no_finished == 1);
+       assert(process_list_get_summary_result(&plist) == -1);
+       assert(process_list_get_summary_result_short(&plist) == -1);
+
+       process_list_move_active_entries_to_kill_list(&plist);
+       assert(process_list_process_kill_list(&plist) == 0);
+       poll(NULL, 0, 500);
+       assert(process_list_process_kill_list(&plist) == 0);
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+
+       assert(process_list_get_kill_list_items(&plist) == 0);
+
+       assert(process_list_process_kill_list(&plist) == 0);
+
+       process_list_free(&plist);
+
+       /*
+        * Test two bash proceses. One ignores INT and second ignores INT and TERM. Waiting for maximum of 2 sec
+        */
+       plist_entry = process_list_add(&plist, "ignoresig1", "bash -c \"trap 'echo trap' SIGINT;while true;do sleep 1;done\"");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "ignoresig2", "bash -c \"trap 'echo trap' SIGINT SIGTERM;while true;do sleep 1;done\"");
+       assert(plist_entry != NULL);
+
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 2);
+       assert(process_list_get_no_running(&plist) == 2);
+
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+
+       assert(process_list_get_no_running(&plist) == 2);
+       assert(no_finished == 0);
+       assert(process_list_get_summary_result(&plist) == -1);
+       assert(process_list_get_summary_result_short(&plist) == -1);
+
+       process_list_move_active_entries_to_kill_list(&plist);
+       assert(process_list_process_kill_list(&plist) == 0);
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_kill_list_items(&plist) == 1);
+
+       assert(process_list_process_kill_list(&plist) == 0);
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_kill_list_items(&plist) == 0);
+
+       process_list_free(&plist);
+
+       /*
+        * Test 3 processes. Test if entries are properly deallocated
+        */
+       process_list_init(&plist, 3, 1, plist_notify, (void *)0x42);
+       plist_entry = process_list_add(&plist, "true", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true2", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true3", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true4", "/bin/true");
+       assert(plist_entry == NULL);
+
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 3);
+       assert(process_list_get_no_running(&plist) == 3);
+
+       /*
+        * Wait to exit
+        */
+       no_repeats = 10;
+       timeout = 1000 / no_repeats;
+       for (i = 0; i < no_repeats; i++) {
+               assert(process_list_waitpid(&plist) == 0);
+               if (process_list_get_no_running(&plist) > 0) {
+                       poll(NULL, 0, timeout);
+               }
+       }
+
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_no_running(&plist) == 0);
+       assert(no_finished == 3);
+       assert(process_list_get_summary_result(&plist) == 0);
+       assert(process_list_get_summary_result_short(&plist) == 0);
+
+       process_list_move_active_entries_to_kill_list(&plist);
+
+       plist_entry = process_list_add(&plist, "true", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "ignoresig1", "bash -c \"trap 'echo trap' SIGINT;while true;do sleep 1;done\"");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "ignoresig2", "bash -c \"trap 'echo trap' SIGINT SIGTERM;while true;do sleep 1;done\"");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true4", "/bin/true");
+       assert(plist_entry == NULL);
+
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 3);
+       assert(process_list_get_no_running(&plist) == 3);
+
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+
+       assert(process_list_get_no_running(&plist) == 2);
+       assert(no_finished == 1);
+       assert(process_list_get_summary_result(&plist) == -1);
+       assert(process_list_get_summary_result_short(&plist) == -1);
+
+       plist_entry = process_list_add(&plist, "true4", "/bin/true");
+       assert(plist_entry == NULL);
+
+       process_list_move_active_entries_to_kill_list(&plist);
+
+       plist_entry = process_list_add(&plist, "true4", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true5", "/bin/true");
+       assert(plist_entry == NULL);
+
+       assert(process_list_process_kill_list(&plist) == 0);
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_kill_list_items(&plist) == 1);
+
+       assert(process_list_process_kill_list(&plist) == 0);
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_kill_list_items(&plist) == 0);
+
+       process_list_move_active_entries_to_kill_list(&plist);
+       assert(process_list_get_summary_result(&plist) == 0);
+       assert(process_list_get_summary_result_short(&plist) == 0);
+
+       plist_entry = process_list_add(&plist, "true", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true2", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true3", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true4", "/bin/true");
+       assert(plist_entry == NULL);
+
+       process_list_free(&plist);
+
+       /*
+        * Test 3 processes and difference between summary and short-circuit summary
+        */
+       process_list_init(&plist, 3, 1, plist_notify, (void *)0x42);
+       plist_entry = process_list_add(&plist, "true", "/bin/true");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "false", "/bin/false");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "loop", "bash -c \"while true;do sleep 1;done\"");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "true4", "/bin/true");
+       assert(plist_entry == NULL);
+
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 3);
+       assert(process_list_get_no_running(&plist) == 3);
+
+       /*
+        * Wait to exit
+        */
+       no_repeats = 10;
+       timeout = 1000 / no_repeats;
+       for (i = 0; i < no_repeats; i++) {
+               assert(process_list_waitpid(&plist) == 0);
+               if (process_list_get_no_running(&plist) > 0) {
+                       poll(NULL, 0, timeout);
+               }
+       }
+
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_no_running(&plist) == 1);
+       assert(no_finished == 2);
+       assert(process_list_get_summary_result(&plist) == -1);
+       assert(process_list_get_summary_result_short(&plist) == 1);
+
+       process_list_move_active_entries_to_kill_list(&plist);
+       assert(process_list_process_kill_list(&plist) == 0);
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+       assert(process_list_get_kill_list_items(&plist) == 0);
+
+       process_list_free(&plist);
+
+       /*
+        * Test process_list_killall by running two bash proceses.
+        * One ignores INT and second ignores INT and TERM. Waiting for maximum of 2 sec
+        */
+       plist_entry = process_list_add(&plist, "ignoresig1", "bash -c \"trap 'echo trap' SIGINT;while true;do sleep 1;done\"");
+       assert(plist_entry != NULL);
+
+       plist_entry = process_list_add(&plist, "ignoresig2", "bash -c \"trap 'echo trap' SIGINT SIGTERM;while true;do sleep 1;done\"");
+       assert(plist_entry != NULL);
+
+       no_executed = 0;
+       no_finished = 0;
+       assert(process_list_exec_initialized(&plist) == 0);
+       assert(no_executed == 2);
+       assert(process_list_get_no_running(&plist) == 2);
+
+       poll(NULL, 0, 500);
+       assert(process_list_waitpid(&plist) == 0);
+
+       assert(process_list_get_no_running(&plist) == 2);
+       assert(no_finished == 0);
+       assert(process_list_get_summary_result(&plist) == -1);
+       assert(process_list_get_summary_result_short(&plist) == -1);
+
+       assert(process_list_killall(&plist, 2000) == 0);
+       assert(process_list_get_kill_list_items(&plist) == 0);
+
+       process_list_free(&plist);
+
+       /*
+        * Empty killall exits with sucess result
+        */
+       assert(process_list_killall(&plist, 2000) == 0);
+
+       process_list_free(&plist);
+
+       return (0);
+}
diff --git a/qdevices/test-qnetd-cluster-list.c b/qdevices/test-qnetd-cluster-list.c
new file mode 100644 (file)
index 0000000..ebbebcb
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include "qnetd-cluster-list.h"
+#include "qnetd-client.h"
+#include "qnetd-client-list.h"
+
+static struct qnetd_client_list clients;
+static struct qnetd_cluster_list clusters;
+
+static void
+add_client(const char *cluster_name, size_t cluster_name_len,
+    struct qnetd_client **client, struct qnetd_cluster **cluster)
+{
+       PRNetAddr addr;
+       struct qnetd_client *tmp_client;
+       struct qnetd_cluster *tmp_cluster;
+       char *client_addr_str;
+
+       memset(&addr, 0, sizeof(addr));
+
+       client_addr_str = strdup("addrstr");
+       assert(client_addr_str != NULL);
+
+       tmp_client = qnetd_client_list_add(&clients, NULL, &addr, client_addr_str, 1000, 2, 1000, NULL);
+       assert(tmp_client != NULL);
+       tmp_client->cluster_name = malloc(cluster_name_len + 1);
+       assert(tmp_client->cluster_name != NULL);
+       memcpy(tmp_client->cluster_name, cluster_name, cluster_name_len);
+       tmp_client->cluster_name_len = cluster_name_len;
+
+       tmp_cluster = qnetd_cluster_list_add_client(&clusters, tmp_client);
+       assert(cluster != NULL);
+       tmp_client->cluster = tmp_cluster;
+
+       *client = tmp_client;
+       *cluster = tmp_cluster;
+}
+
+static int
+no_clients_in_cluster(struct qnetd_cluster *cluster)
+{
+       int i;
+       struct qnetd_client *client;
+
+       i = 0;
+
+       TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) {
+               i++;
+       }
+
+       return (i);
+}
+
+static int
+no_clusters(void)
+{
+       int i;
+       struct qnetd_cluster *cluster;
+
+       i = 0;
+
+       TAILQ_FOREACH(cluster, &clusters, entries) {
+               i++;
+       }
+
+       return (i);
+}
+
+static int
+is_client_in_cluster(struct qnetd_cluster *cluster, const struct qnetd_client *client)
+{
+       struct qnetd_client *tmp_client;
+
+       TAILQ_FOREACH(tmp_client, &cluster->client_list, cluster_entries) {
+               if (tmp_client == client) {
+                       return (1);
+               }
+       }
+
+       return (0);
+}
+
+static void
+del_client(struct qnetd_client *client)
+{
+
+       qnetd_cluster_list_del_client(&clusters, client->cluster, client);
+       qnetd_client_list_del(&clients, client);
+}
+
+int
+main(void)
+{
+       struct qnetd_client *client[4];
+       struct qnetd_cluster *cluster[4];
+       const char *cl_name;
+
+       qnetd_client_list_init(&clients);
+       qnetd_cluster_list_init(&clusters);
+
+       assert(no_clusters() == 0);
+
+       cl_name = "test_cluster";
+       add_client(cl_name, strlen(cl_name), &client[0], &cluster[0]);
+       assert(no_clusters() == 1);
+       add_client(cl_name, strlen(cl_name), &client[1], &cluster[1]);
+       assert(no_clusters() == 1);
+
+       cl_name = "cluster2";
+       add_client(cl_name, strlen(cl_name), &client[2], &cluster[2]);
+       assert(no_clusters() == 2);
+       add_client(cl_name, strlen(cl_name), &client[3], &cluster[3]);
+       assert(no_clusters() == 2);
+
+       assert(cluster[0] == cluster[1]);
+       assert(cluster[2] == cluster[3]);
+       assert(cluster[0] != cluster[2]);
+
+       assert(no_clients_in_cluster(cluster[0]) == 2);
+       assert(no_clients_in_cluster(cluster[2]) == 2);
+
+       assert(is_client_in_cluster(cluster[0], client[0]));
+       assert(is_client_in_cluster(client[0]->cluster, client[0]));
+       assert(is_client_in_cluster(cluster[0], client[1]));
+       assert(!is_client_in_cluster(cluster[0], client[2]));
+       assert(!is_client_in_cluster(cluster[0], client[3]));
+
+       assert(!is_client_in_cluster(cluster[2], client[0]));
+       assert(!is_client_in_cluster(cluster[2], client[1]));
+       assert(is_client_in_cluster(cluster[2], client[2]));
+       assert(is_client_in_cluster(cluster[2], client[3]));
+       assert(is_client_in_cluster(client[2]->cluster, client[2]));
+
+       del_client(client[0]);
+       assert(no_clusters() == 2);
+       assert(no_clients_in_cluster(cluster[0]) == 1);
+       assert(no_clients_in_cluster(cluster[2]) == 2);
+
+       assert(!is_client_in_cluster(cluster[0], client[0]));
+       assert(is_client_in_cluster(cluster[0], client[1]));
+       assert(!is_client_in_cluster(cluster[0], client[2]));
+       assert(!is_client_in_cluster(cluster[0], client[3]));
+
+       add_client(cl_name, strlen(cl_name), &client[0], &cluster[0]);
+       assert(no_clients_in_cluster(cluster[1]) == 1);
+       assert(no_clients_in_cluster(cluster[2]) == 3);
+
+       assert(!is_client_in_cluster(cluster[1], client[0]));
+       assert(is_client_in_cluster(cluster[1], client[1]));
+       assert(!is_client_in_cluster(cluster[1], client[2]));
+       assert(!is_client_in_cluster(cluster[1], client[3]));
+
+       assert(is_client_in_cluster(cluster[2], client[0]));
+       assert(!is_client_in_cluster(cluster[2], client[1]));
+       assert(is_client_in_cluster(cluster[2], client[2]));
+       assert(is_client_in_cluster(cluster[2], client[3]));
+
+       del_client(client[1]);
+       assert(no_clusters() == 1);
+
+       del_client(client[2]);
+       assert(no_clusters() == 1);
+
+       del_client(client[3]);
+       assert(no_clusters() == 1);
+
+       del_client(client[0]);
+       assert(no_clusters() == 0);
+
+       return (0);
+}
diff --git a/qdevices/timer-list.c b/qdevices/timer-list.c
new file mode 100644 (file)
index 0000000..b9b83ba
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "timer-list.h"
+
+void
+timer_list_init(struct timer_list *tlist)
+{
+
+       memset(tlist, 0, sizeof(*tlist));
+
+       TAILQ_INIT(&tlist->list);
+       TAILQ_INIT(&tlist->free_list);
+}
+
+static PRIntervalTime
+timer_list_entry_time_to_expire(const struct timer_list_entry *entry, PRIntervalTime current_time)
+{
+       PRIntervalTime diff, half_interval;
+
+       diff = entry->expire_time - current_time;
+       half_interval = ~0;
+       half_interval /= 2;
+
+       if (diff > half_interval) {
+               return (0);
+       }
+
+       return (diff);
+}
+
+static int
+timer_list_entry_cmp(const struct timer_list_entry *entry1,
+    const struct timer_list_entry *entry2, PRIntervalTime current_time)
+{
+       PRIntervalTime diff1, diff2;
+       int res;
+
+       diff1 = timer_list_entry_time_to_expire(entry1, current_time);
+       diff2 = timer_list_entry_time_to_expire(entry2, current_time);
+
+       res = 0;
+
+       if (diff1 < diff2) res = -1;
+       if (diff1 > diff2) res = 1;
+
+       return (res);
+}
+
+static void
+timer_list_insert_into_list(struct timer_list *tlist, struct timer_list_entry *new_entry)
+{
+       struct timer_list_entry *entry;
+
+       /*
+        * This can overflow and it's not a problem
+        */
+       new_entry->expire_time = new_entry->epoch + PR_MillisecondsToInterval(new_entry->interval);
+
+       entry = TAILQ_FIRST(&tlist->list);
+       while (entry != NULL) {
+               if (timer_list_entry_cmp(entry, new_entry, new_entry->epoch) > 0) {
+                       /*
+                        * Insert new entry right before current entry
+                        */
+                       TAILQ_INSERT_BEFORE(entry, new_entry, entries);
+
+                       break;
+               }
+
+               entry = TAILQ_NEXT(entry, entries);
+       }
+
+       if (entry == NULL) {
+               TAILQ_INSERT_TAIL(&tlist->list, new_entry, entries);
+       }
+}
+
+struct timer_list_entry *
+timer_list_add(struct timer_list *tlist, PRUint32 interval, timer_list_cb_fn func, void *data1,
+    void *data2)
+{
+       struct timer_list_entry *new_entry;
+
+       if (interval < 1 || interval > TIMER_LIST_MAX_INTERVAL) {
+               return (NULL);
+       }
+
+       if (!TAILQ_EMPTY(&tlist->free_list)) {
+               /*
+                * Use free list entry
+                */
+               new_entry = TAILQ_FIRST(&tlist->free_list);
+               TAILQ_REMOVE(&tlist->free_list, new_entry, entries);
+       } else {
+               /*
+                * Alloc new entry
+                */
+               new_entry = malloc(sizeof(*new_entry));
+               if (new_entry == NULL) {
+                       return (NULL);
+               }
+       }
+
+       memset(new_entry, 0, sizeof(*new_entry));
+       new_entry->epoch = PR_IntervalNow();
+       new_entry->interval = interval;
+       new_entry->func = func;
+       new_entry->user_data1 = data1;
+       new_entry->user_data2 = data2;
+       new_entry->is_active = 1;
+
+       timer_list_insert_into_list(tlist, new_entry);
+
+       return (new_entry);
+}
+
+void
+timer_list_reschedule(struct timer_list *tlist, struct timer_list_entry *entry)
+{
+
+       if (entry->is_active) {
+               entry->epoch = PR_IntervalNow();
+               TAILQ_REMOVE(&tlist->list, entry, entries);
+               timer_list_insert_into_list(tlist, entry);
+       }
+}
+
+void
+timer_list_expire(struct timer_list *tlist)
+{
+       PRIntervalTime now;
+       struct timer_list_entry *entry;
+       int res;
+
+       now = PR_IntervalNow();
+
+       while ((entry = TAILQ_FIRST(&tlist->list)) != NULL &&
+           timer_list_entry_time_to_expire(entry, now) == 0) {
+               /*
+                * Expired
+                */
+               res = entry->func(entry->user_data1, entry->user_data2);
+               if (res == 0) {
+                       /*
+                        * Move item to free list
+                        */
+                       timer_list_delete(tlist, entry);
+               } else if (entry->is_active) {
+                       /*
+                        * Schedule again
+                        */
+                       entry->epoch = now;
+                       TAILQ_REMOVE(&tlist->list, entry, entries);
+                       timer_list_insert_into_list(tlist, entry);
+               }
+       }
+}
+
+PRIntervalTime
+timer_list_time_to_expire(struct timer_list *tlist)
+{
+       struct timer_list_entry *entry;
+
+       entry = TAILQ_FIRST(&tlist->list);
+       if (entry == NULL) {
+               return (PR_INTERVAL_NO_TIMEOUT);
+       }
+
+       return (timer_list_entry_time_to_expire(entry, PR_IntervalNow()));
+}
+
+uint32_t
+timer_list_time_to_expire_ms(struct timer_list *tlist)
+{
+       struct timer_list_entry *entry;
+       uint32_t u32;
+
+       entry = TAILQ_FIRST(&tlist->list);
+       if (entry == NULL) {
+               u32 = ~((uint32_t)0);
+               return (u32);
+       }
+
+       return (PR_IntervalToMilliseconds(timer_list_entry_time_to_expire(entry, PR_IntervalNow())));
+}
+
+void
+timer_list_delete(struct timer_list *tlist, struct timer_list_entry *entry)
+{
+
+       if (entry->is_active) {
+               /*
+                * Move item to free list
+                */
+               TAILQ_REMOVE(&tlist->list, entry, entries);
+               TAILQ_INSERT_HEAD(&tlist->free_list, entry, entries);
+               entry->is_active = 0;
+       }
+}
+
+void
+timer_list_free(struct timer_list *tlist)
+{
+       struct timer_list_entry *entry;
+       struct timer_list_entry *entry_next;
+
+
+       entry = TAILQ_FIRST(&tlist->list);
+
+       while (entry != NULL) {
+               entry_next = TAILQ_NEXT(entry, entries);
+
+               free(entry);
+
+               entry = entry_next;
+       }
+
+       entry = TAILQ_FIRST(&tlist->free_list);
+
+       while (entry != NULL) {
+               entry_next = TAILQ_NEXT(entry, entries);
+
+               free(entry);
+
+               entry = entry_next;
+       }
+
+       timer_list_init(tlist);
+}
diff --git a/qdevices/timer-list.h b/qdevices/timer-list.h
new file mode 100644 (file)
index 0000000..86313ff
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TIMER_LIST_H_
+#define _TIMER_LIST_H_
+
+#include <sys/queue.h>
+
+#include <nspr.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * PR Interval is 32-bit integer which overflows. Maximum useable interval is around
+ * 6 hours (less). So define max interval as 5 hours
+ */
+#define TIMER_LIST_MAX_INTERVAL                        18000000
+
+typedef int (*timer_list_cb_fn)(void *data1, void *data2);
+
+struct timer_list_entry {
+       /* Time when timer was planned */
+       PRIntervalTime epoch;
+       /* Number of miliseconds to expire */
+       PRUint32 interval;
+       /* Time when timer expires (epoch + interval) */
+       PRIntervalTime expire_time;
+       timer_list_cb_fn func;
+       void *user_data1;
+       void *user_data2;
+       int is_active;
+       TAILQ_ENTRY(timer_list_entry) entries;
+};
+
+struct timer_list {
+       TAILQ_HEAD(, timer_list_entry) list;
+       TAILQ_HEAD(, timer_list_entry) free_list;
+};
+
+extern void                             timer_list_init(struct timer_list *tlist);
+
+extern struct timer_list_entry         *timer_list_add(struct timer_list *tlist,
+    PRUint32 interval, timer_list_cb_fn func, void *data1, void *data2);
+
+extern void                             timer_list_reschedule(struct timer_list *tlist,
+    struct timer_list_entry *entry);
+
+extern void                             timer_list_delete(struct timer_list *tlist,
+    struct timer_list_entry *entry);
+
+extern void                             timer_list_expire(struct timer_list *tlist);
+
+extern PRIntervalTime                   timer_list_time_to_expire(struct timer_list *tlist);
+
+extern uint32_t                                 timer_list_time_to_expire_ms(struct timer_list *tlist);
+
+extern void                             timer_list_free(struct timer_list *tlist);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TIMER_LIST_H_ */
diff --git a/qdevices/tlv.c b/qdevices/tlv.c
new file mode 100644 (file)
index 0000000..43bf97f
--- /dev/null
@@ -0,0 +1,1121 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * 64-bit variant of ntoh is not exactly standard...
+ */
+#if defined(__linux__)
+#include <endian.h>
+#elif defined(__FreeBSD__) || defined(__NetBSD__)
+#include <sys/endian.h>
+#elif defined(__OpenBSD__)
+#define be64toh(x) betoh64(x)
+#endif
+
+#include "tlv.h"
+
+#define TLV_TYPE_LENGTH                2
+#define TLV_LENGTH_LENGTH      2
+
+#define TLV_STATIC_SUPPORTED_OPTIONS_SIZE      23
+
+enum tlv_opt_type tlv_static_supported_options[TLV_STATIC_SUPPORTED_OPTIONS_SIZE] = {
+    TLV_OPT_MSG_SEQ_NUMBER,
+    TLV_OPT_CLUSTER_NAME,
+    TLV_OPT_TLS_SUPPORTED,
+    TLV_OPT_TLS_CLIENT_CERT_REQUIRED,
+    TLV_OPT_SUPPORTED_MESSAGES,
+    TLV_OPT_SUPPORTED_OPTIONS,
+    TLV_OPT_REPLY_ERROR_CODE,
+    TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE,
+    TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE,
+    TLV_OPT_NODE_ID,
+    TLV_OPT_SUPPORTED_DECISION_ALGORITHMS,
+    TLV_OPT_DECISION_ALGORITHM,
+    TLV_OPT_HEARTBEAT_INTERVAL,
+    TLV_OPT_RING_ID,
+    TLV_OPT_CONFIG_VERSION,
+    TLV_OPT_DATA_CENTER_ID,
+    TLV_OPT_NODE_STATE,
+    TLV_OPT_NODE_INFO,
+    TLV_OPT_NODE_LIST_TYPE,
+    TLV_OPT_VOTE,
+    TLV_OPT_QUORATE,
+    TLV_OPT_TIE_BREAKER,
+    TLV_OPT_HEURISTICS,
+};
+
+int
+tlv_add(struct dynar *msg, enum tlv_opt_type opt_type, uint16_t opt_len, const void *value)
+{
+       uint16_t nlen;
+       uint16_t nopt_type;
+
+       if (dynar_size(msg) + sizeof(nopt_type) + sizeof(nlen) + opt_len > dynar_max_size(msg)) {
+               return (-1);
+       }
+
+       nopt_type = htons((uint16_t)opt_type);
+       nlen = htons(opt_len);
+
+       dynar_cat(msg, &nopt_type, sizeof(nopt_type));
+       dynar_cat(msg, &nlen, sizeof(nlen));
+       dynar_cat(msg, value, opt_len);
+
+       return (0);
+}
+
+int
+tlv_add_u32(struct dynar *msg, enum tlv_opt_type opt_type, uint32_t u32)
+{
+       uint32_t nu32;
+
+       nu32 = htonl(u32);
+
+       return (tlv_add(msg, opt_type, sizeof(nu32), &nu32));
+}
+
+int
+tlv_add_u8(struct dynar *msg, enum tlv_opt_type opt_type, uint8_t u8)
+{
+
+       return (tlv_add(msg, opt_type, sizeof(u8), &u8));
+}
+
+int
+tlv_add_u16(struct dynar *msg, enum tlv_opt_type opt_type, uint16_t u16)
+{
+       uint16_t nu16;
+
+       nu16 = htons(u16);
+
+       return (tlv_add(msg, opt_type, sizeof(nu16), &nu16));
+}
+
+int
+tlv_add_u64(struct dynar *msg, enum tlv_opt_type opt_type, uint64_t u64)
+{
+       uint64_t nu64;
+
+       nu64 = htobe64(u64);
+
+       return (tlv_add(msg, opt_type, sizeof(nu64), &nu64));
+}
+
+int
+tlv_add_string(struct dynar *msg, enum tlv_opt_type opt_type, const char *str)
+{
+
+       return (tlv_add(msg, opt_type, strlen(str), str));
+}
+
+int
+tlv_add_msg_seq_number(struct dynar *msg, uint32_t msg_seq_number)
+{
+
+       return (tlv_add_u32(msg, TLV_OPT_MSG_SEQ_NUMBER, msg_seq_number));
+}
+
+int
+tlv_add_cluster_name(struct dynar *msg, const char *cluster_name)
+{
+
+       return (tlv_add_string(msg, TLV_OPT_CLUSTER_NAME, cluster_name));
+}
+
+int
+tlv_add_tls_supported(struct dynar *msg, enum tlv_tls_supported tls_supported)
+{
+
+       return (tlv_add_u8(msg, TLV_OPT_TLS_SUPPORTED, tls_supported));
+}
+
+int
+tlv_add_tls_client_cert_required(struct dynar *msg, int tls_client_cert_required)
+{
+
+       return (tlv_add_u8(msg, TLV_OPT_TLS_CLIENT_CERT_REQUIRED, tls_client_cert_required));
+}
+
+int
+tlv_add_u16_array(struct dynar *msg, enum tlv_opt_type opt_type, const uint16_t *array,
+    size_t array_size)
+{
+       size_t i;
+       uint16_t *nu16a;
+       uint16_t opt_len;
+       int res;
+
+       nu16a = malloc(sizeof(uint16_t) * array_size);
+       if (nu16a == NULL) {
+               return (-1);
+       }
+
+       for (i = 0; i < array_size; i++) {
+               nu16a[i] = htons(array[i]);
+       }
+
+       opt_len = sizeof(uint16_t) * array_size;
+
+       res = tlv_add(msg, opt_type, opt_len, nu16a);
+
+       free(nu16a);
+
+       return (res);
+}
+
+int
+tlv_add_supported_options(struct dynar *msg, const enum tlv_opt_type *supported_options,
+    size_t no_supported_options)
+{
+       uint16_t *u16a;
+       size_t i;
+       int res;
+
+       u16a = malloc(sizeof(*u16a) * no_supported_options);
+       if (u16a == NULL) {
+               return (-1);
+       }
+
+       for (i = 0; i < no_supported_options; i++) {
+               u16a[i] = (uint16_t)supported_options[i];
+       }
+
+       res = (tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_OPTIONS, u16a, no_supported_options));
+
+       free(u16a);
+
+       return (res);
+}
+
+int
+tlv_add_supported_decision_algorithms(struct dynar *msg,
+    const enum tlv_decision_algorithm_type *supported_algorithms, size_t no_supported_algorithms)
+{
+       uint16_t *u16a;
+       size_t i;
+       int res;
+
+       u16a = malloc(sizeof(*u16a) * no_supported_algorithms);
+       if (u16a == NULL) {
+               return (-1);
+       }
+
+       for (i = 0; i < no_supported_algorithms; i++) {
+               u16a[i] = (uint16_t)supported_algorithms[i];
+       }
+
+       res = (tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_DECISION_ALGORITHMS, u16a,
+           no_supported_algorithms));
+
+       free(u16a);
+
+       return (res);
+}
+
+int
+tlv_add_reply_error_code(struct dynar *msg, enum tlv_reply_error_code error_code)
+{
+
+       return (tlv_add_u16(msg, TLV_OPT_REPLY_ERROR_CODE, (uint16_t)error_code));
+}
+
+int
+tlv_add_server_maximum_request_size(struct dynar *msg, size_t server_maximum_request_size)
+{
+
+       return (tlv_add_u32(msg, TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE, server_maximum_request_size));
+}
+
+int
+tlv_add_server_maximum_reply_size(struct dynar *msg, size_t server_maximum_reply_size)
+{
+
+       return (tlv_add_u32(msg, TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE, server_maximum_reply_size));
+}
+
+int
+tlv_add_node_id(struct dynar *msg, uint32_t node_id)
+{
+
+       return (tlv_add_u32(msg, TLV_OPT_NODE_ID, node_id));
+}
+
+int
+tlv_add_decision_algorithm(struct dynar *msg, enum tlv_decision_algorithm_type decision_algorithm)
+{
+
+       return (tlv_add_u16(msg, TLV_OPT_DECISION_ALGORITHM, (uint16_t)decision_algorithm));
+}
+
+int
+tlv_add_heartbeat_interval(struct dynar *msg, uint32_t heartbeat_interval)
+{
+
+       return (tlv_add_u32(msg, TLV_OPT_HEARTBEAT_INTERVAL, heartbeat_interval));
+}
+
+int
+tlv_add_ring_id(struct dynar *msg, const struct tlv_ring_id *ring_id)
+{
+       uint64_t nu64;
+       uint32_t nu32;
+       char tmp_buf[12];
+
+       nu32 = htonl(ring_id->node_id);
+       nu64 = htobe64(ring_id->seq);
+
+       memcpy(tmp_buf, &nu32, sizeof(nu32));
+       memcpy(tmp_buf + sizeof(nu32), &nu64, sizeof(nu64));
+
+       return (tlv_add(msg, TLV_OPT_RING_ID, sizeof(tmp_buf), tmp_buf));
+}
+
+int
+tlv_add_tie_breaker(struct dynar *msg, const struct tlv_tie_breaker *tie_breaker)
+{
+       uint32_t nu32;
+       uint8_t u8;
+       char tmp_buf[5];
+
+       u8 = tie_breaker->mode;
+       nu32 = (tie_breaker->mode == TLV_TIE_BREAKER_MODE_NODE_ID ?
+           htonl(tie_breaker->node_id) : 0);
+
+       memcpy(tmp_buf, &u8, sizeof(u8));
+       memcpy(tmp_buf + sizeof(u8), &nu32, sizeof(nu32));
+
+       return (tlv_add(msg, TLV_OPT_TIE_BREAKER, sizeof(tmp_buf), tmp_buf));
+}
+
+int
+tlv_add_config_version(struct dynar *msg, uint64_t config_version)
+{
+
+       return (tlv_add_u64(msg, TLV_OPT_CONFIG_VERSION, config_version));
+}
+
+int
+tlv_add_data_center_id(struct dynar *msg, uint32_t data_center_id)
+{
+
+       return (tlv_add_u32(msg, TLV_OPT_DATA_CENTER_ID, data_center_id));
+}
+
+int
+tlv_add_node_state(struct dynar *msg, enum tlv_node_state node_state)
+{
+
+       return (tlv_add_u8(msg, TLV_OPT_NODE_STATE, node_state));
+}
+
+int
+tlv_add_node_info(struct dynar *msg, const struct tlv_node_info *node_info)
+{
+       struct dynar opt_value;
+       int res;
+
+       res = 0;
+       /*
+        * Create sub message,
+        */
+       dynar_init(&opt_value, 1024);
+       if ((res = tlv_add_node_id(&opt_value, node_info->node_id)) != 0) {
+               goto exit_dynar_destroy;
+       }
+
+       if (node_info->data_center_id != 0) {
+               if ((res = tlv_add_data_center_id(&opt_value, node_info->data_center_id)) != 0) {
+                       goto exit_dynar_destroy;
+               }
+       }
+
+       if (node_info->node_state != TLV_NODE_STATE_NOT_SET) {
+               if ((res = tlv_add_node_state(&opt_value, node_info->node_state)) != 0) {
+                       goto exit_dynar_destroy;
+               }
+       }
+
+       res = tlv_add(msg, TLV_OPT_NODE_INFO, dynar_size(&opt_value), dynar_data(&opt_value));
+       if (res != 0) {
+               goto exit_dynar_destroy;
+       }
+
+
+exit_dynar_destroy:
+       dynar_destroy(&opt_value);
+
+       return (res);
+}
+
+int
+tlv_add_node_list_type(struct dynar *msg, enum tlv_node_list_type node_list_type)
+{
+
+       return (tlv_add_u8(msg, TLV_OPT_NODE_LIST_TYPE, node_list_type));
+}
+
+int
+tlv_add_vote(struct dynar *msg, enum tlv_vote vote)
+{
+
+       return (tlv_add_u8(msg, TLV_OPT_VOTE, vote));
+}
+
+int
+tlv_add_quorate(struct dynar *msg, enum tlv_quorate quorate)
+{
+
+       return (tlv_add_u8(msg, TLV_OPT_QUORATE, quorate));
+}
+
+int
+tlv_add_heuristics(struct dynar *msg, enum tlv_heuristics heuristics)
+{
+
+       if (heuristics == TLV_HEURISTICS_UNDEFINED) {
+               return (-1);
+       }
+
+       return (tlv_add_u8(msg, TLV_OPT_HEURISTICS, heuristics));
+}
+
+void
+tlv_iter_init_str(const char *msg, size_t msg_len, size_t msg_header_len,
+    struct tlv_iterator *tlv_iter)
+{
+
+       tlv_iter->msg = msg;
+       tlv_iter->msg_len = msg_len;
+       tlv_iter->current_pos = 0;
+       tlv_iter->msg_header_len = msg_header_len;
+       tlv_iter->iter_next_called = 0;
+}
+
+void
+tlv_iter_init(const struct dynar *msg, size_t msg_header_len, struct tlv_iterator *tlv_iter)
+{
+
+       tlv_iter_init_str(dynar_data(msg), dynar_size(msg), msg_header_len, tlv_iter);
+}
+
+enum tlv_opt_type
+tlv_iter_get_type(const struct tlv_iterator *tlv_iter)
+{
+       uint16_t ntype;
+       uint16_t type;
+
+       memcpy(&ntype, tlv_iter->msg + tlv_iter->current_pos, sizeof(ntype));
+       type = ntohs(ntype);
+
+       return (type);
+}
+
+uint16_t
+tlv_iter_get_len(const struct tlv_iterator *tlv_iter)
+{
+       uint16_t nlen;
+       uint16_t len;
+
+       memcpy(&nlen, tlv_iter->msg + tlv_iter->current_pos + TLV_TYPE_LENGTH, sizeof(nlen));
+       len = ntohs(nlen);
+
+       return (len);
+}
+
+const char *
+tlv_iter_get_data(const struct tlv_iterator *tlv_iter)
+{
+
+       return (tlv_iter->msg + tlv_iter->current_pos + TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH);
+}
+
+int
+tlv_iter_next(struct tlv_iterator *tlv_iter)
+{
+       uint16_t len;
+
+       if (tlv_iter->iter_next_called == 0) {
+               tlv_iter->iter_next_called = 1;
+               tlv_iter->current_pos = tlv_iter->msg_header_len;
+
+               goto check_tlv_validity;
+       }
+
+       len = tlv_iter_get_len(tlv_iter);
+
+       if (tlv_iter->current_pos + TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH + len >=
+           tlv_iter->msg_len) {
+               return (0);
+       }
+
+       tlv_iter->current_pos += TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH + len;
+
+check_tlv_validity:
+       /*
+        * Check if tlv is valid = is not larger than whole message
+        */
+       len = tlv_iter_get_len(tlv_iter);
+
+       if (tlv_iter->current_pos + TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH + len > tlv_iter->msg_len) {
+               return (-1);
+       }
+
+       return (1);
+}
+
+int
+tlv_iter_decode_u32(struct tlv_iterator *tlv_iter, uint32_t *res)
+{
+       const char *opt_data;
+       uint16_t opt_len;
+       uint32_t nu32;
+
+       opt_len = tlv_iter_get_len(tlv_iter);
+       opt_data = tlv_iter_get_data(tlv_iter);
+
+       if (opt_len != sizeof(nu32)) {
+               return (-1);
+       }
+
+       memcpy(&nu32, opt_data, sizeof(nu32));
+       *res = ntohl(nu32);
+
+       return (0);
+}
+
+int
+tlv_iter_decode_u8(struct tlv_iterator *tlv_iter, uint8_t *res)
+{
+       const char *opt_data;
+       uint16_t opt_len;
+
+       opt_len = tlv_iter_get_len(tlv_iter);
+       opt_data = tlv_iter_get_data(tlv_iter);
+
+       if (opt_len != sizeof(*res)) {
+               return (-1);
+       }
+
+       memcpy(res, opt_data, sizeof(*res));
+
+       return (0);
+}
+
+int
+tlv_iter_decode_client_cert_required(struct tlv_iterator *tlv_iter, uint8_t *client_cert_required)
+{
+
+       return (tlv_iter_decode_u8(tlv_iter, client_cert_required));
+}
+
+int
+tlv_iter_decode_str(struct tlv_iterator *tlv_iter, char **str, size_t *str_len)
+{
+       const char *opt_data;
+       uint16_t opt_len;
+       char *tmp_str;
+
+       opt_len = tlv_iter_get_len(tlv_iter);
+       opt_data = tlv_iter_get_data(tlv_iter);
+
+       tmp_str = malloc(opt_len + 1);
+       if (tmp_str == NULL) {
+               return (-1);
+       }
+
+       memcpy(tmp_str, opt_data, opt_len);
+       tmp_str[opt_len] = '\0';
+
+       *str = tmp_str;
+       *str_len = opt_len;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_u16_array(struct tlv_iterator *tlv_iter, uint16_t **u16a, size_t *no_items)
+{
+       uint16_t opt_len;
+       uint16_t *u16a_res;
+       size_t i;
+
+       opt_len = tlv_iter_get_len(tlv_iter);
+
+       if (opt_len % sizeof(uint16_t) != 0) {
+               return (-1);
+       }
+
+       *no_items = opt_len / sizeof(uint16_t);
+
+       u16a_res = malloc(sizeof(uint16_t) * *no_items);
+       if (u16a_res == NULL) {
+               return (-2);
+       }
+
+       memcpy(u16a_res, tlv_iter_get_data(tlv_iter), opt_len);
+
+       for (i = 0; i < *no_items; i++) {
+               u16a_res[i] = ntohs(u16a_res[i]);
+       }
+
+       *u16a = u16a_res;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_supported_options(struct tlv_iterator *tlv_iter,
+    enum tlv_opt_type **supported_options, size_t *no_supported_options)
+{
+       uint16_t *u16a;
+       enum tlv_opt_type *tlv_opt_array;
+       size_t i;
+       int res;
+
+       res = tlv_iter_decode_u16_array(tlv_iter, &u16a, no_supported_options);
+       if (res != 0) {
+               return (res);
+       }
+
+       tlv_opt_array = malloc(sizeof(enum tlv_opt_type) * *no_supported_options);
+       if (tlv_opt_array == NULL) {
+               free(u16a);
+               return (-2);
+       }
+
+       for (i = 0; i < *no_supported_options; i++) {
+               tlv_opt_array[i] = (enum tlv_opt_type)u16a[i];
+       }
+
+       free(u16a);
+
+       *supported_options = tlv_opt_array;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_supported_decision_algorithms(struct tlv_iterator *tlv_iter,
+    enum tlv_decision_algorithm_type **supported_decision_algorithms,
+    size_t *no_supported_decision_algorithms)
+{
+       uint16_t *u16a;
+       enum tlv_decision_algorithm_type *tlv_decision_algorithm_type_array;
+       size_t i;
+       int res;
+
+       res = tlv_iter_decode_u16_array(tlv_iter, &u16a, no_supported_decision_algorithms);
+       if (res != 0) {
+               return (res);
+       }
+
+       tlv_decision_algorithm_type_array = malloc(
+           sizeof(enum tlv_decision_algorithm_type) * *no_supported_decision_algorithms);
+
+       if (tlv_decision_algorithm_type_array == NULL) {
+               free(u16a);
+               return (-2);
+       }
+
+       for (i = 0; i < *no_supported_decision_algorithms; i++) {
+               tlv_decision_algorithm_type_array[i] = (enum tlv_decision_algorithm_type)u16a[i];
+       }
+
+       free(u16a);
+
+       *supported_decision_algorithms = tlv_decision_algorithm_type_array;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_u16(struct tlv_iterator *tlv_iter, uint16_t *u16)
+{
+       const char *opt_data;
+       uint16_t opt_len;
+       uint16_t nu16;
+
+       opt_len = tlv_iter_get_len(tlv_iter);
+       opt_data = tlv_iter_get_data(tlv_iter);
+
+       if (opt_len != sizeof(nu16)) {
+               return (-1);
+       }
+
+       memcpy(&nu16, opt_data, sizeof(nu16));
+       *u16 = ntohs(nu16);
+
+       return (0);
+}
+
+int
+tlv_iter_decode_u64(struct tlv_iterator *tlv_iter, uint64_t *u64)
+{
+       const char *opt_data;
+       uint64_t opt_len;
+       uint64_t nu64;
+
+       opt_len = tlv_iter_get_len(tlv_iter);
+       opt_data = tlv_iter_get_data(tlv_iter);
+
+       if (opt_len != sizeof(nu64)) {
+               return (-1);
+       }
+
+       memcpy(&nu64, opt_data, sizeof(nu64));
+       *u64 = be64toh(nu64);
+
+       return (0);
+}
+
+int
+tlv_iter_decode_reply_error_code(struct tlv_iterator *tlv_iter,
+    enum tlv_reply_error_code *reply_error_code)
+{
+
+       return (tlv_iter_decode_u16(tlv_iter, (uint16_t *)reply_error_code));
+}
+
+int
+tlv_iter_decode_tls_supported(struct tlv_iterator *tlv_iter, enum tlv_tls_supported *tls_supported)
+{
+       uint8_t u8;
+       enum tlv_tls_supported tmp_tls_supported;
+
+       if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) {
+               return (-1);
+       }
+
+       tmp_tls_supported = u8;
+
+       if (tmp_tls_supported != TLV_TLS_UNSUPPORTED &&
+           tmp_tls_supported != TLV_TLS_SUPPORTED &&
+           tmp_tls_supported != TLV_TLS_REQUIRED) {
+               return (-4);
+       }
+
+       *tls_supported = tmp_tls_supported;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_decision_algorithm(struct tlv_iterator *tlv_iter,
+    enum tlv_decision_algorithm_type *decision_algorithm)
+{
+       uint16_t u16;
+
+       if (tlv_iter_decode_u16(tlv_iter, &u16) != 0) {
+               return (-1);
+       }
+
+       *decision_algorithm = (enum tlv_decision_algorithm_type)u16;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_ring_id(struct tlv_iterator *tlv_iter, struct tlv_ring_id *ring_id)
+{
+       const char *opt_data;
+       uint16_t opt_len;
+       uint32_t nu32;
+       uint64_t nu64;
+       char tmp_buf[12];
+
+       opt_len = tlv_iter_get_len(tlv_iter);
+       opt_data = tlv_iter_get_data(tlv_iter);
+
+       if (opt_len != sizeof(tmp_buf)) {
+               return (-1);
+       }
+
+       memcpy(&nu32, opt_data, sizeof(nu32));
+       memcpy(&nu64, opt_data + sizeof(nu32), sizeof(nu64));
+
+       ring_id->node_id = ntohl(nu32);
+       ring_id->seq = be64toh(nu64);
+
+       return (0);
+}
+
+int
+tlv_iter_decode_tie_breaker(struct tlv_iterator *tlv_iter, struct tlv_tie_breaker *tie_breaker)
+{
+       const char *opt_data;
+       uint16_t opt_len;
+       uint32_t nu32;
+       uint8_t u8;
+       enum tlv_tie_breaker_mode tie_breaker_mode;
+       char tmp_buf[5];
+
+       opt_len = tlv_iter_get_len(tlv_iter);
+       opt_data = tlv_iter_get_data(tlv_iter);
+
+       if (opt_len != sizeof(tmp_buf)) {
+               return (-1);
+       }
+
+       memcpy(&u8, opt_data, sizeof(u8));
+       tie_breaker_mode = u8;
+
+       if (tie_breaker_mode != TLV_TIE_BREAKER_MODE_LOWEST &&
+           tie_breaker_mode != TLV_TIE_BREAKER_MODE_HIGHEST &&
+           tie_breaker_mode != TLV_TIE_BREAKER_MODE_NODE_ID) {
+               return (-4);
+       }
+
+       memcpy(&nu32, opt_data + sizeof(u8), sizeof(nu32));
+
+       tie_breaker->mode = tie_breaker_mode;
+       tie_breaker->node_id = (tie_breaker->mode == TLV_TIE_BREAKER_MODE_NODE_ID ?
+           ntohl(nu32) : 0);
+
+       return (0);
+}
+
+int
+tlv_iter_decode_node_state(struct tlv_iterator *tlv_iter, enum tlv_node_state *node_state)
+{
+       uint8_t u8;
+       enum tlv_node_state tmp_node_state;
+
+       if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) {
+               return (-1);
+       }
+
+       tmp_node_state = u8;
+
+       if (tmp_node_state != TLV_NODE_STATE_MEMBER &&
+           tmp_node_state != TLV_NODE_STATE_DEAD &&
+           tmp_node_state != TLV_NODE_STATE_LEAVING) {
+               return (-4);
+       }
+
+       *node_state = tmp_node_state;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_node_info(struct tlv_iterator *tlv_iter, struct tlv_node_info *node_info)
+{
+       struct tlv_iterator data_tlv_iter;
+       int iter_res;
+       int res;
+       enum tlv_opt_type opt_type;
+       struct tlv_node_info tmp_node_info;
+
+       memset(&tmp_node_info, 0, sizeof(tmp_node_info));
+
+       tlv_iter_init_str(tlv_iter_get_data(tlv_iter), tlv_iter_get_len(tlv_iter), 0,
+           &data_tlv_iter);
+
+       while ((iter_res = tlv_iter_next(&data_tlv_iter)) > 0) {
+               opt_type = tlv_iter_get_type(&data_tlv_iter);
+
+               switch (opt_type) {
+               case TLV_OPT_NODE_ID:
+                       if ((res = tlv_iter_decode_u32(&data_tlv_iter,
+                           &tmp_node_info.node_id)) != 0) {
+                               return (res);
+                       }
+                       break;
+               case TLV_OPT_DATA_CENTER_ID:
+                       if ((res = tlv_iter_decode_u32(&data_tlv_iter,
+                           &tmp_node_info.data_center_id)) != 0) {
+                               return (res);
+                       }
+                       break;
+               case TLV_OPT_NODE_STATE:
+                       if ((res = tlv_iter_decode_node_state(&data_tlv_iter,
+                           &tmp_node_info.node_state)) != 0) {
+                               return (res);
+                       }
+                       break;
+               default:
+                       /*
+                        * Other options are not processed
+                        */
+                       break;
+               }
+       }
+
+       if (iter_res != 0) {
+               return (-3);
+       }
+
+       if (tmp_node_info.node_id == 0) {
+               return (-4);
+       }
+
+       memcpy(node_info, &tmp_node_info, sizeof(tmp_node_info));
+
+       return (0);
+}
+
+int
+tlv_iter_decode_node_list_type(struct tlv_iterator *tlv_iter,
+    enum tlv_node_list_type *node_list_type)
+{
+       uint8_t u8;
+       enum tlv_node_list_type tmp_node_list_type;
+
+       if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) {
+               return (-1);
+       }
+
+       tmp_node_list_type = u8;
+
+       if (tmp_node_list_type != TLV_NODE_LIST_TYPE_INITIAL_CONFIG &&
+           tmp_node_list_type != TLV_NODE_LIST_TYPE_CHANGED_CONFIG &&
+           tmp_node_list_type != TLV_NODE_LIST_TYPE_MEMBERSHIP &&
+           tmp_node_list_type != TLV_NODE_LIST_TYPE_QUORUM) {
+               return (-4);
+       }
+
+       *node_list_type = tmp_node_list_type;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_vote(struct tlv_iterator *tlv_iter, enum tlv_vote *vote)
+{
+       uint8_t u8;
+       enum tlv_vote tmp_vote;
+
+       if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) {
+               return (-1);
+       }
+
+       tmp_vote = u8;
+
+       if (tmp_vote != TLV_VOTE_ACK &&
+           tmp_vote != TLV_VOTE_NACK &&
+           tmp_vote != TLV_VOTE_ASK_LATER &&
+           tmp_vote != TLV_VOTE_WAIT_FOR_REPLY &&
+           tmp_vote != TLV_VOTE_NO_CHANGE) {
+               return (-4);
+       }
+
+       *vote = tmp_vote;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_quorate(struct tlv_iterator *tlv_iter, enum tlv_quorate *quorate)
+{
+       uint8_t u8;
+       enum tlv_quorate tmp_quorate;
+
+       if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) {
+               return (-1);
+       }
+
+       tmp_quorate = u8;
+
+       if (tmp_quorate != TLV_QUORATE_QUORATE &&
+           tmp_quorate != TLV_QUORATE_INQUORATE) {
+               return (-4);
+       }
+
+       *quorate = tmp_quorate;
+
+       return (0);
+}
+
+int
+tlv_iter_decode_heuristics(struct tlv_iterator *tlv_iter, enum tlv_heuristics *heuristics)
+{
+       uint8_t u8;
+       enum tlv_heuristics tmp_heuristics;
+
+       if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) {
+               return (-1);
+       }
+
+       tmp_heuristics = u8;
+
+       if (tmp_heuristics != TLV_HEURISTICS_PASS &&
+           tmp_heuristics != TLV_HEURISTICS_FAIL) {
+               return (-4);
+       }
+
+       *heuristics = tmp_heuristics;
+
+       return (0);
+}
+
+void
+tlv_get_supported_options(enum tlv_opt_type **supported_options, size_t *no_supported_options)
+{
+
+       *supported_options = tlv_static_supported_options;
+       *no_supported_options = TLV_STATIC_SUPPORTED_OPTIONS_SIZE;
+}
+
+int
+tlv_ring_id_eq(const struct tlv_ring_id *rid1, const struct tlv_ring_id *rid2)
+{
+
+       return (rid1->node_id == rid2->node_id && rid1->seq == rid2->seq);
+}
+
+int
+tlv_tie_breaker_eq(const struct tlv_tie_breaker *tb1, const struct tlv_tie_breaker *tb2)
+{
+
+       if (tb1->mode == tb2->mode && tb1->mode == TLV_TIE_BREAKER_MODE_NODE_ID) {
+               return (tb1->node_id == tb2->node_id);
+       }
+
+       return (tb1->mode == tb2->mode);
+}
+
+const char *
+tlv_vote_to_str(enum tlv_vote vote)
+{
+
+       switch (vote) {
+       case TLV_VOTE_UNDEFINED: break;
+       case TLV_VOTE_ACK: return ("ACK"); break;
+       case TLV_VOTE_NACK: return ("NACK"); break;
+       case TLV_VOTE_ASK_LATER: return ("Ask later"); break;
+       case TLV_VOTE_WAIT_FOR_REPLY: return ("Wait for reply"); break;
+       case TLV_VOTE_NO_CHANGE: return ("No change"); break;
+       }
+
+       return ("Unknown vote value");
+}
+
+const char *
+tlv_node_state_to_str(enum tlv_node_state state)
+{
+
+       switch (state) {
+       case TLV_NODE_STATE_NOT_SET: return ("not set"); break;
+       case TLV_NODE_STATE_MEMBER: return ("member"); break;
+       case TLV_NODE_STATE_DEAD: return ("dead"); break;
+       case TLV_NODE_STATE_LEAVING: return ("leaving"); break;
+       }
+
+       return ("Unhandled node state");
+}
+
+const char *
+tlv_tls_supported_to_str(enum tlv_tls_supported tls_supported)
+{
+
+       switch (tls_supported) {
+       case TLV_TLS_UNSUPPORTED: return ("Unsupported"); break;
+       case TLV_TLS_SUPPORTED: return ("Supported"); break;
+       case TLV_TLS_REQUIRED: return ("Required"); break;
+       }
+
+       return ("Unhandled tls supported state");
+}
+
+const char *
+tlv_decision_algorithm_type_to_str(enum tlv_decision_algorithm_type algorithm)
+{
+
+       switch (algorithm) {
+       case TLV_DECISION_ALGORITHM_TYPE_TEST: return ("Test"); break;
+       case TLV_DECISION_ALGORITHM_TYPE_FFSPLIT: return ("Fifty-Fifty split"); break;
+       case TLV_DECISION_ALGORITHM_TYPE_2NODELMS: return ("2 Node LMS"); break;
+       case TLV_DECISION_ALGORITHM_TYPE_LMS: return ("LMS"); break;
+       }
+
+       return ("Unknown algorithm");
+}
+
+const char *
+tlv_heuristics_to_str(enum tlv_heuristics heuristics)
+{
+
+       switch (heuristics) {
+       case TLV_HEURISTICS_UNDEFINED: return ("Undefined"); break;
+       case TLV_HEURISTICS_PASS: return ("Pass"); break;
+       case TLV_HEURISTICS_FAIL: return ("Fail"); break;
+       }
+
+       return ("Unknown heuristics type");
+}
+
+int
+tlv_heuristics_cmp(enum tlv_heuristics h1, enum tlv_heuristics h2)
+{
+       int res;
+
+       res = -2;
+
+       switch (h1) {
+       case TLV_HEURISTICS_UNDEFINED:
+               switch (h2) {
+               case TLV_HEURISTICS_UNDEFINED: res = 0; break;
+               case TLV_HEURISTICS_PASS: res = -1; break;
+               case TLV_HEURISTICS_FAIL: res = 1; break;
+               }
+               break;
+       case TLV_HEURISTICS_PASS:
+               switch (h2) {
+               case TLV_HEURISTICS_UNDEFINED: res = 1; break;
+               case TLV_HEURISTICS_PASS: res = 0; break;
+               case TLV_HEURISTICS_FAIL: res = 1; break;
+               }
+               break;
+       case TLV_HEURISTICS_FAIL:
+               switch (h2) {
+               case TLV_HEURISTICS_UNDEFINED: res = -1; break;
+               case TLV_HEURISTICS_PASS: res = -1; break;
+               case TLV_HEURISTICS_FAIL: res = 0; break;
+               }
+               break;
+       }
+
+       assert(res == -1 || res == 0 || res == 1);
+
+       return (res);
+}
diff --git a/qdevices/tlv.h b/qdevices/tlv.h
new file mode 100644 (file)
index 0000000..586421c
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2015-2017 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TLV_H_
+#define _TLV_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum tlv_opt_type {
+       TLV_OPT_MSG_SEQ_NUMBER = 0,
+       TLV_OPT_CLUSTER_NAME = 1,
+       TLV_OPT_TLS_SUPPORTED = 2,
+       TLV_OPT_TLS_CLIENT_CERT_REQUIRED = 3,
+       TLV_OPT_SUPPORTED_MESSAGES = 4,
+       TLV_OPT_SUPPORTED_OPTIONS = 5,
+       TLV_OPT_REPLY_ERROR_CODE = 6,
+       TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE = 7,
+       TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE = 8,
+       TLV_OPT_NODE_ID = 9,
+       TLV_OPT_SUPPORTED_DECISION_ALGORITHMS = 10,
+       TLV_OPT_DECISION_ALGORITHM = 11,
+       TLV_OPT_HEARTBEAT_INTERVAL = 12,
+       TLV_OPT_RING_ID = 13,
+       TLV_OPT_CONFIG_VERSION = 14,
+       TLV_OPT_DATA_CENTER_ID = 15,
+       TLV_OPT_NODE_STATE = 16,
+       TLV_OPT_NODE_INFO = 17,
+       TLV_OPT_NODE_LIST_TYPE = 18,
+       TLV_OPT_VOTE = 19,
+       TLV_OPT_QUORATE = 20,
+       TLV_OPT_TIE_BREAKER = 21,
+       TLV_OPT_HEURISTICS = 22,
+};
+
+enum tlv_tls_supported {
+       TLV_TLS_UNSUPPORTED = 0,
+       TLV_TLS_SUPPORTED = 1,
+       TLV_TLS_REQUIRED = 2,
+};
+
+enum tlv_reply_error_code {
+       TLV_REPLY_ERROR_CODE_NO_ERROR = 0,
+       TLV_REPLY_ERROR_CODE_UNSUPPORTED_NEEDED_MESSAGE = 1,
+       TLV_REPLY_ERROR_CODE_UNSUPPORTED_NEEDED_OPTION = 2,
+       TLV_REPLY_ERROR_CODE_TLS_REQUIRED = 3,
+       TLV_REPLY_ERROR_CODE_UNSUPPORTED_MESSAGE = 4,
+       TLV_REPLY_ERROR_CODE_MESSAGE_TOO_LONG = 5,
+       TLV_REPLY_ERROR_CODE_PREINIT_REQUIRED = 6,
+       TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION = 7,
+       TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE = 8,
+       TLV_REPLY_ERROR_CODE_ERROR_DECODING_MSG = 9,
+       TLV_REPLY_ERROR_CODE_INTERNAL_ERROR = 10,
+       TLV_REPLY_ERROR_CODE_INIT_REQUIRED = 11,
+       TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM = 12,
+       TLV_REPLY_ERROR_CODE_INVALID_HEARTBEAT_INTERVAL = 13,
+       TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM_MESSAGE = 14,
+       TLV_REPLY_ERROR_CODE_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES = 15,
+       TLV_REPLY_ERROR_CODE_ALGORITHM_DIFFERS_FROM_OTHER_NODES = 16,
+       TLV_REPLY_ERROR_CODE_DUPLICATE_NODE_ID = 17,
+       TLV_REPLY_ERROR_CODE_INVALID_CONFIG_NODE_LIST = 18,
+       TLV_REPLY_ERROR_CODE_INVALID_MEMBERSHIP_NODE_LIST = 19,
+};
+
+enum tlv_decision_algorithm_type {
+       TLV_DECISION_ALGORITHM_TYPE_TEST = 0,
+       TLV_DECISION_ALGORITHM_TYPE_FFSPLIT = 1,
+       TLV_DECISION_ALGORITHM_TYPE_2NODELMS = 2,
+       TLV_DECISION_ALGORITHM_TYPE_LMS = 3,
+};
+
+struct tlv_ring_id {
+       uint32_t node_id;
+       uint64_t seq;
+};
+
+enum tlv_node_state {
+       TLV_NODE_STATE_NOT_SET = 0,
+       TLV_NODE_STATE_MEMBER = 1,
+       TLV_NODE_STATE_DEAD = 2,
+       TLV_NODE_STATE_LEAVING = 3,
+};
+
+enum tlv_node_list_type {
+       TLV_NODE_LIST_TYPE_INITIAL_CONFIG = 0,
+       TLV_NODE_LIST_TYPE_CHANGED_CONFIG = 1,
+       TLV_NODE_LIST_TYPE_MEMBERSHIP = 2,
+       TLV_NODE_LIST_TYPE_QUORUM = 3,
+};
+
+enum tlv_vote {
+       TLV_VOTE_UNDEFINED = 0,
+       TLV_VOTE_ACK = 1,
+       TLV_VOTE_NACK = 2,
+       TLV_VOTE_ASK_LATER = 3,
+       TLV_VOTE_WAIT_FOR_REPLY = 4,
+       TLV_VOTE_NO_CHANGE = 5,
+};
+
+enum tlv_quorate {
+       TLV_QUORATE_INQUORATE = 0,
+       TLV_QUORATE_QUORATE = 1,
+};
+
+enum tlv_tie_breaker_mode {
+       TLV_TIE_BREAKER_MODE_LOWEST = 1,
+       TLV_TIE_BREAKER_MODE_HIGHEST = 2,
+       TLV_TIE_BREAKER_MODE_NODE_ID = 3,
+};
+
+struct tlv_tie_breaker {
+       enum tlv_tie_breaker_mode mode;
+       uint32_t node_id;
+};
+
+struct tlv_node_info {
+       uint32_t node_id;
+       uint32_t data_center_id;                /* 0 - data center id was not set */
+       enum tlv_node_state node_state;         /* TLV_NODE_STATE_NOT_SET - state was not set */
+};
+
+enum tlv_heuristics {
+       TLV_HEURISTICS_UNDEFINED = 0,
+       TLV_HEURISTICS_PASS = 1,
+       TLV_HEURISTICS_FAIL = 2,
+};
+
+struct tlv_iterator {
+       const char *msg;
+       size_t msg_len;
+       size_t current_pos;
+       size_t msg_header_len;
+       int iter_next_called;
+};
+
+extern int                      tlv_add(struct dynar *msg, enum tlv_opt_type opt_type,
+    uint16_t opt_len, const void *value);
+
+extern int                      tlv_add_u32(struct dynar *msg, enum tlv_opt_type opt_type,
+    uint32_t u32);
+
+extern int                      tlv_add_u8(struct dynar *msg, enum tlv_opt_type opt_type,
+    uint8_t u8);
+
+extern int                      tlv_add_u16(struct dynar *msg, enum tlv_opt_type opt_type,
+    uint16_t u16);
+
+extern int                      tlv_add_u64(struct dynar *msg, enum tlv_opt_type opt_type,
+    uint64_t u64);
+
+extern int                      tlv_add_string(struct dynar *msg, enum tlv_opt_type opt_type,
+    const char *str);
+
+extern int                      tlv_add_u16_array(struct dynar *msg, enum tlv_opt_type opt_type,
+    const uint16_t *array, size_t array_size);
+
+extern int                      tlv_add_supported_options(struct dynar *msg,
+    const enum tlv_opt_type *supported_options, size_t no_supported_options);
+
+extern int                      tlv_add_msg_seq_number(struct dynar *msg,
+    uint32_t msg_seq_number);
+
+extern int                      tlv_add_cluster_name(struct dynar *msg, const char *cluster_name);
+
+extern int                      tlv_add_tls_supported(struct dynar *msg,
+    enum tlv_tls_supported tls_supported);
+
+extern int                      tlv_add_tls_client_cert_required(struct dynar *msg,
+    int tls_client_cert_required);
+
+extern int                      tlv_add_reply_error_code(struct dynar *msg,
+    enum tlv_reply_error_code error_code);
+
+extern int                      tlv_add_node_id(struct dynar *msg, uint32_t node_id);
+
+extern int                      tlv_add_server_maximum_request_size(struct dynar *msg,
+    size_t server_maximum_request_size);
+
+extern int                      tlv_add_server_maximum_reply_size(struct dynar *msg,
+    size_t server_maximum_reply_size);
+
+extern int                      tlv_add_supported_decision_algorithms(struct dynar *msg,
+    const enum tlv_decision_algorithm_type *supported_algorithms, size_t no_supported_algorithms);
+
+extern int                      tlv_add_decision_algorithm(struct dynar *msg,
+    enum tlv_decision_algorithm_type decision_algorithm);
+
+extern int                      tlv_add_heartbeat_interval(struct dynar *msg,
+    uint32_t heartbeat_interval);
+
+extern int                      tlv_add_ring_id(struct dynar *msg,
+    const struct tlv_ring_id *ring_id);
+
+extern int                      tlv_add_tie_breaker(struct dynar *msg,
+    const struct tlv_tie_breaker *tie_breaker);
+
+extern int                      tlv_add_config_version(struct dynar *msg,
+    uint64_t config_version);
+
+extern int                      tlv_add_data_center_id(struct dynar *msg,
+    uint32_t data_center_id);
+
+extern int                      tlv_add_node_state(struct dynar *msg,
+    enum tlv_node_state node_state);
+
+extern int                      tlv_add_node_info(struct dynar *msg,
+    const struct tlv_node_info *node_info);
+
+extern int                      tlv_add_node_list_type(struct dynar *msg,
+    enum tlv_node_list_type node_list_type);
+
+extern int                      tlv_add_vote(struct dynar *msg, enum tlv_vote vote);
+
+extern int                      tlv_add_quorate(struct dynar *msg, enum tlv_quorate quorate);
+
+extern int                      tlv_add_heuristics(struct dynar *msg,
+    enum tlv_heuristics heuristics);
+
+extern void                     tlv_iter_init_str(const char *msg, size_t msg_len,
+    size_t msg_header_len, struct tlv_iterator *tlv_iter);
+
+extern void                     tlv_iter_init(const struct dynar *msg, size_t msg_header_len,
+    struct tlv_iterator *tlv_iter);
+
+extern enum tlv_opt_type        tlv_iter_get_type(const struct tlv_iterator *tlv_iter);
+
+extern uint16_t                         tlv_iter_get_len(const struct tlv_iterator *tlv_iter);
+
+extern const char              *tlv_iter_get_data(const struct tlv_iterator *tlv_iter);
+
+extern int                      tlv_iter_next(struct tlv_iterator *tlv_iter);
+
+extern int                      tlv_iter_decode_u8(struct tlv_iterator *tlv_iter, uint8_t *res);
+
+extern int                      tlv_iter_decode_tls_supported(struct tlv_iterator *tlv_iter,
+    enum tlv_tls_supported *tls_supported);
+
+extern int                      tlv_iter_decode_u32(struct tlv_iterator *tlv_iter,
+    uint32_t *res);
+
+extern int                      tlv_iter_decode_str(struct tlv_iterator *tlv_iter, char **str,
+    size_t *str_len);
+
+extern int                      tlv_iter_decode_client_cert_required(
+    struct tlv_iterator *tlv_iter, uint8_t *client_cert_required);
+
+extern int                      tlv_iter_decode_u16_array(struct tlv_iterator *tlv_iter,
+    uint16_t **u16a, size_t *no_items);
+
+extern int                      tlv_iter_decode_supported_options(struct tlv_iterator *tlv_iter,
+    enum tlv_opt_type **supported_options, size_t *no_supported_options);
+
+extern int                      tlv_iter_decode_supported_decision_algorithms(
+    struct tlv_iterator *tlv_iter,
+    enum tlv_decision_algorithm_type **supported_decision_algorithms,
+    size_t *no_supported_decision_algorithms);
+
+extern int                      tlv_iter_decode_u16(struct tlv_iterator *tlv_iter,
+    uint16_t *u16);
+
+extern int                      tlv_iter_decode_u64(struct tlv_iterator *tlv_iter,
+    uint64_t *u64);
+
+extern int                      tlv_iter_decode_reply_error_code(struct tlv_iterator *tlv_iter,
+    enum tlv_reply_error_code *reply_error_code);
+
+extern int                      tlv_iter_decode_decision_algorithm(struct tlv_iterator *tlv_iter,
+    enum tlv_decision_algorithm_type *decision_algorithm);
+
+extern int                      tlv_iter_decode_ring_id(struct tlv_iterator *tlv_iter,
+    struct tlv_ring_id *ring_id);
+
+extern int                      tlv_iter_decode_tie_breaker(struct tlv_iterator *tlv_iter,
+    struct tlv_tie_breaker *tie_breaker);
+
+extern int                      tlv_iter_decode_node_state(struct tlv_iterator *tlv_iter,
+    enum tlv_node_state *node_state);
+
+extern int                      tlv_iter_decode_node_info(struct tlv_iterator *tlv_iter,
+    struct tlv_node_info *node_info);
+
+extern int                      tlv_iter_decode_node_list_type(struct tlv_iterator *tlv_iter,
+    enum tlv_node_list_type *node_list_type);
+
+extern int                      tlv_iter_decode_vote(struct tlv_iterator *tlv_iter,
+    enum tlv_vote *vote);
+
+extern int                      tlv_iter_decode_quorate(struct tlv_iterator *tlv_iter,
+    enum tlv_quorate *quorate);
+
+extern int                      tlv_iter_decode_heuristics(struct tlv_iterator *tlv_iter,
+    enum tlv_heuristics *heuristics);
+
+extern void                     tlv_get_supported_options(enum tlv_opt_type **supported_options,
+    size_t *no_supported_options);
+
+extern int                      tlv_ring_id_eq(const struct tlv_ring_id *rid1,
+    const struct tlv_ring_id *rid2);
+
+extern int                      tlv_tie_breaker_eq(const struct tlv_tie_breaker *tb1,
+    const struct tlv_tie_breaker *tb2);
+
+extern const char              *tlv_vote_to_str(enum tlv_vote vote);
+
+extern const char              *tlv_node_state_to_str(enum tlv_node_state state);
+
+extern const char              *tlv_tls_supported_to_str(enum tlv_tls_supported tls_supported);
+
+extern const char              *tlv_decision_algorithm_type_to_str(
+    enum tlv_decision_algorithm_type algorithm);
+
+extern const char              *tlv_heuristics_to_str(enum tlv_heuristics heuristics);
+
+/*
+ * Compare h1 and h2. Return -1 if h1 < h2, 0 if h1 == h2 and 1 if h1 > h2
+ */
+extern int                      tlv_heuristics_cmp(enum tlv_heuristics h1, enum tlv_heuristics h2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TLV_H_ */
diff --git a/qdevices/unix-socket-client-list.c b/qdevices/unix-socket-client-list.c
new file mode 100644 (file)
index 0000000..3c31089
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "unix-socket-client-list.h"
+
+void
+unix_socket_client_list_init(struct unix_socket_client_list *client_list)
+{
+
+       TAILQ_INIT(client_list);
+}
+
+struct unix_socket_client *
+unix_socket_client_list_add(struct unix_socket_client_list *client_list,
+    int sock, size_t max_receive_size, size_t max_send_size, void *user_data)
+{
+       struct unix_socket_client *client;
+
+       client = (struct unix_socket_client *)malloc(sizeof(*client));
+       if (client == NULL) {
+               return (NULL);
+       }
+
+       unix_socket_client_init(client, sock, max_receive_size, max_send_size, user_data);
+
+       TAILQ_INSERT_TAIL(client_list, client, entries);
+
+       return (client);
+}
+
+void
+unix_socket_client_list_free(struct unix_socket_client_list *client_list)
+{
+       struct unix_socket_client *client;
+       struct unix_socket_client *client_next;
+
+       client = TAILQ_FIRST(client_list);
+
+       while (client != NULL) {
+               client_next = TAILQ_NEXT(client, entries);
+
+               unix_socket_client_destroy(client);
+               free(client);
+
+               client = client_next;
+       }
+
+       TAILQ_INIT(client_list);
+}
+
+void
+unix_socket_client_list_del(struct unix_socket_client_list *client_list,
+    struct unix_socket_client *client)
+{
+
+       TAILQ_REMOVE(client_list, client, entries);
+       unix_socket_client_destroy(client);
+       free(client);
+}
+
+size_t
+unix_socket_client_list_no_clients(struct unix_socket_client_list *client_list)
+{
+       size_t res;
+       struct unix_socket_client *client;
+
+       res = 0;
+
+       TAILQ_FOREACH(client, client_list, entries) {
+               res++;
+       }
+
+       return (res);
+}
diff --git a/qdevices/unix-socket-client-list.h b/qdevices/unix-socket-client-list.h
new file mode 100644 (file)
index 0000000..c5a482d
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _UNIX_SOCKET_CLIENT_LIST_H_
+#define _UNIX_SOCKET_CLIENT_LIST_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include "unix-socket-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+TAILQ_HEAD(unix_socket_client_list, unix_socket_client);
+
+extern void                             unix_socket_client_list_init(
+    struct unix_socket_client_list *client_list);
+
+extern struct unix_socket_client       *unix_socket_client_list_add(
+    struct unix_socket_client_list *client_list,
+    int sock, size_t max_receive_size, size_t max_send_size, void *user_data);
+
+extern void                             unix_socket_client_list_free(
+    struct unix_socket_client_list *client_list);
+
+extern void                             unix_socket_client_list_del(
+    struct unix_socket_client_list *client_list, struct unix_socket_client *client);
+
+extern size_t                           unix_socket_client_list_no_clients(
+    struct unix_socket_client_list *client_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UNIX_SOCKET_CLIENT_LIST_H_ */
diff --git a/qdevices/unix-socket-client.c b/qdevices/unix-socket-client.c
new file mode 100644 (file)
index 0000000..10235de
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include "unix-socket-client.h"
+#include "unix-socket.h"
+
+#define UNIX_SOCKET_CLIENT_BUFFER      256
+
+void
+unix_socket_client_init(struct unix_socket_client *client, int sock, size_t max_receive_size,
+    size_t max_send_size, void *user_data)
+{
+
+       memset(client, 0, sizeof(*client));
+       client->socket = sock;
+       client->user_data = user_data;
+       dynar_init(&client->receive_buffer, max_receive_size);
+       dynar_init(&client->send_buffer, max_send_size);
+}
+
+void
+unix_socket_client_destroy(struct unix_socket_client *client)
+{
+
+       dynar_destroy(&client->send_buffer);
+       dynar_destroy(&client->receive_buffer);
+}
+
+void
+unix_socket_client_read_line(struct unix_socket_client *client, int enabled)
+{
+
+       client->reading_line = enabled;
+}
+
+void
+unix_socket_client_write_buffer(struct unix_socket_client *client, int enabled)
+{
+
+       client->writing_buffer = enabled;
+}
+
+/*
+ *  1 Full line readed
+ *  0 Partial read (no error)
+ * -1 End of connection
+ * -2 Buffer too long
+ * -3 Unhandled error
+ */
+int
+unix_socket_client_io_read(struct unix_socket_client *client)
+{
+       char buf[UNIX_SOCKET_CLIENT_BUFFER];
+       ssize_t readed;
+       int res;
+       size_t zi;
+
+       res = 0;
+       readed = unix_socket_read(client->socket, buf, sizeof(buf));
+       if (readed > 0) {
+               client->msg_already_received_bytes += readed;
+               if (dynar_cat(&client->receive_buffer, buf, readed) == -1) {
+                       res = -2;
+                       goto exit_err;
+               }
+
+               for (zi = 0; zi < (size_t)readed; zi++) {
+                       if (buf[zi] == '\n') {
+                               res = 1;
+                       }
+               }
+       }
+
+       if (readed == 0) {
+               res = -1;
+       }
+
+       if (readed < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
+               res = -3;
+       }
+
+exit_err:
+       return (res);
+}
+
+/*
+ *  1 All data succesfully sent
+ *  0 Partial send (no error)
+ * -1 End of connection
+ * -2 Unhandled error
+ */
+int
+unix_socket_client_io_write(struct unix_socket_client *client)
+{
+       ssize_t sent;
+       size_t to_send;
+       int res;
+
+       res = 0;
+
+       to_send = dynar_size(&client->send_buffer) - client->msg_already_sent_bytes;
+       if (to_send > UNIX_SOCKET_CLIENT_BUFFER) {
+               to_send = UNIX_SOCKET_CLIENT_BUFFER;
+       }
+
+       sent = unix_socket_write(client->socket,
+           dynar_data(&client->send_buffer) + client->msg_already_sent_bytes,
+           to_send);
+
+       if (sent > 0) {
+               client->msg_already_sent_bytes += sent;
+
+               if (client->msg_already_sent_bytes == dynar_size(&client->send_buffer)) {
+                       return (1);
+               }
+       }
+
+       if (sent == 0) {
+               res = -1;
+       }
+
+       if (sent < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
+               res = -2;
+       }
+
+       return (res);
+}
diff --git a/qdevices/unix-socket-client.h b/qdevices/unix-socket-client.h
new file mode 100644 (file)
index 0000000..6a758ff
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _UNIX_SOCKET_CLIENT_H_
+#define _UNIX_SOCKET_CLIENT_H_
+
+#include <sys/types.h>
+
+#include <sys/queue.h>
+#include <inttypes.h>
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct unix_socket_client {
+       int socket;
+       struct dynar receive_buffer;
+       struct dynar send_buffer;
+       size_t msg_already_received_bytes;
+       size_t msg_already_sent_bytes;
+       int reading_line;
+       int writing_buffer;
+       int schedule_disconnect;
+       void *user_data;
+       TAILQ_ENTRY(unix_socket_client) entries;
+};
+
+extern void            unix_socket_client_init(struct unix_socket_client *client, int sock,
+    size_t max_receive_size, size_t max_send_size, void *user_data);
+
+extern void            unix_socket_client_destroy(struct unix_socket_client *client);
+
+extern void            unix_socket_client_read_line(struct unix_socket_client *client, int enabled);
+
+extern void            unix_socket_client_write_buffer(struct unix_socket_client *client, int enabled);
+
+extern int             unix_socket_client_io_read(struct unix_socket_client *client);
+
+extern int             unix_socket_client_io_write(struct unix_socket_client *client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UNIX_SOCKET_CLIENT_H_ */
diff --git a/qdevices/unix-socket-ipc.c b/qdevices/unix-socket-ipc.c
new file mode 100644 (file)
index 0000000..a06dd10
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "unix-socket.h"
+#include "unix-socket-ipc.h"
+
+int
+unix_socket_ipc_init(struct unix_socket_ipc *ipc, const char *socket_file_name, int backlog,
+    size_t max_clients, size_t max_receive_size, size_t max_send_size)
+{
+
+       memset(ipc, 0, sizeof(*ipc));
+
+       ipc->socket_file_name = strdup(socket_file_name);
+       if (ipc->socket_file_name == NULL) {
+               return (-1);
+       }
+
+       unix_socket_client_list_init(&ipc->clients);
+
+       ipc->backlog = backlog;
+       ipc->socket = unix_socket_server_create(ipc->socket_file_name, 1,
+               backlog);
+       if (ipc->socket < 0) {
+               free(ipc->socket_file_name);
+               return (-1);
+       }
+
+       ipc->max_clients = max_clients;
+       ipc->max_receive_size = max_receive_size;
+       ipc->max_send_size = max_send_size;
+
+       return (0);
+}
+
+int
+unix_socket_ipc_close(struct unix_socket_ipc *ipc)
+{
+       int res;
+
+       res = 0;
+
+       if (ipc->socket < 0) {
+               return (0);
+       }
+
+       if (unix_socket_server_destroy(ipc->socket, ipc->socket_file_name) != 0) {
+               res = -1;
+       }
+
+       free(ipc->socket_file_name);
+       ipc->socket_file_name = NULL;
+
+       ipc->socket = -1;
+
+       return (res);
+}
+
+int
+unix_socket_ipc_is_closed(struct unix_socket_ipc *ipc)
+{
+
+       return (ipc->socket < 0);
+}
+
+int
+unix_socket_ipc_destroy(struct unix_socket_ipc *ipc)
+{
+
+       if (unix_socket_ipc_close(ipc) != 0) {
+               return (-1);
+       }
+
+       unix_socket_client_list_free(&ipc->clients);
+
+       return (0);
+}
+
+/*
+ *  0 = No error
+ * -1 = Can't accept connection (errno set)
+ * -2 = Too much clients
+ * -3 = Can't add client to list
+ */
+int
+unix_socket_ipc_accept(struct unix_socket_ipc *ipc, struct unix_socket_client **res_client)
+{
+       int client_sock;
+       struct unix_socket_client *client;
+
+       if ((client_sock = unix_socket_server_accept(ipc->socket, 1)) < 0) {
+               return (-1);
+       }
+
+       if (ipc->max_clients != 0 &&
+           unix_socket_client_list_no_clients(&ipc->clients) >= ipc->max_clients) {
+               unix_socket_close(client_sock);
+
+               return (-2);
+       }
+
+       client = unix_socket_client_list_add(&ipc->clients, client_sock, ipc->max_receive_size,
+           ipc->max_send_size, NULL);
+       if (client == NULL) {
+               unix_socket_close(client_sock);
+
+               return (-3);
+       }
+
+       *res_client = client;
+
+       return (0);
+}
+
+void
+unix_socket_ipc_client_disconnect(struct unix_socket_ipc *ipc, struct unix_socket_client *client)
+{
+
+       unix_socket_close(client->socket);
+       unix_socket_client_list_del(&ipc->clients, client);
+}
diff --git a/qdevices/unix-socket-ipc.h b/qdevices/unix-socket-ipc.h
new file mode 100644 (file)
index 0000000..540cc3a
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _UNIX_SOCKET_IPC_H_
+#define _UNIX_SOCKET_IPC_H_
+
+#include "unix-socket-client.h"
+#include "unix-socket-client-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct unix_socket_ipc {
+       int socket;
+       int backlog;
+       char *socket_file_name;
+       struct unix_socket_client_list clients;
+       size_t max_clients;
+       size_t max_receive_size;
+       size_t max_send_size;
+};
+
+extern int             unix_socket_ipc_init(struct unix_socket_ipc *ipc,
+    const char *socket_file_name, int backlog, size_t max_clients, size_t max_receive_size,
+    size_t max_send_size);
+
+extern int             unix_socket_ipc_destroy(struct unix_socket_ipc *ipc);
+
+extern int             unix_socket_ipc_accept(struct unix_socket_ipc *ipc,
+    struct unix_socket_client **res_client);
+
+void                   unix_socket_ipc_client_disconnect(struct unix_socket_ipc *ipc,
+    struct unix_socket_client *client);
+
+extern int             unix_socket_ipc_close(struct unix_socket_ipc *ipc);
+
+extern int             unix_socket_ipc_is_closed(struct unix_socket_ipc *ipc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UNIX_SOCKET_IPC_H_ */
diff --git a/qdevices/unix-socket.c b/qdevices/unix-socket.c
new file mode 100644 (file)
index 0000000..464eae7
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "unix-socket.h"
+#include "utils.h"
+
+int
+unix_socket_server_create(const char *path, int non_blocking, int backlog)
+{
+       int s;
+       struct sockaddr_un sun;
+
+       if (strlen(path) >= sizeof(sun.sun_path)) {
+               errno = ENAMETOOLONG;
+               return (-1);
+       }
+
+       if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+               return (-1);
+       }
+
+       memset(&sun, 0, sizeof(sun));
+       sun.sun_family = AF_UNIX;
+
+       strncpy(sun.sun_path, path, strlen(path));
+       unlink(path);
+       if (bind(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) != 0) {
+               close(s);
+
+               return (-1);
+       }
+
+       if (non_blocking) {
+               if (utils_fd_set_non_blocking(s) != 0) {
+                       close(s);
+
+                       return (-1);
+               }
+       }
+
+       if (listen(s, backlog) != 0) {
+               close(s);
+
+               return (-1);
+       }
+
+       return (s);
+}
+
+int
+unix_socket_client_create(const char *path, int non_blocking)
+{
+       int s;
+       struct sockaddr_un sun;
+
+       if (strlen(path) >= sizeof(sun.sun_path)) {
+               errno = ENAMETOOLONG;
+               return (-1);
+       }
+
+       if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+               return (-1);
+       }
+
+       memset(&sun, 0, sizeof(sun));
+       sun.sun_family = AF_UNIX;
+
+       strncpy(sun.sun_path, path, strlen(path));
+
+       if (non_blocking) {
+               if (utils_fd_set_non_blocking(s) != 0) {
+                       close(s);
+
+                       return (-1);
+               }
+       }
+
+       if (connect(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) != 0) {
+               close(s);
+
+               return (-1);
+       }
+
+       return (s);
+}
+
+
+
+int
+unix_socket_server_destroy(int sock, const char *path)
+{
+       int res;
+
+       res = 0;
+
+       if (close(sock) != 0) {
+               res = -1;
+       }
+
+       if (unlink(path) != 0) {
+               res = -1;
+       }
+
+       return (res);
+}
+
+int
+unix_socket_server_accept(int sock, int non_blocking)
+{
+       struct sockaddr_un sun;
+       socklen_t sun_len;
+       int client_sock;
+
+       sun_len = sizeof(sun);
+       if ((client_sock = accept(sock, (struct sockaddr *)&sun, &sun_len)) < 0) {
+               return (-1);
+       }
+
+       if (non_blocking) {
+               if (utils_fd_set_non_blocking(client_sock) != 0) {
+                       close(client_sock);
+
+                       return (-1);
+               }
+       }
+
+       return (client_sock);
+}
+
+int
+unix_socket_close(int sock)
+{
+
+       return (close(sock));
+}
+
+ssize_t
+unix_socket_read(int sock, void *buf, size_t len)
+{
+
+       return (recv(sock, buf, len, 0));
+}
+
+ssize_t
+unix_socket_write(int sock, void *buf, size_t len)
+{
+
+       return (send(sock, buf, len, 0));
+}
diff --git a/qdevices/unix-socket.h b/qdevices/unix-socket.h
new file mode 100644 (file)
index 0000000..8583feb
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _UNIX_SOCKET_H_
+#define _UNIX_SOCKET_H_
+
+#include <string.h>
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             unix_socket_server_create(const char *path, int non_blocking, int backlog);
+
+extern int             unix_socket_client_create(const char *path, int non_blocking);
+
+extern int             unix_socket_server_destroy(int sock, const char *path);
+
+extern int             unix_socket_server_accept(int sock, int non_blocking);
+
+extern int             unix_socket_close(int sock);
+
+extern ssize_t         unix_socket_read(int sock, void *buf, size_t len);
+
+extern ssize_t         unix_socket_write(int sock, void *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UNIX_SOCKET_H_ */
diff --git a/qdevices/utils.c b/qdevices/utils.c
new file mode 100644 (file)
index 0000000..9574c96
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <arpa/inet.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <syslog.h>
+
+#include "utils.h"
+
+/*
+ * Check string to value on, off, yes, no, 0, 1. Return 1 if value is on, yes or 1, 0 if
+ * value is off, no or 0 and -1 otherwise.
+ */
+int
+utils_parse_bool_str(const char *str)
+{
+
+       if (strcasecmp(str, "yes") == 0 ||
+           strcasecmp(str, "on") == 0 ||
+           strcasecmp(str, "1") == 0) {
+               return (1);
+       } else if (strcasecmp(str, "no") == 0 ||
+           strcasecmp(str, "off") == 0 ||
+           strcasecmp(str, "0") == 0) {
+               return (0);
+       }
+
+       return (-1);
+}
+
+int
+utils_flock(const char *lockfile, pid_t pid, int *another_instance_running)
+{
+       struct flock lock;
+       char pid_s[17];
+       int fd_flag;
+       int lf;
+       char *dname;
+
+       *another_instance_running = 0;
+
+       /*
+        * lockfile directory may not exists. Creation of directory should
+        * be handled by initscript/tmpfiles.d. But as a last chance it
+        * make sense to try to create it here.
+        */
+       dname = strdup(lockfile);
+       if (dname != NULL) {
+               (void)mkdir(dirname(dname), 0770);
+               free(dname);
+       }
+
+       lf = open(lockfile, O_WRONLY | O_CREAT, 0640);
+       if (lf == -1) {
+               return (-1);
+       }
+
+retry_fcntl:
+       lock.l_type = F_WRLCK;
+       lock.l_start = 0;
+       lock.l_whence = SEEK_SET;
+       lock.l_len = 0;
+       if (fcntl(lf, F_SETLK, &lock) == -1) {
+               switch (errno) {
+               case EINTR:
+                       goto retry_fcntl;
+                       break;
+               case EAGAIN:
+               case EACCES:
+                       *another_instance_running = 1;
+                       goto error_close;
+                       break;
+               default:
+                       goto error_close;
+                       break;
+               }
+       }
+
+       if (ftruncate(lf, 0) == -1) {
+               goto error_close_unlink;
+       }
+
+       memset(pid_s, 0, sizeof(pid_s));
+       snprintf(pid_s, sizeof(pid_s) - 1, "%u\n", pid);
+
+retry_write:
+       if (write(lf, pid_s, strlen(pid_s)) != (ssize_t)strlen(pid_s)) {
+               if (errno == EINTR) {
+                       goto retry_write;
+               } else {
+                       goto error_close_unlink;
+               }
+       }
+
+       if ((fd_flag = fcntl(lf, F_GETFD, 0)) == -1) {
+               goto error_close_unlink;
+       }
+       fd_flag |= FD_CLOEXEC;
+       if (fcntl(lf, F_SETFD, fd_flag) == -1) {
+               goto error_close_unlink;
+       }
+
+       return (lf);
+
+error_close_unlink:
+       unlink(lockfile);
+error_close:
+       close(lf);
+
+       return (-1);
+}
+
+void
+utils_tty_detach(void)
+{
+       int devnull;
+
+       switch (fork()) {
+               case -1:
+                       err(1, "Can't create child process");
+                       break;
+               case 0:
+                       break;
+               default:
+                       exit(0);
+                       break;
+       }
+
+       /* Create new session */
+       (void)setsid();
+
+       /*
+        * Map stdin/out/err to /dev/null.
+        */
+       devnull = open("/dev/null", O_RDWR);
+       if (devnull == -1) {
+               err(1, "Can't open /dev/null");
+       }
+
+       if (dup2(devnull, 0) < 0 || dup2(devnull, 1) < 0
+           || dup2(devnull, 2) < 0) {
+               close(devnull);
+               err(1, "Can't dup2 stdin/out/err to /dev/null");
+       }
+       close(devnull);
+}
+
+int
+utils_fd_set_non_blocking(int fd)
+{
+       int flags;
+
+       flags = fcntl(fd, F_GETFL, NULL);
+
+       if (flags < 0) {
+               return (-1);
+       }
+
+       flags |= O_NONBLOCK;
+       if (fcntl(fd, F_SETFL, flags) < 0) {
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/qdevices/utils.h b/qdevices/utils.h
new file mode 100644 (file)
index 0000000..fe9b5b4
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#define UTILS_PRI_NODE_ID              "%" PRIu32
+#define UTILS_PRI_DATACENTER_ID                "%" PRIu32
+/*
+#define UTILS_PRI_NODE_ID              "0x%" PRIx32
+#define UTILS_PRI_DATACENTER_ID                "0x%" PRIx32
+*/
+#define UTILS_PRI_MSG_SEQ              "%" PRIu32
+#define UTILS_PRI_RING_ID              "%" PRIx32 ".%" PRIx64
+#define UTILS_PRI_CONFIG_VERSION       "%" PRIu64
+#define UTILS_PRI_EXPECTED_VOTES       "%" PRIu32
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int             utils_parse_bool_str(const char *str);
+
+extern int             utils_flock(const char *lockfile, pid_t pid,
+    int *another_instance_running);
+
+extern void            utils_tty_detach(void);
+
+extern int             utils_fd_set_non_blocking(int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UTILS_H_ */