ソースを参照

Initial import from corosync codebase

Used the code from corosync master
(31ddba64a2726bcedf81eb84df2e2da4846832f7)

Signed-off-by: Jan Friesse <jfriesse@redhat.com>
Jan Friesse 8 年 前
コミット
9a1955a7d6
100 ファイル変更17026 行追加0 行削除
  1. 33 0
      .gitignore
  2. 58 0
      LICENSE
  3. 156 0
      Makefile.am
  4. 33 0
      README
  5. 5 0
      autogen.sh
  6. 161 0
      build-aux/git-version-gen
  7. 191 0
      build-aux/gitlog-to-changelog
  8. 75 0
      build-aux/release.mk
  9. 368 0
      configure.ac
  10. 212 0
      corosync-qdevice.spec.in
  11. 8 0
      init/.gitignore
  12. 83 0
      init/Makefile.am
  13. 164 0
      init/corosync-qdevice.in
  14. 17 0
      init/corosync-qdevice.service.in
  15. 6 0
      init/corosync-qdevice.sysconfig.example
  16. 171 0
      init/corosync-qnetd.in
  17. 19 0
      init/corosync-qnetd.service.in
  18. 13 0
      init/corosync-qnetd.sysconfig.example
  19. 2 0
      man/.gitignore
  20. 92 0
      man/Makefile.am
  21. 85 0
      man/corosync-qdevice-net-certutil.8
  22. 130 0
      man/corosync-qdevice-tool.8
  23. 461 0
      man/corosync-qdevice.8
  24. 73 0
      man/corosync-qnetd-certutil.8
  25. 128 0
      man/corosync-qnetd-tool.8
  26. 227 0
      man/corosync-qnetd.8
  27. 7 0
      qdevices/.gitignore
  28. 180 0
      qdevices/Makefile.am
  29. 404 0
      qdevices/corosync-qdevice-net-certutil.sh
  30. 281 0
      qdevices/corosync-qdevice-tool.c
  31. 298 0
      qdevices/corosync-qdevice.c
  32. 215 0
      qdevices/corosync-qnetd-certutil.sh
  33. 310 0
      qdevices/corosync-qnetd-tool.c
  34. 662 0
      qdevices/corosync-qnetd.c
  35. 129 0
      qdevices/dynar-getopt-lex.c
  36. 61 0
      qdevices/dynar-getopt-lex.h
  37. 203 0
      qdevices/dynar-simple-lex.c
  38. 68 0
      qdevices/dynar-simple-lex.h
  39. 169 0
      qdevices/dynar-str.c
  40. 66 0
      qdevices/dynar-str.h
  41. 183 0
      qdevices/dynar.c
  42. 81 0
      qdevices/dynar.h
  43. 1095 0
      qdevices/msg.c
  44. 212 0
      qdevices/msg.h
  45. 218 0
      qdevices/msgio.c
  46. 60 0
      qdevices/msgio.h
  47. 211 0
      qdevices/node-list.c
  48. 91 0
      qdevices/node-list.h
  49. 479 0
      qdevices/nss-sock.c
  50. 90 0
      qdevices/nss-sock.h
  51. 156 0
      qdevices/pr-poll-array.c
  52. 78 0
      qdevices/pr-poll-array.h
  53. 621 0
      qdevices/process-list.c
  54. 120 0
      qdevices/process-list.h
  55. 357 0
      qdevices/qdevice-advanced-settings.c
  56. 98 0
      qdevices/qdevice-advanced-settings.h
  57. 508 0
      qdevices/qdevice-cmap.c
  58. 77 0
      qdevices/qdevice-cmap.h
  59. 116 0
      qdevices/qdevice-config.h
  60. 56 0
      qdevices/qdevice-heuristics-cmd-str.h
  61. 353 0
      qdevices/qdevice-heuristics-cmd.c
  62. 60 0
      qdevices/qdevice-heuristics-cmd.h
  63. 209 0
      qdevices/qdevice-heuristics-exec-list.c
  64. 88 0
      qdevices/qdevice-heuristics-exec-list.h
  65. 48 0
      qdevices/qdevice-heuristics-exec-result.c
  66. 65 0
      qdevices/qdevice-heuristics-exec-result.h
  67. 60 0
      qdevices/qdevice-heuristics-instance.c
  68. 82 0
      qdevices/qdevice-heuristics-instance.h
  69. 151 0
      qdevices/qdevice-heuristics-io.c
  70. 56 0
      qdevices/qdevice-heuristics-io.h
  71. 173 0
      qdevices/qdevice-heuristics-log.c
  72. 51 0
      qdevices/qdevice-heuristics-log.h
  73. 51 0
      qdevices/qdevice-heuristics-mode.c
  74. 55 0
      qdevices/qdevice-heuristics-mode.h
  75. 134 0
      qdevices/qdevice-heuristics-result-notifier.c
  76. 87 0
      qdevices/qdevice-heuristics-result-notifier.h
  77. 384 0
      qdevices/qdevice-heuristics-worker-cmd.c
  78. 56 0
      qdevices/qdevice-heuristics-worker-cmd.h
  79. 69 0
      qdevices/qdevice-heuristics-worker-instance.h
  80. 96 0
      qdevices/qdevice-heuristics-worker-log.c
  81. 54 0
      qdevices/qdevice-heuristics-worker-log.h
  82. 353 0
      qdevices/qdevice-heuristics-worker.c
  83. 55 0
      qdevices/qdevice-heuristics-worker.h
  84. 372 0
      qdevices/qdevice-heuristics.c
  85. 70 0
      qdevices/qdevice-heuristics.h
  86. 297 0
      qdevices/qdevice-instance.c
  87. 136 0
      qdevices/qdevice-instance.h
  88. 279 0
      qdevices/qdevice-ipc-cmd.c
  89. 52 0
      qdevices/qdevice-ipc-cmd.h
  90. 335 0
      qdevices/qdevice-ipc.c
  91. 80 0
      qdevices/qdevice-ipc.h
  92. 56 0
      qdevices/qdevice-log-debug.c
  93. 50 0
      qdevices/qdevice-log-debug.h
  94. 323 0
      qdevices/qdevice-log.c
  95. 65 0
      qdevices/qdevice-log.h
  96. 686 0
      qdevices/qdevice-model-net.c
  97. 82 0
      qdevices/qdevice-model-net.h
  98. 51 0
      qdevices/qdevice-model-type.h
  99. 257 0
      qdevices/qdevice-model.c
  100. 114 0
      qdevices/qdevice-model.h

+ 33 - 0
.gitignore

@@ -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

+ 58 - 0
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/>.

+ 156 - 0
Makefile.am

@@ -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

+ 33 - 0
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

+ 5 - 0
autogen.sh

@@ -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

+ 161 - 0
build-aux/git-version-gen

@@ -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:

+ 191 - 0
build-aux/gitlog-to-changelog

@@ -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:

+ 75 - 0
build-aux/release.mk

@@ -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-*

+ 368 - 0
configure.ac

@@ -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}])

+ 212 - 0
corosync-qdevice.spec.in

@@ -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

+ 8 - 0
init/.gitignore

@@ -0,0 +1,8 @@
+corosync
+corosync-notifyd
+corosync.service
+corosync-notifyd.service
+corosync-qnetd
+corosync-qnetd.service
+corosync-qdevice
+corosync-qdevice.service

+ 83 - 0
init/Makefile.am

@@ -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)

+ 164 - 0
init/corosync-qdevice.in

@@ -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

+ 17 - 0
init/corosync-qdevice.service.in

@@ -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

+ 6 - 0
init/corosync-qdevice.sysconfig.example

@@ -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=""

+ 171 - 0
init/corosync-qnetd.in

@@ -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

+ 19 - 0
init/corosync-qnetd.service.in

@@ -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

+ 13 - 0
init/corosync-qnetd.sysconfig.example

@@ -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=""

+ 2 - 0
man/.gitignore

@@ -0,0 +1,2 @@
+*.html
+*.3

+ 92 - 0
man/Makefile.am

@@ -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

+ 85 - 0
man/corosync-qdevice-net-certutil.8

@@ -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

+ 130 - 0
man/corosync-qdevice-tool.8

@@ -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

+ 461 - 0
man/corosync-qdevice.8

@@ -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

+ 73 - 0
man/corosync-qnetd-certutil.8

@@ -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

+ 128 - 0
man/corosync-qnetd-tool.8

@@ -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

+ 227 - 0
man/corosync-qnetd.8

@@ -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

+ 7 - 0
qdevices/.gitignore

@@ -0,0 +1,7 @@
+corosync-qdevice
+corosync-qdevice-tool
+corosync-qnetd-certutil
+corosync-qdevice-net-certutil
+corosync-qnetd
+corosync-qnetd-tool
+*.test

+ 180 - 0
qdevices/Makefile.am

@@ -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)

+ 404 - 0
qdevices/corosync-qdevice-net-certutil.sh

@@ -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

+ 281 - 0
qdevices/corosync-qdevice-tool.c

@@ -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);
+}

+ 298 - 0
qdevices/corosync-qdevice.c

@@ -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);
+}

+ 215 - 0
qdevices/corosync-qnetd-certutil.sh

@@ -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

+ 310 - 0
qdevices/corosync-qnetd-tool.c

@@ -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);
+}

+ 662 - 0
qdevices/corosync-qnetd.c

@@ -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);
+}

+ 129 - 0
qdevices/dynar-getopt-lex.c

@@ -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);
+}

+ 61 - 0
qdevices/dynar-getopt-lex.h

@@ -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_ */

+ 203 - 0
qdevices/dynar-simple-lex.c

@@ -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);
+}

+ 68 - 0
qdevices/dynar-simple-lex.h

@@ -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_ */

+ 169 - 0
qdevices/dynar-str.c

@@ -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));
+}

+ 66 - 0
qdevices/dynar-str.h

@@ -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_ */

+ 183 - 0
qdevices/dynar.c

@@ -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);
+}

+ 81 - 0
qdevices/dynar.h

@@ -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_ */

+ 1095 - 0
qdevices/msg.c

@@ -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");
+}

+ 212 - 0
qdevices/msg.h

@@ -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_ */

+ 218 - 0
qdevices/msgio.c

@@ -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);
+}

+ 60 - 0
qdevices/msgio.h

@@ -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_ */

+ 211 - 0
qdevices/node-list.c

@@ -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);
+}

+ 91 - 0
qdevices/node-list.h

@@ -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_ */

+ 479 - 0
qdevices/nss-sock.c

@@ -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);
+}

+ 90 - 0
qdevices/nss-sock.h

@@ -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_ */

+ 156 - 0
qdevices/pr-poll-array.c

@@ -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));
+}

+ 78 - 0
qdevices/pr-poll-array.h

@@ -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_ */

+ 621 - 0
qdevices/process-list.c

@@ -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);
+}

+ 120 - 0
qdevices/process-list.h

@@ -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_ */

+ 357 - 0
qdevices/qdevice-advanced-settings.c

@@ -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);
+}

+ 98 - 0
qdevices/qdevice-advanced-settings.h

@@ -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_ */

+ 508 - 0
qdevices/qdevice-cmap.c

@@ -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);
+}

+ 77 - 0
qdevices/qdevice-cmap.h

@@ -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_ */

+ 116 - 0
qdevices/qdevice-config.h

@@ -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_ */

+ 56 - 0
qdevices/qdevice-heuristics-cmd-str.h

@@ -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_ */

+ 353 - 0
qdevices/qdevice-heuristics-cmd.c

@@ -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);
+}

+ 60 - 0
qdevices/qdevice-heuristics-cmd.h

@@ -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_ */

+ 209 - 0
qdevices/qdevice-heuristics-exec-list.c

@@ -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);
+}

+ 88 - 0
qdevices/qdevice-heuristics-exec-list.h

@@ -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_ */

+ 48 - 0
qdevices/qdevice-heuristics-exec-result.c

@@ -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");
+}

+ 65 - 0
qdevices/qdevice-heuristics-exec-result.h

@@ -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_ */

+ 60 - 0
qdevices/qdevice-heuristics-instance.c

@@ -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);
+}

+ 82 - 0
qdevices/qdevice-heuristics-instance.h

@@ -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_ */

+ 151 - 0
qdevices/qdevice-heuristics-io.c

@@ -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);
+}

+ 56 - 0
qdevices/qdevice-heuristics-io.h

@@ -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_ */

+ 173 - 0
qdevices/qdevice-heuristics-log.c

@@ -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);
+}

+ 51 - 0
qdevices/qdevice-heuristics-log.h

@@ -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_ */

+ 51 - 0
qdevices/qdevice-heuristics-mode.c

@@ -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");
+}

+ 55 - 0
qdevices/qdevice-heuristics-mode.h

@@ -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_ */

+ 134 - 0
qdevices/qdevice-heuristics-result-notifier.c

@@ -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);
+}

+ 87 - 0
qdevices/qdevice-heuristics-result-notifier.h

@@ -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_ */

+ 384 - 0
qdevices/qdevice-heuristics-worker-cmd.c

@@ -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);
+}

+ 56 - 0
qdevices/qdevice-heuristics-worker-cmd.h

@@ -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_ */

+ 69 - 0
qdevices/qdevice-heuristics-worker-instance.h

@@ -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_ */

+ 96 - 0
qdevices/qdevice-heuristics-worker-log.c

@@ -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);
+}

+ 54 - 0
qdevices/qdevice-heuristics-worker-log.h

@@ -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_ */

+ 353 - 0
qdevices/qdevice-heuristics-worker.c

@@ -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);
+}

+ 55 - 0
qdevices/qdevice-heuristics-worker.h

@@ -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_ */

+ 372 - 0
qdevices/qdevice-heuristics.c

@@ -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);
+}

+ 70 - 0
qdevices/qdevice-heuristics.h

@@ -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_ */

+ 297 - 0
qdevices/qdevice-instance.c

@@ -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);
+}

+ 136 - 0
qdevices/qdevice-instance.h

@@ -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_ */

+ 279 - 0
qdevices/qdevice-ipc-cmd.c

@@ -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);
+}

+ 52 - 0
qdevices/qdevice-ipc-cmd.h

@@ -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_ */

+ 335 - 0
qdevices/qdevice-ipc.c

@@ -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;
+	}
+}

+ 80 - 0
qdevices/qdevice-ipc.h

@@ -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_ */

+ 56 - 0
qdevices/qdevice-log-debug.c

@@ -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++;
+	}
+}

+ 50 - 0
qdevices/qdevice-log-debug.h

@@ -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_ */

+ 323 - 0
qdevices/qdevice-log.c

@@ -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();
+}

+ 65 - 0
qdevices/qdevice-log.h

@@ -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_ */

+ 686 - 0
qdevices/qdevice-model-net.c

@@ -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));
+}

+ 82 - 0
qdevices/qdevice-model-net.h

@@ -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_ */

+ 51 - 0
qdevices/qdevice-model-type.h

@@ -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_ */

+ 257 - 0
qdevices/qdevice-model.c

@@ -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");
+}

+ 114 - 0
qdevices/qdevice-model.h

@@ -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_ */

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません