From 9a1955a7d623f98c64deb464970a0b96d4a433c0 Mon Sep 17 00:00:00 2001 From: Jan Friesse Date: Mon, 22 Jan 2018 15:59:23 +0100 Subject: [PATCH] Initial import from corosync codebase Used the code from corosync master (31ddba64a2726bcedf81eb84df2e2da4846832f7) Signed-off-by: Jan Friesse --- .gitignore | 33 + LICENSE | 58 + Makefile.am | 156 ++ README | 33 + autogen.sh | 5 + build-aux/git-version-gen | 161 +++ build-aux/gitlog-to-changelog | 191 +++ build-aux/release.mk | 75 + configure.ac | 368 +++++ corosync-qdevice.spec.in | 212 +++ init/.gitignore | 8 + init/Makefile.am | 83 ++ init/corosync-qdevice.in | 164 +++ init/corosync-qdevice.service.in | 17 + init/corosync-qdevice.sysconfig.example | 6 + init/corosync-qnetd.in | 171 +++ init/corosync-qnetd.service.in | 19 + init/corosync-qnetd.sysconfig.example | 13 + man/.gitignore | 2 + man/Makefile.am | 92 ++ man/corosync-qdevice-net-certutil.8 | 85 ++ man/corosync-qdevice-tool.8 | 130 ++ man/corosync-qdevice.8 | 461 ++++++ man/corosync-qnetd-certutil.8 | 73 + man/corosync-qnetd-tool.8 | 128 ++ man/corosync-qnetd.8 | 227 +++ qdevices/.gitignore | 7 + qdevices/Makefile.am | 180 +++ qdevices/corosync-qdevice-net-certutil.sh | 404 ++++++ qdevices/corosync-qdevice-tool.c | 281 ++++ qdevices/corosync-qdevice.c | 298 ++++ qdevices/corosync-qnetd-certutil.sh | 215 +++ qdevices/corosync-qnetd-tool.c | 310 ++++ qdevices/corosync-qnetd.c | 662 +++++++++ qdevices/dynar-getopt-lex.c | 129 ++ qdevices/dynar-getopt-lex.h | 61 + qdevices/dynar-simple-lex.c | 203 +++ qdevices/dynar-simple-lex.h | 68 + qdevices/dynar-str.c | 169 +++ qdevices/dynar-str.h | 66 + qdevices/dynar.c | 183 +++ qdevices/dynar.h | 81 ++ qdevices/msg.c | 1095 ++++++++++++++ qdevices/msg.h | 212 +++ qdevices/msgio.c | 218 +++ qdevices/msgio.h | 60 + qdevices/node-list.c | 211 +++ qdevices/node-list.h | 91 ++ qdevices/nss-sock.c | 479 +++++++ qdevices/nss-sock.h | 90 ++ qdevices/pr-poll-array.c | 156 ++ qdevices/pr-poll-array.h | 78 + qdevices/process-list.c | 621 ++++++++ qdevices/process-list.h | 120 ++ qdevices/qdevice-advanced-settings.c | 357 +++++ qdevices/qdevice-advanced-settings.h | 98 ++ qdevices/qdevice-cmap.c | 508 +++++++ qdevices/qdevice-cmap.h | 77 + qdevices/qdevice-config.h | 116 ++ qdevices/qdevice-heuristics-cmd-str.h | 56 + qdevices/qdevice-heuristics-cmd.c | 353 +++++ qdevices/qdevice-heuristics-cmd.h | 60 + qdevices/qdevice-heuristics-exec-list.c | 209 +++ qdevices/qdevice-heuristics-exec-list.h | 88 ++ qdevices/qdevice-heuristics-exec-result.c | 48 + qdevices/qdevice-heuristics-exec-result.h | 65 + qdevices/qdevice-heuristics-instance.c | 60 + qdevices/qdevice-heuristics-instance.h | 82 ++ qdevices/qdevice-heuristics-io.c | 151 ++ qdevices/qdevice-heuristics-io.h | 56 + qdevices/qdevice-heuristics-log.c | 173 +++ qdevices/qdevice-heuristics-log.h | 51 + qdevices/qdevice-heuristics-mode.c | 51 + qdevices/qdevice-heuristics-mode.h | 55 + qdevices/qdevice-heuristics-result-notifier.c | 134 ++ qdevices/qdevice-heuristics-result-notifier.h | 87 ++ qdevices/qdevice-heuristics-worker-cmd.c | 384 +++++ qdevices/qdevice-heuristics-worker-cmd.h | 56 + qdevices/qdevice-heuristics-worker-instance.h | 69 + qdevices/qdevice-heuristics-worker-log.c | 96 ++ qdevices/qdevice-heuristics-worker-log.h | 54 + qdevices/qdevice-heuristics-worker.c | 353 +++++ qdevices/qdevice-heuristics-worker.h | 55 + qdevices/qdevice-heuristics.c | 372 +++++ qdevices/qdevice-heuristics.h | 70 + qdevices/qdevice-instance.c | 297 ++++ qdevices/qdevice-instance.h | 136 ++ qdevices/qdevice-ipc-cmd.c | 279 ++++ qdevices/qdevice-ipc-cmd.h | 52 + qdevices/qdevice-ipc.c | 335 +++++ qdevices/qdevice-ipc.h | 80 ++ qdevices/qdevice-log-debug.c | 56 + qdevices/qdevice-log-debug.h | 50 + qdevices/qdevice-log.c | 323 +++++ qdevices/qdevice-log.h | 65 + qdevices/qdevice-model-net.c | 686 +++++++++ qdevices/qdevice-model-net.h | 82 ++ qdevices/qdevice-model-type.h | 51 + qdevices/qdevice-model.c | 257 ++++ qdevices/qdevice-model.h | 114 ++ qdevices/qdevice-net-algo-2nodelms.c | 239 ++++ qdevices/qdevice-net-algo-2nodelms.h | 115 ++ qdevices/qdevice-net-algo-ffsplit.c | 325 +++++ qdevices/qdevice-net-algo-ffsplit.h | 115 ++ qdevices/qdevice-net-algo-lms.c | 291 ++++ qdevices/qdevice-net-algo-lms.h | 115 ++ qdevices/qdevice-net-algo-test.c | 457 ++++++ qdevices/qdevice-net-algo-test.h | 115 ++ qdevices/qdevice-net-algorithm.c | 388 +++++ qdevices/qdevice-net-algorithm.h | 165 +++ qdevices/qdevice-net-cast-vote-timer.c | 188 +++ qdevices/qdevice-net-cast-vote-timer.h | 55 + qdevices/qdevice-net-disconnect-reason.h | 152 ++ qdevices/qdevice-net-echo-request-timer.c | 106 ++ qdevices/qdevice-net-echo-request-timer.h | 50 + qdevices/qdevice-net-heuristics.c | 462 ++++++ qdevices/qdevice-net-heuristics.h | 61 + qdevices/qdevice-net-instance.c | 436 ++++++ qdevices/qdevice-net-instance.h | 142 ++ qdevices/qdevice-net-ipc-cmd.c | 273 ++++ qdevices/qdevice-net-ipc-cmd.h | 52 + qdevices/qdevice-net-msg-received.c | 983 +++++++++++++ qdevices/qdevice-net-msg-received.h | 51 + qdevices/qdevice-net-nss.c | 74 + qdevices/qdevice-net-nss.h | 57 + qdevices/qdevice-net-poll-array-user-data.h | 64 + qdevices/qdevice-net-poll.c | 546 +++++++ qdevices/qdevice-net-poll.h | 47 + qdevices/qdevice-net-send.c | 354 +++++ qdevices/qdevice-net-send.h | 73 + qdevices/qdevice-net-socket.c | 213 +++ qdevices/qdevice-net-socket.h | 52 + qdevices/qdevice-net-votequorum.c | 64 + qdevices/qdevice-net-votequorum.h | 57 + qdevices/qdevice-votequorum.c | 390 +++++ qdevices/qdevice-votequorum.h | 65 + qdevices/qnet-config.h | 139 ++ qdevices/qnetd-advanced-settings.c | 213 +++ qdevices/qnetd-advanced-settings.h | 72 + qdevices/qnetd-algo-2nodelms.c | 354 +++++ qdevices/qnetd-algo-2nodelms.h | 84 ++ qdevices/qnetd-algo-ffsplit.c | 885 ++++++++++++ qdevices/qnetd-algo-ffsplit.h | 83 ++ qdevices/qnetd-algo-lms.c | 447 ++++++ qdevices/qnetd-algo-lms.h | 84 ++ qdevices/qnetd-algo-test.c | 284 ++++ qdevices/qnetd-algo-test.h | 84 ++ qdevices/qnetd-algo-utils.c | 179 +++ qdevices/qnetd-algo-utils.h | 68 + qdevices/qnetd-algorithm.c | 232 +++ qdevices/qnetd-algorithm.h | 121 ++ qdevices/qnetd-client-algo-timer.c | 146 ++ qdevices/qnetd-client-algo-timer.h | 57 + qdevices/qnetd-client-list.c | 115 ++ qdevices/qnetd-client-list.h | 67 + qdevices/qnetd-client-msg-received.c | 1253 +++++++++++++++++ qdevices/qnetd-client-msg-received.h | 51 + qdevices/qnetd-client-net.c | 248 ++++ qdevices/qnetd-client-net.h | 59 + qdevices/qnetd-client-send.c | 107 ++ qdevices/qnetd-client-send.h | 56 + qdevices/qnetd-client.c | 72 + qdevices/qnetd-client.h | 107 ++ qdevices/qnetd-cluster-list.c | 139 ++ qdevices/qnetd-cluster-list.h | 72 + qdevices/qnetd-cluster.c | 95 ++ qdevices/qnetd-cluster.h | 72 + qdevices/qnetd-dpd-timer.c | 98 ++ qdevices/qnetd-dpd-timer.h | 52 + qdevices/qnetd-instance.c | 134 ++ qdevices/qnetd-instance.h | 92 ++ qdevices/qnetd-ipc-cmd.c | 307 ++++ qdevices/qnetd-ipc-cmd.h | 55 + qdevices/qnetd-ipc.c | 412 ++++++ qdevices/qnetd-ipc.h | 80 ++ qdevices/qnetd-log-debug.c | 181 +++ qdevices/qnetd-log-debug.h | 82 ++ qdevices/qnetd-log.c | 183 +++ qdevices/qnetd-log.h | 76 + qdevices/qnetd-poll-array-user-data.h | 61 + qdevices/send-buffer-list.c | 180 +++ qdevices/send-buffer-list.h | 95 ++ qdevices/test-dynar-getopt-lex.c | 109 ++ qdevices/test-dynar-simple-lex.c | 242 ++++ qdevices/test-dynar.c | 160 +++ qdevices/test-process-list.c | 517 +++++++ qdevices/test-qnetd-cluster-list.c | 207 +++ qdevices/timer-list.c | 263 ++++ qdevices/timer-list.h | 96 ++ qdevices/tlv.c | 1121 +++++++++++++++ qdevices/tlv.h | 360 +++++ qdevices/unix-socket-client-list.c | 108 ++ qdevices/unix-socket-client-list.h | 69 + qdevices/unix-socket-client.c | 161 +++ qdevices/unix-socket-client.h | 79 ++ qdevices/unix-socket-ipc.c | 155 ++ qdevices/unix-socket-ipc.h | 75 + qdevices/unix-socket.c | 187 +++ qdevices/unix-socket.h | 64 + qdevices/utils.c | 208 +++ qdevices/utils.h | 69 + 201 files changed, 36801 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile.am create mode 100644 README create mode 100755 autogen.sh create mode 100755 build-aux/git-version-gen create mode 100755 build-aux/gitlog-to-changelog create mode 100644 build-aux/release.mk create mode 100644 configure.ac create mode 100644 corosync-qdevice.spec.in create mode 100644 init/.gitignore create mode 100644 init/Makefile.am create mode 100755 init/corosync-qdevice.in create mode 100644 init/corosync-qdevice.service.in create mode 100644 init/corosync-qdevice.sysconfig.example create mode 100755 init/corosync-qnetd.in create mode 100644 init/corosync-qnetd.service.in create mode 100644 init/corosync-qnetd.sysconfig.example create mode 100644 man/.gitignore create mode 100644 man/Makefile.am create mode 100644 man/corosync-qdevice-net-certutil.8 create mode 100644 man/corosync-qdevice-tool.8 create mode 100644 man/corosync-qdevice.8 create mode 100644 man/corosync-qnetd-certutil.8 create mode 100644 man/corosync-qnetd-tool.8 create mode 100644 man/corosync-qnetd.8 create mode 100644 qdevices/.gitignore create mode 100644 qdevices/Makefile.am create mode 100644 qdevices/corosync-qdevice-net-certutil.sh create mode 100644 qdevices/corosync-qdevice-tool.c create mode 100644 qdevices/corosync-qdevice.c create mode 100644 qdevices/corosync-qnetd-certutil.sh create mode 100644 qdevices/corosync-qnetd-tool.c create mode 100644 qdevices/corosync-qnetd.c create mode 100644 qdevices/dynar-getopt-lex.c create mode 100644 qdevices/dynar-getopt-lex.h create mode 100644 qdevices/dynar-simple-lex.c create mode 100644 qdevices/dynar-simple-lex.h create mode 100644 qdevices/dynar-str.c create mode 100644 qdevices/dynar-str.h create mode 100644 qdevices/dynar.c create mode 100644 qdevices/dynar.h create mode 100644 qdevices/msg.c create mode 100644 qdevices/msg.h create mode 100644 qdevices/msgio.c create mode 100644 qdevices/msgio.h create mode 100644 qdevices/node-list.c create mode 100644 qdevices/node-list.h create mode 100644 qdevices/nss-sock.c create mode 100644 qdevices/nss-sock.h create mode 100644 qdevices/pr-poll-array.c create mode 100644 qdevices/pr-poll-array.h create mode 100644 qdevices/process-list.c create mode 100644 qdevices/process-list.h create mode 100644 qdevices/qdevice-advanced-settings.c create mode 100644 qdevices/qdevice-advanced-settings.h create mode 100644 qdevices/qdevice-cmap.c create mode 100644 qdevices/qdevice-cmap.h create mode 100644 qdevices/qdevice-config.h create mode 100644 qdevices/qdevice-heuristics-cmd-str.h create mode 100644 qdevices/qdevice-heuristics-cmd.c create mode 100644 qdevices/qdevice-heuristics-cmd.h create mode 100644 qdevices/qdevice-heuristics-exec-list.c create mode 100644 qdevices/qdevice-heuristics-exec-list.h create mode 100644 qdevices/qdevice-heuristics-exec-result.c create mode 100644 qdevices/qdevice-heuristics-exec-result.h create mode 100644 qdevices/qdevice-heuristics-instance.c create mode 100644 qdevices/qdevice-heuristics-instance.h create mode 100644 qdevices/qdevice-heuristics-io.c create mode 100644 qdevices/qdevice-heuristics-io.h create mode 100644 qdevices/qdevice-heuristics-log.c create mode 100644 qdevices/qdevice-heuristics-log.h create mode 100644 qdevices/qdevice-heuristics-mode.c create mode 100644 qdevices/qdevice-heuristics-mode.h create mode 100644 qdevices/qdevice-heuristics-result-notifier.c create mode 100644 qdevices/qdevice-heuristics-result-notifier.h create mode 100644 qdevices/qdevice-heuristics-worker-cmd.c create mode 100644 qdevices/qdevice-heuristics-worker-cmd.h create mode 100644 qdevices/qdevice-heuristics-worker-instance.h create mode 100644 qdevices/qdevice-heuristics-worker-log.c create mode 100644 qdevices/qdevice-heuristics-worker-log.h create mode 100644 qdevices/qdevice-heuristics-worker.c create mode 100644 qdevices/qdevice-heuristics-worker.h create mode 100644 qdevices/qdevice-heuristics.c create mode 100644 qdevices/qdevice-heuristics.h create mode 100644 qdevices/qdevice-instance.c create mode 100644 qdevices/qdevice-instance.h create mode 100644 qdevices/qdevice-ipc-cmd.c create mode 100644 qdevices/qdevice-ipc-cmd.h create mode 100644 qdevices/qdevice-ipc.c create mode 100644 qdevices/qdevice-ipc.h create mode 100644 qdevices/qdevice-log-debug.c create mode 100644 qdevices/qdevice-log-debug.h create mode 100644 qdevices/qdevice-log.c create mode 100644 qdevices/qdevice-log.h create mode 100644 qdevices/qdevice-model-net.c create mode 100644 qdevices/qdevice-model-net.h create mode 100644 qdevices/qdevice-model-type.h create mode 100644 qdevices/qdevice-model.c create mode 100644 qdevices/qdevice-model.h create mode 100644 qdevices/qdevice-net-algo-2nodelms.c create mode 100644 qdevices/qdevice-net-algo-2nodelms.h create mode 100644 qdevices/qdevice-net-algo-ffsplit.c create mode 100644 qdevices/qdevice-net-algo-ffsplit.h create mode 100644 qdevices/qdevice-net-algo-lms.c create mode 100644 qdevices/qdevice-net-algo-lms.h create mode 100644 qdevices/qdevice-net-algo-test.c create mode 100644 qdevices/qdevice-net-algo-test.h create mode 100644 qdevices/qdevice-net-algorithm.c create mode 100644 qdevices/qdevice-net-algorithm.h create mode 100644 qdevices/qdevice-net-cast-vote-timer.c create mode 100644 qdevices/qdevice-net-cast-vote-timer.h create mode 100644 qdevices/qdevice-net-disconnect-reason.h create mode 100644 qdevices/qdevice-net-echo-request-timer.c create mode 100644 qdevices/qdevice-net-echo-request-timer.h create mode 100644 qdevices/qdevice-net-heuristics.c create mode 100644 qdevices/qdevice-net-heuristics.h create mode 100644 qdevices/qdevice-net-instance.c create mode 100644 qdevices/qdevice-net-instance.h create mode 100644 qdevices/qdevice-net-ipc-cmd.c create mode 100644 qdevices/qdevice-net-ipc-cmd.h create mode 100644 qdevices/qdevice-net-msg-received.c create mode 100644 qdevices/qdevice-net-msg-received.h create mode 100644 qdevices/qdevice-net-nss.c create mode 100644 qdevices/qdevice-net-nss.h create mode 100644 qdevices/qdevice-net-poll-array-user-data.h create mode 100644 qdevices/qdevice-net-poll.c create mode 100644 qdevices/qdevice-net-poll.h create mode 100644 qdevices/qdevice-net-send.c create mode 100644 qdevices/qdevice-net-send.h create mode 100644 qdevices/qdevice-net-socket.c create mode 100644 qdevices/qdevice-net-socket.h create mode 100644 qdevices/qdevice-net-votequorum.c create mode 100644 qdevices/qdevice-net-votequorum.h create mode 100644 qdevices/qdevice-votequorum.c create mode 100644 qdevices/qdevice-votequorum.h create mode 100644 qdevices/qnet-config.h create mode 100644 qdevices/qnetd-advanced-settings.c create mode 100644 qdevices/qnetd-advanced-settings.h create mode 100644 qdevices/qnetd-algo-2nodelms.c create mode 100644 qdevices/qnetd-algo-2nodelms.h create mode 100644 qdevices/qnetd-algo-ffsplit.c create mode 100644 qdevices/qnetd-algo-ffsplit.h create mode 100644 qdevices/qnetd-algo-lms.c create mode 100644 qdevices/qnetd-algo-lms.h create mode 100644 qdevices/qnetd-algo-test.c create mode 100644 qdevices/qnetd-algo-test.h create mode 100644 qdevices/qnetd-algo-utils.c create mode 100644 qdevices/qnetd-algo-utils.h create mode 100644 qdevices/qnetd-algorithm.c create mode 100644 qdevices/qnetd-algorithm.h create mode 100644 qdevices/qnetd-client-algo-timer.c create mode 100644 qdevices/qnetd-client-algo-timer.h create mode 100644 qdevices/qnetd-client-list.c create mode 100644 qdevices/qnetd-client-list.h create mode 100644 qdevices/qnetd-client-msg-received.c create mode 100644 qdevices/qnetd-client-msg-received.h create mode 100644 qdevices/qnetd-client-net.c create mode 100644 qdevices/qnetd-client-net.h create mode 100644 qdevices/qnetd-client-send.c create mode 100644 qdevices/qnetd-client-send.h create mode 100644 qdevices/qnetd-client.c create mode 100644 qdevices/qnetd-client.h create mode 100644 qdevices/qnetd-cluster-list.c create mode 100644 qdevices/qnetd-cluster-list.h create mode 100644 qdevices/qnetd-cluster.c create mode 100644 qdevices/qnetd-cluster.h create mode 100644 qdevices/qnetd-dpd-timer.c create mode 100644 qdevices/qnetd-dpd-timer.h create mode 100644 qdevices/qnetd-instance.c create mode 100644 qdevices/qnetd-instance.h create mode 100644 qdevices/qnetd-ipc-cmd.c create mode 100644 qdevices/qnetd-ipc-cmd.h create mode 100644 qdevices/qnetd-ipc.c create mode 100644 qdevices/qnetd-ipc.h create mode 100644 qdevices/qnetd-log-debug.c create mode 100644 qdevices/qnetd-log-debug.h create mode 100644 qdevices/qnetd-log.c create mode 100644 qdevices/qnetd-log.h create mode 100644 qdevices/qnetd-poll-array-user-data.h create mode 100644 qdevices/send-buffer-list.c create mode 100644 qdevices/send-buffer-list.h create mode 100644 qdevices/test-dynar-getopt-lex.c create mode 100644 qdevices/test-dynar-simple-lex.c create mode 100644 qdevices/test-dynar.c create mode 100644 qdevices/test-process-list.c create mode 100644 qdevices/test-qnetd-cluster-list.c create mode 100644 qdevices/timer-list.c create mode 100644 qdevices/timer-list.h create mode 100644 qdevices/tlv.c create mode 100644 qdevices/tlv.h create mode 100644 qdevices/unix-socket-client-list.c create mode 100644 qdevices/unix-socket-client-list.h create mode 100644 qdevices/unix-socket-client.c create mode 100644 qdevices/unix-socket-client.h create mode 100644 qdevices/unix-socket-ipc.c create mode 100644 qdevices/unix-socket-ipc.h create mode 100644 qdevices/unix-socket.c create mode 100644 qdevices/unix-socket.h create mode 100644 qdevices/utils.c create mode 100644 qdevices/utils.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..118c4f5 --- /dev/null +++ b/.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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..031f06a --- /dev/null +++ b/LICENSE @@ -0,0 +1,58 @@ +----------------------------------------------------------------------------- +The following license applies to every file in this source distribution except +for the files git-version-gen, and gitlog-to-changelog. + +The git* files, which are available under GPLv3 or later, are only used by +our release process to generate text file content and are not part of any +generated binary. +----------------------------------------------------------------------------- + +Copyright (c) 2015-2018 Red Hat, Inc. + +All rights reserved. + +This software licensed under BSD license, the text of which follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +- Neither the name of the Red Hat, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- +The corosync-qdevice project uses software for release processing which generates +changelogs and version information for the software. These programs are not +used by the generated binaries or libraries These files are git-version-gen +and gitlog-to-changelog. +----------------------------------------------------------------------------- +The license for these files is as follows: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..a926e3f --- /dev/null +++ b/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 diff --git a/README b/README new file mode 100644 index 0000000..e479343 --- /dev/null +++ b/README @@ -0,0 +1,33 @@ +Corosync-qdevice +---------------- +corosync-qdevice is a daemon running on each node of a cluster. It provides +a configured number of votes to the quorum subsystem based on a third-party +arbitrator's decision. Its primary use is to allow a cluster to sustain more +node failures than standard quorum rules allow. It is recommended for clusters +with an even number of nodes and highly recommended for 2 node clusters. + +corosync-qnetd is a daemon running outside of the cluster with the purpose +of providing a vote to the corosync-qdevice model net. It's designed to +support multiple clusters and be almost configuration and state free. +New clusters are handled dynamically and no configuration file exists. +It's also able to run as non-root user - which is recommended. +Connection between the corosync-qdevice model net client can be optionally +configured with TLS client certificate checking. The communication protocol +between server and client is designed to be very simple and allow +backwards compatibility. + +Originally both qdevice and qnetd were part of the Corosync codebase +(https://github.com/corosync/corosync) but because it's got quite big we +decided to split it into it's own sub project. + +Dependencies +------------ +* Corosync >= 2.0 +* NSS + +Installation +------------ +$ ./autogen.sh +$ ./configure +$ make +$ sudo make install diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..35f44b3 --- /dev/null +++ b/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 diff --git a/build-aux/git-version-gen b/build-aux/git-version-gen new file mode 100755 index 0000000..795a98b --- /dev/null +++ b/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 . + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1|2) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version" \ + '[TAG-NORMALIZATION-SED-SCRIPT]' + exit 1;; +esac + +tarball_version_file=$1 +tag_sed_script="${2:-s/x/x/}" +nl=' +' + +# Avoid meddling by environment variable of the same name. +v= + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +# Otherwise, if there is at least one git commit involving the working +# directory, and "git describe" output looks sensible, use that to +# derive a version string. +elif test "`git log -1 --pretty=format:x . 2>&1`" = x \ + && v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && v=`printf '%s\n' "$v" | sed "$tag_sed_script"` \ + && case $v in + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git update-index --refresh > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d "$nl" + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/gitlog-to-changelog b/build-aux/gitlog-to-changelog new file mode 100755 index 0000000..7660af5 --- /dev/null +++ b/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 . + +# 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 < 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 = ) + 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 = ) + or last; + $in ne "\n" + and die "$ME:$.: unexpected line:\n$in"; + } + + close PIPE + or die "$ME: error closing pipe from " . quoted_cmd (@cmd) . "\n"; + # FIXME-someday: include $PROCESS_STATUS in the diagnostic +} + +# Local Variables: +# mode: perl +# indent-tabs-mode: nil +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "my $VERSION = '" +# time-stamp-format: "%:y-%02m-%02d %02H:%02M" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "'; # UTC" +# End: diff --git a/build-aux/release.mk b/build-aux/release.mk new file mode 100644 index 0000000..739821a --- /dev/null +++ b/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-* diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..0b1ff0d --- /dev/null +++ b/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}]) diff --git a/corosync-qdevice.spec.in b/corosync-qdevice.spec.in new file mode 100644 index 0000000..eace00d --- /dev/null +++ b/corosync-qdevice.spec.in @@ -0,0 +1,212 @@ +@ALPHATAG@ +@NUMCOMM@ +@DIRTY@ + +# Conditionals +# Invoke "rpmbuild --without " or "rpmbuild --with " +# 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 - @version@-1-@numcomm@.@alphatag@.@dirty@ +- Autotools generated version diff --git a/init/.gitignore b/init/.gitignore new file mode 100644 index 0000000..4bac5c3 --- /dev/null +++ b/init/.gitignore @@ -0,0 +1,8 @@ +corosync +corosync-notifyd +corosync.service +corosync-notifyd.service +corosync-qnetd +corosync-qnetd.service +corosync-qdevice +corosync-qdevice.service diff --git a/init/Makefile.am b/init/Makefile.am new file mode 100644 index 0000000..453ade9 --- /dev/null +++ b/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) diff --git a/init/corosync-qdevice.in b/init/corosync-qdevice.in new file mode 100755 index 0000000..581b6e6 --- /dev/null +++ b/init/corosync-qdevice.in @@ -0,0 +1,164 @@ +#!@BASHPATH@ + +# Authors: +# Jan Friesse +# +# License: Revised BSD + +# chkconfig: - 20 80 +# description: Corosync Qdevice daemon +# processname: corosync-qdevice +# +### BEGIN INIT INFO +# Provides: corosync-qdevice +# Required-Start: corosync +# Required-Stop: corosync +# Default-Start: +# Default-Stop: +# Short-Description: Starts and stops Corosync Qdevice daemon. +# Description: Starts and stops Corosync Qdevice daemon. +### END INIT INFO + +desc="Corosync Qdevice daemon" +prog="corosync-qdevice" + +# set secure PATH +PATH="/sbin:/bin:/usr/sbin:/usr/bin:@SBINDIR@" + +success() +{ + echo -ne "[ OK ]\r" +} + +failure() +{ + echo -ne "[FAILED]\r" +} + +status() +{ + pid=$(pidof $1 2>/dev/null) + res=$? + if [ $res -ne 0 ]; then + echo "$1 is stopped" + else + echo "$1 (pid $pid) is running..." + fi + return $res +} + +[ -f @INITCONFIGDIR@/$prog ] && . @INITCONFIGDIR@/$prog + +case '@INITCONFIGDIR@' in + */sysconfig) # rpm based distros + [ -f @INITDDIR@/functions ] && . @INITDDIR@/functions + [ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/subsys/$prog";; + */default) # deb based distros + [ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/$prog";; +esac + +# The version of __pids_pidof in /etc/init.d/functions calls pidof with -x +# This means it matches scripts, including this one. +# Redefine it here so that status (from the same file) works. +# Otherwise simultaneous calls to stop() will loop forever +__pids_pidof() { + pidof -c -o $$ -o $PPID -o %PPID "$1" || \ + pidof -c -o $$ -o $PPID -o %PPID "${1##*/}" +} + +cluster_disabled_at_boot() +{ + if grep -q nocluster /proc/cmdline && \ + [ "$(tty)" = "/dev/console" ]; then + echo -e "not configured to run at boot" + failure + return 1 + fi + return 0 +} + +start() +{ + echo -n "Starting $desc ($prog): " + + ! cluster_disabled_at_boot && return + + # most recent distributions use tmpfs for @LOCALSTATEDIR@/run + # to avoid to clean it up on every boot. + # they also assume that init scripts will create + # required subdirectories for proper operations + if [ ! -d "@LOCALSTATEDIR@/run/corosync-qdevice" ];then + mkdir -p "@LOCALSTATEDIR@/run/corosync-qdevice" + chmod 0770 "@LOCALSTATEDIR@/run/corosync-qdevice" + fi + + if status $prog > /dev/null 2>&1; then + success + else + $prog $COROSYNC_QDEVICE_OPTIONS > /dev/null 2>&1 + + if [ "$?" != 0 ]; then + failure + rtrn=1 + else + touch $LOCK_FILE + success + fi + fi + echo +} + +stop() +{ + ! status $prog > /dev/null 2>&1 && return + + echo -n "Signaling $desc ($prog) to terminate: " + kill -TERM $(pidof $prog) > /dev/null 2>&1 + success + echo + + echo -n "Waiting for $prog services to unload:" + while status $prog > /dev/null 2>&1; do + sleep 1 + echo -n "." + done + + rm -f $LOCK_FILE + success + echo +} + +restart() +{ + stop + start +} + +rtrn=0 + +case "$1" in +start) + start +;; +restart|reload|force-reload) + restart +;; +condrestart|try-restart) + if status $prog > /dev/null 2>&1; then + restart + fi +;; +status) + status $prog + rtrn=$? +;; +stop) + stop +;; +*) + echo "usage: $0 {start|stop|restart|reload|force-reload|condrestart|try-restart|status}" + rtrn=2 +;; +esac + +exit $rtrn diff --git a/init/corosync-qdevice.service.in b/init/corosync-qdevice.service.in new file mode 100644 index 0000000..18bc60b --- /dev/null +++ b/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 diff --git a/init/corosync-qdevice.sysconfig.example b/init/corosync-qdevice.sysconfig.example new file mode 100644 index 0000000..8cac818 --- /dev/null +++ b/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="" diff --git a/init/corosync-qnetd.in b/init/corosync-qnetd.in new file mode 100755 index 0000000..681ea08 --- /dev/null +++ b/init/corosync-qnetd.in @@ -0,0 +1,171 @@ +#!@BASHPATH@ + +# Authors: +# Jan Friesse +# +# License: Revised BSD + +# chkconfig: - 20 80 +# description: Corosync Qdevice Network daemon +# processname: corosync-qnetd +# +### BEGIN INIT INFO +# Provides: corosync-qnetd +# Required-Start: $network $syslog +# Required-Stop: $network $syslog +# Default-Start: +# Default-Stop: +# Short-Description: Starts and stops Corosync Qdevice Network daemon. +# Description: Starts and stops Corosync Qdevice Network daemon. +### END INIT INFO + +desc="Corosync Qdevice Network daemon" +prog="corosync-qnetd" + +# set secure PATH +PATH="/sbin:/bin:/usr/sbin:/usr/bin:@SBINDIR@" + +success() +{ + echo -ne "[ OK ]\r" +} + +failure() +{ + echo -ne "[FAILED]\r" +} + +status() +{ + pid=$(pidof $1 2>/dev/null) + res=$? + if [ $res -ne 0 ]; then + echo "$1 is stopped" + else + echo "$1 (pid $pid) is running..." + fi + return $res +} + +[ -f @INITCONFIGDIR@/$prog ] && . @INITCONFIGDIR@/$prog + +case '@INITCONFIGDIR@' in + */sysconfig) # rpm based distros + [ -f @INITDDIR@/functions ] && . @INITDDIR@/functions + [ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/subsys/$prog";; + */default) # deb based distros + [ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/$prog";; +esac + +# The version of __pids_pidof in /etc/init.d/functions calls pidof with -x +# This means it matches scripts, including this one. +# Redefine it here so that status (from the same file) works. +# Otherwise simultaneous calls to stop() will loop forever +__pids_pidof() { + pidof -c -o $$ -o $PPID -o %PPID "$1" || \ + pidof -c -o $$ -o $PPID -o %PPID "${1##*/}" +} + +cluster_disabled_at_boot() +{ + if grep -q nocluster /proc/cmdline && \ + [ "$(tty)" = "/dev/console" ]; then + echo -e "not configured to run at boot" + failure + return 1 + fi + return 0 +} + +start() +{ + echo -n "Starting $desc ($prog): " + + ! cluster_disabled_at_boot && return + + # most recent distributions use tmpfs for @LOCALSTATEDIR@/run + # to avoid to clean it up on every boot. + # they also assume that init scripts will create + # required subdirectories for proper operations + if [ ! -d "@LOCALSTATEDIR@/run/corosync-qnetd" ];then + mkdir -p "@LOCALSTATEDIR@/run/corosync-qnetd" + chmod 0770 "@LOCALSTATEDIR@/run/corosync-qnetd" + if [ ! -z "$COROSYNC_QNETD_RUNAS" ];then + chown "$COROSYNC_QNETD_RUNAS:$COROSYNC_QNETD_RUNAS" "@LOCALSTATEDIR@/run/corosync-qnetd" + fi + fi + + if status $prog > /dev/null 2>&1; then + success + else + if [ -z "$COROSYNC_QNETD_RUNAS" ];then + $prog $COROSYNC_QNETD_OPTIONS > /dev/null 2>&1 + else + runuser -s @BASHPATH@ $COROSYNC_QNETD_RUNAS -c "$prog $COROSYNC_QNETD_OPTIONS > /dev/null 2>&1" + fi + + if [ "$?" != 0 ]; then + failure + rtrn=1 + else + touch $LOCK_FILE + success + fi + fi + echo +} + +stop() +{ + ! status $prog > /dev/null 2>&1 && return + + echo -n "Signaling $desc ($prog) to terminate: " + kill -TERM $(pidof $prog) > /dev/null 2>&1 + success + echo + + echo -n "Waiting for $prog services to unload:" + while status $prog > /dev/null 2>&1; do + sleep 1 + echo -n "." + done + + rm -f $LOCK_FILE + success + echo +} + +restart() +{ + stop + start +} + +rtrn=0 + +case "$1" in +start) + start +;; +restart|reload|force-reload) + restart +;; +condrestart|try-restart) + if status $prog > /dev/null 2>&1; then + restart + fi +;; +status) + status $prog + rtrn=$? +;; +stop) + stop +;; +*) + echo "usage: $0 {start|stop|restart|reload|force-reload|condrestart|try-restart|status}" + rtrn=2 +;; +esac + +exit $rtrn diff --git a/init/corosync-qnetd.service.in b/init/corosync-qnetd.service.in new file mode 100644 index 0000000..283edff --- /dev/null +++ b/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 diff --git a/init/corosync-qnetd.sysconfig.example b/init/corosync-qnetd.sysconfig.example new file mode 100644 index 0000000..8d37535 --- /dev/null +++ b/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="" diff --git a/man/.gitignore b/man/.gitignore new file mode 100644 index 0000000..0315fcd --- /dev/null +++ b/man/.gitignore @@ -0,0 +1,2 @@ +*.html +*.3 diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 0000000..6b959e1 --- /dev/null +++ b/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 diff --git a/man/corosync-qdevice-net-certutil.8 b/man/corosync-qdevice-net-certutil.8 new file mode 100644 index 0000000..e689945 --- /dev/null +++ b/man/corosync-qdevice-net-certutil.8 @@ -0,0 +1,85 @@ +.\"/* +.\" * Copyright (C) 2016 Red Hat, Inc. +.\" * +.\" * All rights reserved. +.\" * +.\" * Author: Jan Friesse +.\" * +.\" * This software licensed under BSD license, the text of which follows: +.\" * +.\" * Redistribution and use in source and binary forms, with or without +.\" * modification, are permitted provided that the following conditions are met: +.\" * +.\" * - Redistributions of source code must retain the above copyright notice, +.\" * this list of conditions and the following disclaimer. +.\" * - Redistributions in binary form must reproduce the above copyright notice, +.\" * this list of conditions and the following disclaimer in the documentation +.\" * and/or other materials provided with the distribution. +.\" * - Neither the name of Red Hat, Inc. nor the names of its +.\" * contributors may be used to endorse or promote products derived from this +.\" * software without specific prior written permission. +.\" * +.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +.\" * THE POSSIBILITY OF SUCH DAMAGE. +.\" */ +.TH COROSYNC-QDEVICE-NET-CERTUTIL 8 2016-06-28 +.SH NAME +corosync-qdevice-net-certutil - tool to generate qdevice model net TLS certificates +.SH SYNOPSIS +.B "corosync-qdevice-net-certutil [-i|-m|-M|-r|-s|-Q] [-c certificate] [-n cluster_name]" +.SH DESCRIPTION +.B corosync-qdevice-net-certutil +is a frontend for NSS certutil used for generating client certificate for the net model of +qdevice. +.SH OPTIONS +.TP +.B -i +Initialize the QDevice Net NSS certificate database. +The default directory for the database is /etc/corosync/qdevice/net/. This directory +has to be writable by the current user. It needs the QNetd CA certificate passed as the +.B -c +parameter. This certificate can be found on the server running QNetd in the file +/etc/corosync/qnetd/nssdb/qnetd-cacert.crt. +.TP +.B -m +Import the cluster certificate and key from a pk12 file. +.TP +.B -r +Generate a certificate request. The certificate request is exported into +/etc/corosync/qdevice/net/qdevice-net-node.crq. It is necessary to +pass the cluster name using the +.B -n +parameter. The cluster name has to match the one defined in /etc/corosync/corosync.conf. +.TP +.B -M +Import a signed certificate and export a certificate with private key into +pk12 file. +.TP +.B -Q +Use ssh/scp to properly set both +.B corosync-qnetd +and +.B corosync-qdevice +certificates on all nodes. It's highly recommended that you use an ssh agent, +or ssh/scp will keep asking for a password - roughly 8 times the number of nodes. +.TP +.B -c +File with certificate to load. +.TP +.B -n +Name of the cluster. +.SH SEE ALSO +.BR corosync-qnetd (8) +.BR corosync-qdevice (8) +.SH AUTHOR +Jan Friesse +.PP diff --git a/man/corosync-qdevice-tool.8 b/man/corosync-qdevice-tool.8 new file mode 100644 index 0000000..ac71e59 --- /dev/null +++ b/man/corosync-qdevice-tool.8 @@ -0,0 +1,130 @@ +.\"/* +.\" * Copyright (C) 2016-2017 Red Hat, Inc. +.\" * +.\" * All rights reserved. +.\" * +.\" * Author: Jan Friesse +.\" * +.\" * This software licensed under BSD license, the text of which follows: +.\" * +.\" * Redistribution and use in source and binary forms, with or without +.\" * modification, are permitted provided that the following conditions are met: +.\" * +.\" * - Redistributions of source code must retain the above copyright notice, +.\" * this list of conditions and the following disclaimer. +.\" * - Redistributions in binary form must reproduce the above copyright notice, +.\" * this list of conditions and the following disclaimer in the documentation +.\" * and/or other materials provided with the distribution. +.\" * - Neither the name of Red Hat, Inc. nor the names of its +.\" * contributors may be used to endorse or promote products derived from this +.\" * software without specific prior written permission. +.\" * +.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +.\" * THE POSSIBILITY OF SUCH DAMAGE. +.\" */ +.TH COROSYNC-QDEVICE-TOOL 8 2017-10-17 +.SH NAME +corosync-qdevice-tool \- corosync-qdevice control interface. +.SH SYNOPSIS +.B "corosync-qdevice-tool [-Hhsv] [-p qdevice_ipc_socket_path]" +.SH DESCRIPTION +.B corosync-qdevice-tool +is a frontend to the internal corosync-qdevice IPC. Its main purpose is to show important +information about the current internal state of +.B corosync-qdevice. +.SH OPTIONS +.TP +.B -H +Properly shutdown the +.B corosync-qdevice +process +.TP +.B -h +Display a short usage text +.TP +.B -s +Display the status of the +.B corosync-qdevice +process. The output is described in its own section below. +.TP +.B -v +Display more verbose output for the +.B -s +option. +.TP +.B -p +Path to the +.B corosync-qdevice +communication socket. + +.SH STATUS COMMAND OUTPUT +.nf +Qdevice information +------------------- +Model: Net +Node ID: 1 +HB interval: 10000ms +Sync HB interval: 30000ms +Configured node list: + 0 Node ID = 1 +Heuristics: Enabled +Ring ID: 1.a00000000021b48 +Membership node list: 1 +Quorate: Yes +Quorum node list: + 0 Node ID = 1, State = member +Expected votes: 2 +Last poll call: 2016-06-24T17:05:20 (cast vote) + +Qdevice-net information +---------------------- +Cluster name: Cluster +QNetd host: localhost:5403 +Connect timeout: 8000ms +HB interval: 8000ms +VQ vote timer interval: 5000ms +TLS: Supported +Algorithm: Fifty-Fifty split +Tie-breaker: Node with lowest node ID +Poll timer running: Yes (cast vote) +State: Connected +Heuristics result: Pass (regular: Pass, membership: Fail, connect: Fail) +TLS active: Yes (client certificate sent) +Connected since: 2016-06-24T17:02:35 +Echo reply received: 2016-06-24T17:05:15 +.fi + +The output is split into a generic qdevice section and a model specific section. +Most of the items are just taken from corosync.conf file. It's helpful to note that the +.I Membership node list +is the membership list of the current node and should match the quorum node list. +.I Last poll call +is the timestamp (in iso format) of the last call to the votequorum_qdevice_poll +function. + +For model net, it's good to check the +.I Poll timer running +state. Internally, model net supports 3 states. Not voting (when +.I Poll timer running +is No, which means +.B corosync-qdevice +is waiting for +.B corosync-qnetd +to reply), voting (without cast vote, it means that the +.B corosync-qnetd +algorithm decides that the current node shouldn't get a vote) and voting (with cast vote). +.SH SEE ALSO +.BR corosync-qnetd (8) +.BR corosync-qdevice (8) +.SH AUTHOR +Jan Friesse +.PP diff --git a/man/corosync-qdevice.8 b/man/corosync-qdevice.8 new file mode 100644 index 0000000..1d86da9 --- /dev/null +++ b/man/corosync-qdevice.8 @@ -0,0 +1,461 @@ +.\"/* +.\" * Copyright (C) 2016-2017 Red Hat, Inc. +.\" * +.\" * All rights reserved. +.\" * +.\" * Author: Jan Friesse +.\" * +.\" * This software licensed under BSD license, the text of which follows: +.\" * +.\" * Redistribution and use in source and binary forms, with or without +.\" * modification, are permitted provided that the following conditions are met: +.\" * +.\" * - Redistributions of source code must retain the above copyright notice, +.\" * this list of conditions and the following disclaimer. +.\" * - Redistributions in binary form must reproduce the above copyright notice, +.\" * this list of conditions and the following disclaimer in the documentation +.\" * and/or other materials provided with the distribution. +.\" * - Neither the name of Red Hat, Inc. nor the names of its +.\" * contributors may be used to endorse or promote products derived from this +.\" * software without specific prior written permission. +.\" * +.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +.\" * THE POSSIBILITY OF SUCH DAMAGE. +.\" */ +.TH COROSYNC-QDEVICE 8 2017-10-17 +.SH NAME +corosync-qdevice \- QDevice daemon +.SH SYNOPSIS +.B "corosync-qdevice [-dfh] [-S option=value[,option2=value2,...]]" + +.SH DESCRIPTION +.B corosync-qdevice +is a daemon running on each node of a cluster. It provides a configured +number of votes to the +quorum subsystem based on a third-party arbitrator's decision. Its primary use +is to allow a cluster to sustain more node failures than standard quorum rules allow. +It is recommended for clusters with an even number of nodes and highly recommended +for 2 node clusters. +.SH OPTIONS +.TP +.B -d +Forcefully turn on debug information without the need to change corosync.conf. +.TP +.B -f +Do not daemonize, run in the foreground. +.TP +.B -h +Show short help text +.TP +.B -S +Set advanced settings described in its own section below. This option +shouldn't be generally used because most of the options are +not safe to change. +.SH CONFIGURATION +.B corosync-qdevice +reads its configuration from corosync.conf file. + +The main configuration is within +.B quorum.device +sub-key. Each model also has its own configuration within a +similarly named sub-key. +.TP +.B model +Specifies the model to be used. This parameter is required. +.B corosync-qdevice +is modular and is able to support multiple different models. The model basically +defines what type of arbitrator is used. Currently only +.I net +is supported. +.TP +.B timeout +Specifies how often +.B corosync-qdevice +should call the votequorum_poll function. It is also used by the +.I net +model to adjust +its hearbeat timeout. It is recommended that you don't change this value. +Default is +.IR 10000 . +.TP +.B sync_timeout +Specifies how often +.B corosync-qdevice +should call the votequorum_poll function during a sync phase. It is recommended that you don't change this value. +Default is +.IR 30000 . +.TP +.B votes +The number of votes provided to the cluster by qdevice. Default is (number_of_nodes - 1) or generally +sum(votes_per_node) - 1. + +.PP +.B quorum.device.heuristics +subkey holds the configuration of the heuristics. Heuristics are set of commands executed locally on +startup, cluster membership change, successful connect to +.B corosync-qnetd +and optionally also at regular times. Commands are executed in parallel. +When all commands finish successfully +(their return error code is zero) on time, +heuristics have passed, otherwise they have failed. The heuristics result is sent to +.B corosync-qnetd +and there it's used in calculations to determine which partition should be quorate. +.TP +.B timeout +Specifies maximum time in milliseconds how long +.B corosync-qdevice +waits till the heuristics commands finish. If some command doesn't finish before the timeout, it's +killed and heuristics fail. This timeout is used for heuristics executed at regular times. +Default value is half of the +.BR quorum.device.timeout ", so" +.IR 5000 . +.TP +.B sync_timeout +Similar to quorum.device.heuristics.timeout but used during membership changes. Default +value is half of the +.BR quorum.device.sync_timeout ", so" +.IR 15000 . +.TP +.B interval +Specifies interval between two regular heuristics execution. Default value is +3 * +.BR quorum.device.timeout ", so" +.IR 30000 . +.TP +.B mode +Can be one of +.IR on ", " sync " or " off +and specifies mode of operation of heuristics. Default is +.IR off , +which means heuristics are disabled. When +.I sync +is set, heuristics are executed only during startup, membership change and when connection +to +.B corosync-qnetd +is established. When heuristics should be running also on regular basis, this option +should be set to +.I on +value. +.TP +.B exec_NAME +defines executables. +.I NAME +can be arbitrary valid cmap key name string and it has no special meaning. +The value of this variable must contain a command to execute. The value is parsed (split) +into arguments similarly as Bourne shell would do. Quoting is possible by +using backslash and double quotes. + +.PP +.B quorum.device.net +subkey holds the configuration for +.B model +.IR net . +.TP +.B tls +Can be one of +.IR on ", " off " or " required +and specifies if tls should be used. +.I on +means a connection with TLS is attempted first, but if the server doesn't advertise TLS support +then non-TLS will be used. +.I off +is used then TLS is not required and it's then not even tried. This mode is the +only one which doesn't need a properly initialized NSS database. +.I required +means TLS is required and if the server doesn't support TLS, qdevice will +exit with error message. Default is +.IR on . +.TP +.B host +Specifies the IP address or host name of the qnetd server to be used. This parameter +is required. +.TP +.B port +Specifies TCP port of qnetd server. Default is +.IR 5403 . +.TP +.B algorithm +Decision algorithm. Can be one of the +.I ffsplit +or +.IR lms . +(actually there are also +.I test +and +.IR 2nodelms , +both of which are mainly for developers and shouldn't be used for production clusters). +For a description of what each algorithm means and how the algorithms differ see their +individual sections. +Default value is +.IR ffsplit . +.TP +.B tie_breaker +can be one of +.IR lowest ", " highest +or valid_node_id (number) values. It's used as a fallback if qdevice has to decide between two or more +equal partitions. +.I lowest +means the partition with the lowest node id is chosen. +.I highest +means the partition with highest node id is chosen. And valid_node_id means that the partition +containing the node with the given node id is chosen. +Default is +.IR lowest . +.TP +.B connect_timeout +Timeout when +.B corosync-qdevice +is trying to connect to +.B corosync-qnetd +host. Default is 0.8 * +.BR quorum.sync_timeout . +.TP +.B force_ip_version +can be one of +.I 0|4|6 +and forces the software to use the given IP version. +.I 0 +(default value) means IPv6 is preferred and IPv4 should be used as a fallback. + +.PP +Logging configuration is within the +.B logging +directive. +.B corosync-qdevice +parses and supports most of the options with exception of +.BR to_logfile ", " logfile +and +.BR logfile_priority . +The +.B logger_subsys +sub-directive can be also used if +.B subsys +is set to +.IR QDEVICE . + +.PP +For +.B corosync-qdevice +to work correctly, the +.B nodelist +directive has to be used and properly configured. Also the +.I net +model requires that +.B totem.cluster_name +option is set. + +.SH MODEL NET TLS CONFIGURATION +For +.B model +.I net +to work using TLS, it's necessary to create the NSS database, import Qnetd +CA certificate, and get/distribute a valid client certificate. + +If pcs is used (recommended) the following steps are not needed because pcs does them automatically. + +.B corosync-qdevice-net-certutil +is the tool to perform required actions semi-automatically. Please consult the help output of +it and its man page. For a first time configuration it may make sense to start with the +.B -Q +option. + +If TLS is not required just edit corosync.conf file and set +.B quorum.device.net.tls +to +.IR off . + +.SH MODEL NET ALGORITHMS +Algorithms are used to change behavior of how +.B corosync-qnetd +provides votes to a given node/partition. Currently there are two algorithms supported. +.TP +.B ffsplit +This one makes sense only for clusters with an even number of nodes. It provides exactly one +vote to the partition with the highest number of active nodes. If there are two exactly +similar partitions, +it provides its vote to the partition with higher score. The score is computed +as (number_of_connected_nodes + +number_of_connected_nodes_with_passed_heuristics - number_of_connected_nodes_with_failed_heuristics) +If the scores are equal, the vote is provided to partition with the most clients connected to the qnetd +server. If this number is also equal, then the tie_breaker is used. It is able to transition +its vote if the currently active partition becomes partitioned and a non-active partition +still has at least 50% of the active nodes. Because of this, a vote is not provided +if the qnetd connection is not active. + +To use this algorithm it's required to set the number of votes per node to 1 (default) +and the qdevice number of votes has to be also 1. This is achieved by setting +.B quorum.device.votes +key in corosync.conf file to 1. +.TP +.B lms +Last-man-standing. If the node is the only one left in the cluster that can see the +qnetd server then we return a vote. + +If more than one node can see the qnetd server but some nodes can't +see each other then the cluster is divided up into 'partitions' based on +their ring_id and this algorithm returns a vote to the partition with highest +heuristics score (computed the same way as for the +.B ffsplit +algorithm), or if there is more than 1 partition with equal scores, +the largest active partition or, +if there is more than 1 equal partition, the partition that contains the tie_breaker +node (lowest, highest, etc). For LMS to work, the number +of qdevice votes has to be set to default (so just delete +.B quorum.device.votes +key from corosync.conf). + +.SH ADVANCED SETTINGS +Set by using +.B -S +option. The default value is shown in parentheses) Options +beginning with +.B net_ +prefix are specific to +.B model +.IR net . +.TP +.B lock_file +Lock file location. (/var/run/corosync-qdevice/corosync-qdevice.pid) +.TP +.B local_socket_file +Internal IPC socket file location. (/var/run/corosync-qdevice/corosync-qdevice.sock) +.TP +.B local_socket_backlog +Parameter passed to listen syscall. (10) +.TP +.B max_cs_try_again +How many times to retry the call to a corosync function which has returned CS_ERR_TRY_AGAIN. (10) +.TP +.B votequorum_device_name +Name used for qdevice registration. (Qdevice) +.TP +.B ipc_max_clients +Maximum allowed simultaneous IPC clients. (10) +.TP +.B ipc_max_receive_size +Maximum size of a message received by IPC client. (4096) +.TP +.B ipc_max_send_size +Maximum size of a message allowed to be sent to an IPC client. (65536) +.TP +.B master_wins +Force enable/disable master wins. (default is model) +.TP +.B heuristics_ipc_max_send_buffers +Maximum number of heuristics worker send buffers. (128) +.TP +.B heuristics_ipc_max_send_receive_size +Maximum size of a message allowed to be send to, or received from heuristics worker. (4096) +.TP +.B heuristics_min_timeout +Minimum heuristics timeout accepted by client in ms. (1000) +.TP +.B heuristics_max_timeout +Maximum heuristics timeout accepted by client in ms. (120000) +.TP +.B heuristics_min_interval +Minimum heuristics interval accepted by client in ms. (1000) +.TP +.B heuristics_max_interval +Maximum heuristics interval accepted by client in ms. (3600000) +.TP +.B heuristics_max_execs +Maximum number of exec_ commands. (32) +.TP +.B heuristics_use_execvp +Use execvp instead of execv for executing commands. (off) +.TP +.B heuristics_max_processes +Maximum number of processes running at one time. (160) +.TP +.B heuristics_kill_list_interval +Interval between status is gathered and eventually signal is sent +to processes which didn't finished on time in ms. (5000) +.TP +.B net_nss_db_dir +NSS database directory. (/etc/corosync/qdevice/net/nssdb) +.TP +.B net_initial_msg_receive_size +Initial (used during connection parameters negotiation) +maximum size of the receive buffer for message (maximum +allowed message size received from qnetd). (32768) +.TP +.B net_initial_msg_send_size +Initial (used during connection parameter negotiation) +maximum size of one send buffer (message) to be sent to server. (32768) +.TP +.B net_min_msg_send_size +Minimum required size of one send buffer (message) to be sent to server. (32768) +.TP +.B net_max_msg_receive_size +Maximum allowed size of receive buffer for a message sent by server. (16777216) +.TP +.B net_max_send_buffers +Maximum number of send buffers. (10) +.TP +.B net_nss_qnetd_cn +Canonical name of qnetd server certificate. (Qnetd Server) +.TP +.B net_nss_client_cert_nickname +NSS nickname of qdevice client certificate. (Cluster Cert) +.TP +.B net_heartbeat_interval_min +Minimum heartbeat timeout accepted by client in ms. (1000) +.TP +.B net_heartbeat_interval_max +Maximum heartbeat timeout accepted by client in ms. (120000) +.TP +.B net_min_connect_timeout +Minimum connection timeout accepted by client in ms. (1000) +.TP +.B net_max_connect_timeout +Maximum connection timeout accepted by client in ms. (120000) +.TP +.B net_test_algorithm_enabled +Enable test algorithm. (if built with --enable-debug on, otherwise off) + +.SH EXAMPLE +Define qdevice with +.I net +model connecting to qnetd running on qnetd.example.org host, using +.I ffsplit +algorithm. +Heuristics is set to +.I sync +mode and executes two commands. + +.nf +quorum { + provider: corosync_votequorum + device { + votes: 1 + model: net + net { + tls: on + host: qnetd.example.org + algorithm: ffsplit + } + heuristics { + mode: sync + exec_ping: /bin/ping -q -c 1 "www.example.org" + exec_test_txt_exists: /usr/bin/test -f /tmp/test.txt + } +} +.fi +.SH SEE ALSO +.BR corosync-qdevice-tool (8) +.BR corosync-qdevice-net-certutil (8) +.BR corosync-qnetd (8) +.BR corosync.conf (5) +.SH AUTHOR +Jan Friesse +.PP diff --git a/man/corosync-qnetd-certutil.8 b/man/corosync-qnetd-certutil.8 new file mode 100644 index 0000000..d55ac28 --- /dev/null +++ b/man/corosync-qnetd-certutil.8 @@ -0,0 +1,73 @@ +.\"/* +.\" * Copyright (C) 2016 Red Hat, Inc. +.\" * +.\" * All rights reserved. +.\" * +.\" * Author: Jan Friesse +.\" * +.\" * This software licensed under BSD license, the text of which follows: +.\" * +.\" * Redistribution and use in source and binary forms, with or without +.\" * modification, are permitted provided that the following conditions are met: +.\" * +.\" * - Redistributions of source code must retain the above copyright notice, +.\" * this list of conditions and the following disclaimer. +.\" * - Redistributions in binary form must reproduce the above copyright notice, +.\" * this list of conditions and the following disclaimer in the documentation +.\" * and/or other materials provided with the distribution. +.\" * - Neither the name of Red Hat, Inc. nor the names of its +.\" * contributors may be used to endorse or promote products derived from this +.\" * software without specific prior written permission. +.\" * +.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +.\" * THE POSSIBILITY OF SUCH DAMAGE. +.\" */ +.TH COROSYNC-QNETD-CERTUTIL 8 2016-06-28 +.SH NAME +corosync-qnetd-certutil - tool to generate qnetd TLS certificates +.SH SYNOPSIS +.B "corosync-qnetd-certutil [-i|-s] [-c certificate] [-n cluster_name]" +.SH DESCRIPTION +.B corosync-qnetd-certutil +is a frontend for the NSS certutil, it is used for generating the QNetd CA (Certificate Authority), +server certificate and signing cluster certificate used by +.B corosync-qdevice +when using the model 'net'. +.SH OPTIONS +.TP +.B -i +Initialize the QNetd NSS certificate database and generate the QNetd CA and server certificates. +The default directory for the database is /etc/corosync/qnetd. This directory must be +writeable by the current user. The QNetd CA certificate is also exported into the file +/etc/corosync/qnetd/nssdb/qnetd-cacert.crt. +.TP +.B -s +Sign the cluster certificate. It is necessary to pass the cluster name (as +configured in corosync.conf) and the certificate request file - see options below. +The signed certificate will be written to the +file /etc/corosync/qnetd/nssdb/cluster-$ClusterName.crt +.TP +.B -c +Certificate request file to sign. +.TP +.B -n +Name of the cluster. +.SH NOTES +If qnetd is executed by a non root user, /etc/corosync/qnetd and its subdirectories must be owned by (or have group access for) the given user. If +.B corosync-qnetd-certutil +is executed as root it tries to copy the owner and group of /etc/corosync/qnetd to all of the created files. +.SH SEE ALSO +.BR corosync-qnetd (8) +.BR corosync-qdevice (8) +.SH AUTHOR +Jan Friesse +.PP diff --git a/man/corosync-qnetd-tool.8 b/man/corosync-qnetd-tool.8 new file mode 100644 index 0000000..81fb516 --- /dev/null +++ b/man/corosync-qnetd-tool.8 @@ -0,0 +1,128 @@ +.\"/* +.\" * Copyright (C) 2016 Red Hat, Inc. +.\" * +.\" * All rights reserved. +.\" * +.\" * Author: Jan Friesse +.\" * +.\" * This software licensed under BSD license, the text of which follows: +.\" * +.\" * Redistribution and use in source and binary forms, with or without +.\" * modification, are permitted provided that the following conditions are met: +.\" * +.\" * - Redistributions of source code must retain the above copyright notice, +.\" * this list of conditions and the following disclaimer. +.\" * - Redistributions in binary form must reproduce the above copyright notice, +.\" * this list of conditions and the following disclaimer in the documentation +.\" * and/or other materials provided with the distribution. +.\" * - Neither the name of Red Hat, Inc. nor the names of its +.\" * contributors may be used to endorse or promote products derived from this +.\" * software without specific prior written permission. +.\" * +.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +.\" * THE POSSIBILITY OF SUCH DAMAGE. +.\" */ +.TH COROSYNC-QNETD-TOOL 8 2016-06-23 +.SH NAME +corosync-qnetd-tool \- corosync-qnetd control interface. +.SH SYNOPSIS +.B "corosync-qnetd-tool [-Hhlsv] [-c cluster_name] [-p qnetd_ipc_socket_path]" +.SH DESCRIPTION +.B corosync-qnetd-tool +is a frontend to the internal corosync-qnetd IPC. Its main purpose is to show important +information about the current internal state of +.B corosync-qnetd. +.SH OPTIONS +.TP +.B -H +Properly shutdown the +.B corosync-qnetd +process +.TP +.B -h +Display a short usage text +.TP +.B -l +List all clients connected to the +.B corosync-qnetd +process. The output is described in its own section below. +.TP +.B -s +Display status of the +.B corosync-qnetd +process. +.TP +.B -v +Display more verbose output for options +.B -l +and +.B -s +.TP +.B -c +Used only with the +.B -l +option. By default, information about all clients from all clusters is displayed, with +this option it's possible to filter information from a single cluster given the +.I cluster_name. +.TP +.B -p +Path to the +.B corosync-qnetd +communication socket. + +.SH LIST COMMAND OUTPUT +.nf +Cluster "Cluster": + Algorithm: Fifty-Fifty split + Tie-breaker: Node with lowest node ID + Node ID 1: + Client address: ::ffff:127.0.0.1:52000 + HB interval: 8000ms + Configured node list: 1, 2 + Ring ID: 1.a00000000021b40 + Membership node list: 1, 2 + TLS active: Yes (client certificate verified) + Vote: No change (ACK) + ... +.fi + +The output contains a list of clusters. Each cluster has the cluster common options +.I Algorithm +and +.I Tie-breaker +as configured in the corosync.conf file. Information about nodes follows. +.I Client address +is the IP address and port of the client. +.I HB interval +is the heartbeat interval between +.B corosync-qnetd +and +.B corosync-qdevice +client. This option can be configured in corosync.conf. +.I Configured node list +is the list of nodes configured in corosync.conf. +.I Ring ID +and +.I Membership node list +are self-explanatory. +.I TLS active +describes if an encrypted transport is used between server and client. +.I Vote +is last vote sent to +.B corosync-qdevice +client. The last ACK/NACK vote (if it exists) is in parentheses. +.SH SEE ALSO +.BR corosync-qnetd (8) +.BR corosync-qdevice (8) +.SH AUTHOR +Jan Friesse +.PP diff --git a/man/corosync-qnetd.8 b/man/corosync-qnetd.8 new file mode 100644 index 0000000..fa58224 --- /dev/null +++ b/man/corosync-qnetd.8 @@ -0,0 +1,227 @@ +.\"/* +.\" * Copyright (C) 2016 Red Hat, Inc. +.\" * +.\" * All rights reserved. +.\" * +.\" * Author: Jan Friesse +.\" * +.\" * This software licensed under BSD license, the text of which follows: +.\" * +.\" * Redistribution and use in source and binary forms, with or without +.\" * modification, are permitted provided that the following conditions are met: +.\" * +.\" * - Redistributions of source code must retain the above copyright notice, +.\" * this list of conditions and the following disclaimer. +.\" * - Redistributions in binary form must reproduce the above copyright notice, +.\" * this list of conditions and the following disclaimer in the documentation +.\" * and/or other materials provided with the distribution. +.\" * - Neither the name of Red Hat, Inc. nor the names of its +.\" * contributors may be used to endorse or promote products derived from this +.\" * software without specific prior written permission. +.\" * +.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +.\" * THE POSSIBILITY OF SUCH DAMAGE. +.\" */ +.TH COROSYNC-QNETD 8 2016-06-29 +.SH NAME +corosync-qnetd \- QNet daemon +.SH SYNOPSIS +.B "corosync-qnetd [-46dfhv] [-l listen_addr] [-p listen_port] [-s tls] +.B [-c client_cert_required] [-m max_clients] [-S option=value[,option2=value2,...]]" + +.SH DESCRIPTION +.B corosync-qnetd +is a daemon running outside of the cluster with the purpose of providing a vote to the +.B corosync-qdevice +model net. It's designed to support multiple clusters and be almost configuration +and state free. New clusters are handled dynamically and no configuration file exists. +It's also able to run as non-root user - which is recommended. Connection between the +.B corosync-qdevice +model net client can be optionally configured with TLS client certificate checking. +The communication protocol between server and client is designed to be very simple +and allow backwards compatibility. +.SH OPTIONS +.TP +.B -4 +and its counterpart +.B -6 +are used to force IPv4 or IPv6 communication. The default is to listen on both address families. +.TP +.B -d +Turn on debug logging. By default the messages sent to syslog are purely operational, this +option sends additional debug messages. For even more detail use the +.B -d +parameter twice. +.TP +.B -f +Do not daemonize, run in the foreground. +.TP +.B -h +Show short help text +.TP +.B -v +Show version and supported communication protocol messages/options. +.TP +.B -l +IP address to listen on. By default the daemon listens on all addresses (wildcard). +.TP +.B -p +TCP port to listen on. Default port is 5403. +.TP +.B -s +Determines if TLS should be used and can be one of +.I on/off/required +(the default is +.I on +). +.I on +means TLS is enabled but the client is not required to start TLS, +.I off +means TLS is completely disabled, and +.I required +means TLS is required. +.I on +and +.I required +require the NSS database to be properly initialized by running the +.B corosync-qnetd-certutil +command. +.TP +.B -c +can be set to +.I on/off. +This option only makes sense if TLS is enabled. When +.B -c +is +.I on +a client is required to send its client certificate (default). +.TP +.B -m +Maximum simultaneous clients. The default is 0 which means no limit. +.TP +.B -S +Set advanced settings described in its own section below. This option +shouldn't be generally used because most of the options are +not safe to change. +.SH UNPRIVILEGED USER CONFIGURATION +It's generally recommended to run +.B corosync-qnetd +as a non root user. If you get a package from a distribution its highly +possible that the packager has done all the hard work for you. If the installation +is performed from source code, a few steps have to be taken. + +First it's necessary to create an unprivileged user/group. The following commands +can be used (executed as root): + +.nf +# groupadd -r coroqnetd +# useradd -r -g coroqnetd -d / -s /sbin/nologin -c "User for corosync-qnetd" coroqnetd +.fi + +The next step is to set the correct owner and group on /etc/corosync/qnetd and /var/run/corosync-qnetd +directories. + +.nf +# chown -R coroqnetd:coroqnetd /etc/corosync/qnetd /var/run/corosync-qnetd +.fi + +Some systems have the /var/run directory on a tmpfs file system which gets discarded after +a reboot. The solution is to use an initscript which takes care of the /var/run/corosync-qnetd +creation and sets the correct owner and permissions. For systems with systemd it's possible +to use a tmpfile.d configuration file (installed by default if systemd is enabled during +corosync compilation). + +The last step is to make sure +.B corosync-qnetd +is really executed as an unprivileged user. For initscript systems it's enough to set the +line COROSYNC_QNETD_RUNAS in /etc/(sysconfig|default)/corosync-qnetd file. If the file +is not already installed then use the one provided in the corosync source code +(init/corosync-qnetd.sysconfig.example). For systemd, overwrite/copy the +corosync-qnetd.service unit file and uncomment/change the "User=" directive. + +.SH TLS CONFIGURATION +For TLS to work its necessary to create the NSS database. If pcs is used then the following +steps are not needed because pcs does them automatically. + +.B corosync-qnetd-certutil +is the tool to perform required actions. Just run: + +.nf +# corosync-qnetd-certutil -i +.fi + +If TLS is not required then simply edit /etc/(sysconfig|default)/corosync-qnetd or +systemd unit file and add the parameter +.B -s +.I off +in the proper place. + +.SH ADVANCED SETTINGS +Set by the +.B -S +option. The default value is shown in parentheses. +.TP +.B listen_backlog +Parameter passed to the listen syscall on the network socket. (10) +.TP +.B max_client_send_buffers +Maximum number of send buffers for one client. (32) +.TP +.B max_client_send_size +Maximum size of one send buffer (message) to be sent to a client. (32768) +.TP +.B max_client_receive_size +Maximum size of the receive buffer for a client message (maximum +allowed message size received by client). (32768) +.TP +.B nss_db_dir +NSS database directory. (/etc/corosync/qnetd/nssdb) +.TP +.B cert_nickname +NSS nickname of qnetd server certificate. (QNetd Cert) +.TP +.B heartbeat_interval_min +Minimum heartbeat timeout accepted by server in ms. (1000) +.TP +.B heartbeat_interval_max +Maximum heartbeat timeout accepted by server in ms. (120000) +.TP +.B dpd_enabled +Dead peer detection enabled. (on) +.TP +.B dpd_interval +How often the DPD algorithm detects dead peers in ms. (10000) +.TP +.B lock_file +Lock file location. (/var/run/corosync-qnetd/corosync-qnetd.pid) +.TP +.B local_socket_file +Internal IPC socket file location. (/var/run/corosync-qnetd/corosync-qnetd.sock) +.TP +.B local_socket_backlog +Parameter passed to listen syscall on the local socket. (10) +.TP +.B ipc_max_clients +Maximum allowed simultaneous IPC clients. (10) +.TP +.B ipc_max_receive_size +Maximum size of a message received by IPC client. (4096) +.TP +.B ipc_max_send_size +Maximum size of a message sent to an IPC client. (10485760) +.SH SEE ALSO +.BR corosync-qnetd-tool (8) +.BR corosync-qnetd-certutil (8) +.BR corosync-qdevice (8) +.SH AUTHOR +Jan Friesse +.PP diff --git a/qdevices/.gitignore b/qdevices/.gitignore new file mode 100644 index 0000000..779cdee --- /dev/null +++ b/qdevices/.gitignore @@ -0,0 +1,7 @@ +corosync-qdevice +corosync-qdevice-tool +corosync-qnetd-certutil +corosync-qdevice-net-certutil +corosync-qnetd +corosync-qnetd-tool +*.test diff --git a/qdevices/Makefile.am b/qdevices/Makefile.am new file mode 100644 index 0000000..d48b004 --- /dev/null +++ b/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) diff --git a/qdevices/corosync-qdevice-net-certutil.sh b/qdevices/corosync-qdevice-net-certutil.sh new file mode 100644 index 0000000..10b47c8 --- /dev/null +++ b/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 diff --git a/qdevices/corosync-qdevice-tool.c b/qdevices/corosync-qdevice-tool.c new file mode 100644 index 0000000..e30730e --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#include "qdevice-config.h" + +#include "dynar.h" +#include "dynar-str.h" +#include "utils.h" +#include "unix-socket.h" + +#define IPC_READ_BUF_SIZE 512 + +enum qdevice_tool_operation { + QDEVICE_TOOL_OPERATION_NONE, + QDEVICE_TOOL_OPERATION_SHUTDOWN, + QDEVICE_TOOL_OPERATION_STATUS, +}; + +enum qdevice_tool_exit_code { + QDEVICE_TOOL_EXIT_CODE_NO_ERROR = 0, + QDEVICE_TOOL_EXIT_CODE_USAGE = 1, + QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR = 2, + QDEVICE_TOOL_EXIT_CODE_SOCKET_CONNECT = 3, + QDEVICE_TOOL_EXIT_CODE_QDEVICE_RETURNED_ERROR = 4, +}; + +static void +usage(void) +{ + + printf("usage: %s [-Hhsv] [-p qdevice_ipc_socket_path]\n", + QDEVICE_TOOL_PROGRAM_NAME); +} + +static void +cli_parse(int argc, char * const argv[], enum qdevice_tool_operation *operation, + int *verbose, char **socket_path) +{ + int ch; + + *operation = QDEVICE_TOOL_OPERATION_NONE; + *verbose = 0; + *socket_path = strdup(QDEVICE_DEFAULT_LOCAL_SOCKET_FILE); + + if (*socket_path == NULL) { + errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, + "Can't alloc memory for socket path string"); + } + + while ((ch = getopt(argc, argv, "Hhsvp:")) != -1) { + switch (ch) { + case 'H': + *operation = QDEVICE_TOOL_OPERATION_SHUTDOWN; + break; + case 's': + *operation = QDEVICE_TOOL_OPERATION_STATUS; + break; + case 'v': + *verbose = 1; + break; + case 'p': + free(*socket_path); + *socket_path = strdup(optarg); + if (*socket_path == NULL) { + errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, + "Can't alloc memory for socket path string"); + } + break; + case 'h': + case '?': + usage(); + exit(QDEVICE_TOOL_EXIT_CODE_USAGE); + break; + } + } + + if (*operation == QDEVICE_TOOL_OPERATION_NONE) { + usage(); + exit(QDEVICE_TOOL_EXIT_CODE_USAGE); + } +} + +static int +store_command(struct dynar *str, enum qdevice_tool_operation operation, int verbose) +{ + const char *nline = "\n\0"; + const int nline_len = 2; + + switch (operation) { + case QDEVICE_TOOL_OPERATION_NONE: + errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Unhandled operation none"); + break; + case QDEVICE_TOOL_OPERATION_SHUTDOWN: + if (dynar_str_cat(str, "shutdown ") != 0) { + return (-1); + } + break; + case QDEVICE_TOOL_OPERATION_STATUS: + if (dynar_str_cat(str, "status ") != 0) { + return (-1); + } + break; + } + + if (verbose) { + if (dynar_str_cat(str, "verbose ") != 0) { + return (-1); + } + } + + if (dynar_cat(str, nline, nline_len) != 0) { + return (-1); + } + + return (0); +} + +/* + * -1 - Internal error (can't alloc memory) + * 0 - No error + * 1 - IPC returned error + * 2 - Unknown status line + */ +static int +read_ipc_reply(FILE *f) +{ + struct dynar read_str; + int ch; + int status_readed; + int res; + static const char *ok_str = "OK"; + static const char *err_str = "Error"; + int err_set; + char c; + + dynar_init(&read_str, IPC_READ_BUF_SIZE); + + status_readed = 0; + err_set = 0; + res = 0; + + while ((ch = fgetc(f)) != EOF) { + if (status_readed) { + putc(ch, (err_set ? stderr : stdout)); + } else { + if (ch == '\r') { + } else if (ch == '\n') { + status_readed = 1; + + c = '\0'; + if (dynar_cat(&read_str, &c, sizeof(c)) != 0) { + res = -1; + goto exit_destroy; + } + + if (strcasecmp(dynar_data(&read_str), ok_str) == 0) { + } else if (strcasecmp(dynar_data(&read_str), err_str) == 0) { + err_set = 1; + res = 1; + fprintf(stderr, "Error: "); + } else { + res = 2; + goto exit_destroy; + } + } else { + c = ch; + if (dynar_cat(&read_str, &c, sizeof(c)) != 0) { + res = -1; + goto exit_destroy; + } + } + } + } + +exit_destroy: + dynar_destroy(&read_str); + + return (res); +} + +int +main(int argc, char * const argv[]) +{ + enum qdevice_tool_operation operation; + int verbose; + char *socket_path; + int sock_fd; + FILE *sock; + struct dynar send_str; + int res; + int exit_code; + + exit_code = QDEVICE_TOOL_EXIT_CODE_NO_ERROR; + + cli_parse(argc, argv, &operation, &verbose, &socket_path); + + dynar_init(&send_str, QDEVICE_DEFAULT_IPC_MAX_RECEIVE_SIZE); + + sock_fd = unix_socket_client_create(socket_path, 0); + if (sock_fd == -1) { + err(QDEVICE_TOOL_EXIT_CODE_SOCKET_CONNECT, + "Can't connect to QDevice socket (is QDevice running?)"); + } + + sock = fdopen(sock_fd, "w+t"); + if (sock == NULL) { + err(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't open QDevice socket fd"); + } + + if (store_command(&send_str, operation, verbose) != 0) { + errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't store command"); + } + + res = fprintf(sock, "%s", dynar_data(&send_str)); + if (res < 0 || (size_t)res != strlen(dynar_data(&send_str)) || + fflush(sock) != 0) { + errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't send command"); + } + + res = read_ipc_reply(sock); + switch (res) { + case -1: + errx(QDEVICE_TOOL_EXIT_CODE_INTERNAL_ERROR, "Internal error during IPC status line read"); + break; + case 0: + break; + case 1: + exit_code = QDEVICE_TOOL_EXIT_CODE_QDEVICE_RETURNED_ERROR; + break; + case 2: + errx(QDEVICE_TOOL_EXIT_CODE_SOCKET_CONNECT, "Unknown status line returned by IPC server"); + break; + } + + if (fclose(sock) != 0) { + warn("Can't close QDevice socket"); + } + + free(socket_path); + dynar_destroy(&send_str); + + return (exit_code); +} diff --git a/qdevices/corosync-qdevice.c b/qdevices/corosync-qdevice.c new file mode 100644 index 0000000..dc7c79c --- /dev/null +++ b/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 +#include + +#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 +#endif + +struct qdevice_instance *global_instance; + +static void +signal_int_handler(int sig) +{ + qdevice_log(LOG_DEBUG, "SIGINT received - closing local unix socket"); + qdevice_ipc_close(global_instance); +} + +static void +signal_term_handler(int sig) +{ + qdevice_log(LOG_DEBUG, "SIGTERM received - closing server socket"); + qdevice_ipc_close(global_instance); +} + +static void +signal_handlers_register(void) +{ + struct sigaction act; + + act.sa_handler = signal_int_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGINT, &act, NULL); + + act.sa_handler = signal_term_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGTERM, &act, NULL); + + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGCHLD, &act, NULL); + + act.sa_handler = SIG_IGN; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGPIPE, &act, NULL); +} + +static void +usage(void) +{ + + printf("usage: %s [-dfh] [-S option=value[,option2=value2,...]]\n", QDEVICE_PROGRAM_NAME); +} + +static void +cli_parse_long_opt(struct qdevice_advanced_settings *advanced_settings, const char *long_opt) +{ + struct dynar_getopt_lex lex; + struct dynar dynar_long_opt; + const char *opt; + const char *val; + int res; + + dynar_init(&dynar_long_opt, strlen(long_opt) + 1); + if (dynar_str_cpy(&dynar_long_opt, long_opt) != 0) { + errx(1, "Can't alloc memory for long option"); + } + + dynar_getopt_lex_init(&lex, &dynar_long_opt); + + while (dynar_getopt_lex_token_next(&lex) == 0 && strcmp(dynar_data(&lex.option), "") != 0) { + opt = dynar_data(&lex.option); + val = dynar_data(&lex.value); + + res = qdevice_advanced_settings_set(advanced_settings, opt, val); + switch (res) { + case -1: + errx(1, "Unknown option '%s'", opt); + break; + case -2: + errx(1, "Invalid value '%s' for option '%s'", val, opt); + break; + } + } + + dynar_getopt_lex_destroy(&lex); + dynar_destroy(&dynar_long_opt); +} + +static void +cli_parse(int argc, char * const argv[], int *foreground, int *force_debug, + struct qdevice_advanced_settings *advanced_settings) +{ + int ch; + + *foreground = 0; + *force_debug = 0; + + while ((ch = getopt(argc, argv, "dfhS:")) != -1) { + switch (ch) { + case 'd': + *force_debug = 1; + break; + case 'f': + *foreground = 1; + break; + case 'S': + cli_parse_long_opt(advanced_settings, optarg); + break; + case 'h': + case '?': + usage(); + exit(1); + break; + } + } +} + +int +main(int argc, char * const argv[]) +{ + struct qdevice_instance instance; + struct qdevice_advanced_settings advanced_settings; + int foreground; + int force_debug; + int lock_file; + int another_instance_running; + + if (qdevice_advanced_settings_init(&advanced_settings) != 0) { + errx(1, "Can't alloc memory for advanced settings"); + } + + cli_parse(argc, argv, &foreground, &force_debug, &advanced_settings); + + qdevice_instance_init(&instance, &advanced_settings); + + qdevice_heuristics_init(&instance.heuristics_instance, &advanced_settings); + instance.heuristics_instance.qdevice_instance_ptr = &instance; + + qdevice_cmap_init(&instance); + qdevice_log_init(&instance, force_debug); + + /* + * Daemonize + */ + if (!foreground) { + utils_tty_detach(); + } + + if ((lock_file = utils_flock(advanced_settings.lock_file, getpid(), + &another_instance_running)) == -1) { + if (another_instance_running) { + qdevice_log(LOG_ERR, "Another instance is running"); + } else { + qdevice_log_err(LOG_ERR, "Can't acquire lock"); + } + + exit(1); + } + + qdevice_log(LOG_DEBUG, "Initializing votequorum"); + qdevice_votequorum_init(&instance); + + qdevice_log(LOG_DEBUG, "Initializing local socket"); + if (qdevice_ipc_init(&instance) != 0) { + return (1); + } + + qdevice_log(LOG_DEBUG, "Registering qdevice models"); + qdevice_model_register_all(); + + qdevice_log(LOG_DEBUG, "Configuring qdevice"); + if (qdevice_instance_configure_from_cmap(&instance) != 0) { + return (1); + } + + qdevice_log(LOG_DEBUG, "Configuring master_wins"); + if (qdevice_votequorum_master_wins(&instance, (advanced_settings.master_wins == + QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_ON ? 1 : 0)) != 0) { + return (1); + } + + qdevice_log(LOG_DEBUG, "Getting configuration node list"); + if (qdevice_cmap_store_config_node_list(&instance) != 0) { + return (1); + } + + qdevice_log(LOG_DEBUG, "Initializing qdevice model"); + if (qdevice_model_init(&instance) != 0) { + return (1); + } + + qdevice_log(LOG_DEBUG, "Initializing cmap tracking"); + if (qdevice_cmap_add_track(&instance) != 0) { + return (1); + } + + qdevice_log(LOG_DEBUG, "Waiting for ring id"); + if (qdevice_votequorum_wait_for_ring_id(&instance) != 0) { + return (1); + } + + qdevice_log(LOG_DEBUG, "Waiting for initial heuristics exec result"); + if (qdevice_heuristics_wait_for_initial_exec_result(&instance.heuristics_instance) != 0) { + return (1); + } + + global_instance = &instance; + signal_handlers_register(); + + qdevice_log(LOG_DEBUG, "Running qdevice model"); +#ifdef HAVE_LIBSYSTEMD + sd_notify (0, "READY=1"); +#endif + if (qdevice_model_run(&instance) != 0) { + return (1); + } + + qdevice_log(LOG_DEBUG, "Removing cmap tracking"); + if (qdevice_cmap_del_track(&instance) != 0) { + return (1); + } + + qdevice_log(LOG_DEBUG, "Destroying qdevice model"); + qdevice_model_destroy(&instance); + + qdevice_log(LOG_DEBUG, "Destroying qdevice ipc"); + qdevice_ipc_destroy(&instance); + + qdevice_log(LOG_DEBUG, "Destroying votequorum and cmap"); + qdevice_votequorum_destroy(&instance); + qdevice_cmap_destroy(&instance); + + qdevice_log(LOG_DEBUG, "Destroying heuristics"); + qdevice_heuristics_destroy(&instance.heuristics_instance); + + qdevice_log(LOG_DEBUG, "Closing log"); + qdevice_log_close(&instance); + + qdevice_instance_destroy(&instance); + + qdevice_advanced_settings_destroy(&advanced_settings); + + return (0); +} diff --git a/qdevices/corosync-qnetd-certutil.sh b/qdevices/corosync-qnetd-certutil.sh new file mode 100644 index 0000000..726f482 --- /dev/null +++ b/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 diff --git a/qdevices/corosync-qnetd-tool.c b/qdevices/corosync-qnetd-tool.c new file mode 100644 index 0000000..e52d1ce --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#include "qnet-config.h" + +#include "dynar.h" +#include "dynar-str.h" +#include "utils.h" +#include "unix-socket.h" + +#define IPC_READ_BUF_SIZE 512 + +enum qnetd_tool_operation { + QNETD_TOOL_OPERATION_NONE, + QNETD_TOOL_OPERATION_SHUTDOWN, + QNETD_TOOL_OPERATION_STATUS, + QNETD_TOOL_OPERATION_LIST, +}; + +enum qnetd_tool_exit_code { + QNETD_TOOL_EXIT_CODE_NO_ERROR = 0, + QNETD_TOOL_EXIT_CODE_USAGE = 1, + QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR = 2, + QNETD_TOOL_EXIT_CODE_SOCKET_CONNECT = 3, + QNETD_TOOL_EXIT_CODE_QNETD_RETURNED_ERROR = 4, +}; + +static void +usage(void) +{ + + printf("usage: %s [-Hhlsv] [-c cluster_name] [-p qnetd_ipc_socket_path]\n", + QNETD_TOOL_PROGRAM_NAME); +} + +static void +cli_parse(int argc, char * const argv[], enum qnetd_tool_operation *operation, + int *verbose, char **cluster_name, char **socket_path) +{ + int ch; + + *operation = QNETD_TOOL_OPERATION_NONE; + *verbose = 0; + *cluster_name = NULL; + *socket_path = strdup(QNETD_DEFAULT_LOCAL_SOCKET_FILE); + + if (*socket_path == NULL) { + errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, + "Can't alloc memory for socket path string"); + } + + while ((ch = getopt(argc, argv, "Hhlsvc:p:")) != -1) { + switch (ch) { + case 'H': + *operation = QNETD_TOOL_OPERATION_SHUTDOWN; + break; + case 'l': + *operation = QNETD_TOOL_OPERATION_LIST; + break; + case 's': + *operation = QNETD_TOOL_OPERATION_STATUS; + break; + case 'v': + *verbose = 1; + break; + case 'c': + free(*cluster_name); + *cluster_name = strdup(optarg); + if (*cluster_name == NULL) { + errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, + "Can't alloc memory for cluster name string"); + } + break; + case 'p': + free(*socket_path); + *socket_path = strdup(optarg); + if (*socket_path == NULL) { + errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, + "Can't alloc memory for socket path string"); + } + break; + case 'h': + case '?': + usage(); + exit(QNETD_TOOL_EXIT_CODE_USAGE); + break; + } + } + + if (*operation == QNETD_TOOL_OPERATION_NONE) { + usage(); + exit(QNETD_TOOL_EXIT_CODE_USAGE); + } +} + +static int +store_command(struct dynar *str, enum qnetd_tool_operation operation, int verbose, + const char *cluster_name) +{ + const char *nline = "\n\0"; + const int nline_len = 2; + + switch (operation) { + case QNETD_TOOL_OPERATION_NONE: + errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Unhandled operation none"); + break; + case QNETD_TOOL_OPERATION_SHUTDOWN: + if (dynar_str_cat(str, "shutdown ") != 0) { + return (-1); + } + break; + case QNETD_TOOL_OPERATION_STATUS: + if (dynar_str_cat(str, "status ") != 0) { + return (-1); + } + break; + case QNETD_TOOL_OPERATION_LIST: + if (dynar_str_cat(str, "list ") != 0) { + return (-1); + } + break; + } + + if (verbose) { + if (dynar_str_cat(str, "verbose ") != 0) { + return (-1); + } + } + + if (cluster_name != NULL) { + if (dynar_str_cat(str, "cluster ") != 0 || + dynar_str_quote_cat(str, cluster_name) != 0 || + dynar_str_cat(str, " ") != 0) { + return (-1); + } + } + + if (dynar_cat(str, nline, nline_len) != 0) { + return (-1); + } + + return (0); +} + +/* + * -1 - Internal error (can't alloc memory) + * 0 - No error + * 1 - IPC returned error + * 2 - Unknown status line + */ +static int +read_ipc_reply(FILE *f) +{ + struct dynar read_str; + int ch; + int status_readed; + int res; + static const char *ok_str = "OK"; + static const char *err_str = "Error"; + int err_set; + char c; + + dynar_init(&read_str, IPC_READ_BUF_SIZE); + + status_readed = 0; + err_set = 0; + res = 0; + + while ((ch = fgetc(f)) != EOF) { + if (status_readed) { + putc(ch, (err_set ? stderr : stdout)); + } else { + if (ch == '\r') { + } else if (ch == '\n') { + status_readed = 1; + + c = '\0'; + if (dynar_cat(&read_str, &c, sizeof(c)) != 0) { + res = -1; + goto exit_destroy; + } + + if (strcasecmp(dynar_data(&read_str), ok_str) == 0) { + } else if (strcasecmp(dynar_data(&read_str), err_str) == 0) { + err_set = 1; + res = 1; + fprintf(stderr, "Error: "); + } else { + res = 2; + goto exit_destroy; + } + } else { + c = ch; + if (dynar_cat(&read_str, &c, sizeof(c)) != 0) { + res = -1; + goto exit_destroy; + } + } + } + } + +exit_destroy: + dynar_destroy(&read_str); + + return (res); +} + +int +main(int argc, char * const argv[]) +{ + enum qnetd_tool_operation operation; + int verbose; + char *cluster_name; + char *socket_path; + int sock_fd; + FILE *sock; + struct dynar send_str; + int res; + int exit_code; + + exit_code = QNETD_TOOL_EXIT_CODE_NO_ERROR; + + cli_parse(argc, argv, &operation, &verbose, &cluster_name, &socket_path); + + dynar_init(&send_str, QNETD_DEFAULT_IPC_MAX_RECEIVE_SIZE); + + sock_fd = unix_socket_client_create(socket_path, 0); + if (sock_fd == -1) { + err(QNETD_TOOL_EXIT_CODE_SOCKET_CONNECT, + "Can't connect to qnetd socket (is QNetd running?)"); + } + + sock = fdopen(sock_fd, "w+t"); + if (sock == NULL) { + err(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't open QNetd socket fd"); + } + + if (store_command(&send_str, operation, verbose, cluster_name) != 0) { + errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't store command"); + } + + res = fprintf(sock, "%s", dynar_data(&send_str)); + if (res < 0 || (size_t)res != strlen(dynar_data(&send_str)) || + fflush(sock) != 0) { + errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Can't send command"); + } + + res = read_ipc_reply(sock); + switch (res) { + case -1: + errx(QNETD_TOOL_EXIT_CODE_INTERNAL_ERROR, "Internal error during IPC status line read"); + break; + case 0: + break; + case 1: + exit_code = QNETD_TOOL_EXIT_CODE_QNETD_RETURNED_ERROR; + break; + case 2: + errx(QNETD_TOOL_EXIT_CODE_SOCKET_CONNECT, "Unknown status line returned by IPC server"); + break; + } + + if (fclose(sock) != 0) { + warn("Can't close QNetd socket"); + } + + free(cluster_name); + free(socket_path); + dynar_destroy(&send_str); + + return (exit_code); +} diff --git a/qdevices/corosync-qnetd.c b/qdevices/corosync-qnetd.c new file mode 100644 index 0000000..45cf5db --- /dev/null +++ b/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 +#include +#include +#include +#include + +#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 +#endif + +/* + * This is global variable used for comunication with main loop and signal (calls close) + */ +struct qnetd_instance *global_instance; + +enum tlv_decision_algorithm_type + qnetd_static_supported_decision_algorithms[QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE] = { + TLV_DECISION_ALGORITHM_TYPE_TEST, + TLV_DECISION_ALGORITHM_TYPE_FFSPLIT, + TLV_DECISION_ALGORITHM_TYPE_2NODELMS, + TLV_DECISION_ALGORITHM_TYPE_LMS, +}; + +static void +qnetd_err_nss(void) +{ + + qnetd_log_nss(LOG_CRIT, "NSS error"); + + exit(1); +} + +static void +qnetd_warn_nss(void) +{ + + qnetd_log_nss(LOG_WARNING, "NSS warning"); +} + +static PRPollDesc * +qnetd_pr_poll_array_create(struct qnetd_instance *instance) +{ + struct pr_poll_array *poll_array; + const struct qnetd_client_list *client_list; + struct qnetd_client *client; + PRPollDesc *poll_desc; + struct qnetd_poll_array_user_data *user_data; + const struct unix_socket_client_list *ipc_client_list; + struct unix_socket_client *ipc_client; + + poll_array = &instance->poll_array; + client_list = &instance->clients; + ipc_client_list = &instance->local_ipc.clients; + + pr_poll_array_clean(poll_array); + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + + poll_desc->fd = instance->server.socket; + poll_desc->in_flags = PR_POLL_READ; + + user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET; + + if (qnetd_ipc_is_closed(instance)) { + qnetd_log(LOG_DEBUG, "Listening socket is closed"); + + return (NULL); + } + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + + poll_desc->fd = instance->ipc_socket_poll_fd; + poll_desc->in_flags = PR_POLL_READ; + user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET; + + TAILQ_FOREACH(client, client_list, entries) { + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + poll_desc->fd = client->socket; + poll_desc->in_flags = PR_POLL_READ; + + if (!send_buffer_list_empty(&client->send_buffer_list)) { + poll_desc->in_flags |= PR_POLL_WRITE; + } + + user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT; + user_data->client = client; + } + + TAILQ_FOREACH(ipc_client, ipc_client_list, entries) { + if (!ipc_client->reading_line && !ipc_client->writing_buffer) { + continue; + } + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + + poll_desc->fd = ((struct qnetd_ipc_user_data *)ipc_client->user_data)->nspr_poll_fd; + if (ipc_client->reading_line) { + poll_desc->in_flags |= PR_POLL_READ; + } + + if (ipc_client->writing_buffer) { + poll_desc->in_flags |= PR_POLL_WRITE; + } + + user_data->type = QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT; + user_data->ipc_client = ipc_client; + } + + pr_poll_array_gc(poll_array); + + return (poll_array->array); +} + +static int +qnetd_poll(struct qnetd_instance *instance) +{ + struct qnetd_client *client; + PRPollDesc *pfds; + PRInt32 poll_res; + ssize_t i; + int client_disconnect; + struct qnetd_poll_array_user_data *user_data; + struct unix_socket_client *ipc_client; + + client = NULL; + client_disconnect = 0; + + pfds = qnetd_pr_poll_array_create(instance); + if (pfds == NULL) { + return (-1); + } + + if ((poll_res = PR_Poll(pfds, pr_poll_array_size(&instance->poll_array), + timer_list_time_to_expire(&instance->main_timer_list))) >= 0) { + timer_list_expire(&instance->main_timer_list); + + /* + * Walk thru pfds array and process events + */ + for (i = 0; i < pr_poll_array_size(&instance->poll_array); i++) { + user_data = pr_poll_array_get_user_data(&instance->poll_array, i); + + client = NULL; + ipc_client = NULL; + client_disconnect = 0; + + switch (user_data->type) { + case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET: + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT: + client = user_data->client; + client_disconnect = client->schedule_disconnect; + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET: + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT: + ipc_client = user_data->ipc_client; + client_disconnect = ipc_client->schedule_disconnect; + } + + if (!client_disconnect && poll_res > 0 && + pfds[i].out_flags & PR_POLL_READ) { + switch (user_data->type) { + case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET: + qnetd_client_net_accept(instance); + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT: + if (qnetd_client_net_read(instance, client) == -1) { + client_disconnect = 1; + } + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET: + qnetd_ipc_accept(instance, &ipc_client); + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT: + qnetd_ipc_io_read(instance, ipc_client); + break; + } + } + + if (!client_disconnect && poll_res > 0 && + pfds[i].out_flags & PR_POLL_WRITE) { + switch (user_data->type) { + case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET: + /* + * Poll write on listen socket -> fatal error + */ + qnetd_log(LOG_CRIT, "POLL_WRITE on listening socket"); + + return (-1); + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT: + if (qnetd_client_net_write(instance, client) == -1) { + client_disconnect = 1; + } + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET: + qnetd_log(LOG_CRIT, "POLL_WRITE on listening IPC socket"); + return (-1); + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT: + qnetd_ipc_io_write(instance, ipc_client); + break; + } + } + + if (!client_disconnect && poll_res > 0 && + (pfds[i].out_flags & (PR_POLL_ERR|PR_POLL_NVAL|PR_POLL_HUP|PR_POLL_EXCEPT)) && + !(pfds[i].out_flags & (PR_POLL_READ|PR_POLL_WRITE))) { + switch (user_data->type) { + case QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET: + case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET: + if (pfds[i].out_flags != PR_POLL_NVAL) { + /* + * Poll ERR on listening socket is fatal error. + * POLL_NVAL is used as a signal to quit poll loop. + */ + qnetd_log(LOG_CRIT, "POLL_ERR (%u) on listening " + "socket", pfds[i].out_flags); + } else { + qnetd_log(LOG_DEBUG, "Listening socket is closed"); + } + + return (-1); + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT: + qnetd_log(LOG_DEBUG, "POLL_ERR (%u) on client socket. " + "Disconnecting.", pfds[i].out_flags); + + client_disconnect = 1; + break; + case QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT: + qnetd_log(LOG_DEBUG, "POLL_ERR (%u) on ipc client socket." + " Disconnecting.", pfds[i].out_flags); + + client_disconnect = 1; + break; + } + } + + /* + * If client is scheduled for disconnect, disconnect it + */ + if (user_data->type == QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT && + client_disconnect) { + qnetd_instance_client_disconnect(instance, client, 0); + } else if (user_data->type == QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT && + (client_disconnect || ipc_client->schedule_disconnect)) { + qnetd_ipc_client_disconnect(instance, ipc_client); + } + } + } + + + return (0); +} + +static void +signal_int_handler(int sig) +{ + + qnetd_log(LOG_DEBUG, "SIGINT received - closing server IPC socket"); + + qnetd_ipc_close(global_instance); +} + +static void +signal_term_handler(int sig) +{ + + qnetd_log(LOG_DEBUG, "SIGTERM received - closing server IPC socket"); + + qnetd_ipc_close(global_instance); +} + +static void +signal_handlers_register(void) +{ + struct sigaction act; + + act.sa_handler = signal_int_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGINT, &act, NULL); + + act.sa_handler = signal_term_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGTERM, &act, NULL); +} + +static void +usage(void) +{ + + printf("usage: %s [-46dfhv] [-l listen_addr] [-p listen_port] [-s tls]\n", QNETD_PROGRAM_NAME); + printf("%14s[-c client_cert_required] [-m max_clients] [-S option=value[,option2=value2,...]]\n", ""); +} + +static void +display_version(void) +{ + enum msg_type *supported_messages; + size_t no_supported_messages; + size_t zi; + + msg_get_supported_messages(&supported_messages, &no_supported_messages); + printf("Corosync Qdevice Network Daemon, version '%s'\n\n", VERSION); + printf("Supported algorithms: "); + for (zi = 0; zi < QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE; zi++) { + if (zi != 0) { + printf(", "); + } + printf("%s (%u)", + tlv_decision_algorithm_type_to_str(qnetd_static_supported_decision_algorithms[zi]), + qnetd_static_supported_decision_algorithms[zi]); + } + printf("\n"); + printf("Supported message types: "); + for (zi = 0; zi < no_supported_messages; zi++) { + if (zi != 0) { + printf(", "); + } + printf("%s (%u)", msg_type_to_str(supported_messages[zi]), supported_messages[zi]); + } + printf("\n"); +} + +static void +cli_parse_long_opt(struct qnetd_advanced_settings *advanced_settings, const char *long_opt) +{ + struct dynar_getopt_lex lex; + struct dynar dynar_long_opt; + const char *opt; + const char *val; + int res; + + dynar_init(&dynar_long_opt, strlen(long_opt) + 1); + if (dynar_str_cpy(&dynar_long_opt, long_opt) != 0) { + errx(1, "Can't alloc memory for long option"); + } + + dynar_getopt_lex_init(&lex, &dynar_long_opt); + + while (dynar_getopt_lex_token_next(&lex) == 0 && strcmp(dynar_data(&lex.option), "") != 0) { + opt = dynar_data(&lex.option); + val = dynar_data(&lex.value); + + res = qnetd_advanced_settings_set(advanced_settings, opt, val); + switch (res) { + case -1: + errx(1, "Unknown option '%s'", opt); + break; + case -2: + errx(1, "Invalid value '%s' for option '%s'", val, opt); + break; + } + } + + dynar_getopt_lex_destroy(&lex); + dynar_destroy(&dynar_long_opt); +} + +static void +cli_parse(int argc, char * const argv[], char **host_addr, uint16_t *host_port, int *foreground, + int *debug_log, int *bump_log_priority, enum tlv_tls_supported *tls_supported, + int *client_cert_required, size_t *max_clients, PRIntn *address_family, + struct qnetd_advanced_settings *advanced_settings) +{ + int ch; + char *ep; + long long int tmpll; + + *host_addr = NULL; + *host_port = QNETD_DEFAULT_HOST_PORT; + *foreground = 0; + *debug_log = 0; + *bump_log_priority = 0; + *tls_supported = QNETD_DEFAULT_TLS_SUPPORTED; + *client_cert_required = QNETD_DEFAULT_TLS_CLIENT_CERT_REQUIRED; + *max_clients = QNETD_DEFAULT_MAX_CLIENTS; + *address_family = PR_AF_UNSPEC; + + while ((ch = getopt(argc, argv, "46dfhvc:l:m:p:S:s:")) != -1) { + switch (ch) { + case '4': + *address_family = PR_AF_INET; + break; + case '6': + *address_family = PR_AF_INET6; + break; + case 'f': + *foreground = 1; + break; + case 'd': + if (*debug_log) { + *bump_log_priority = 1; + } + *debug_log = 1; + break; + case 'c': + if ((*client_cert_required = utils_parse_bool_str(optarg)) == -1) { + errx(1, "client_cert_required should be on/yes/1, off/no/0"); + } + break; + case 'l': + free(*host_addr); + *host_addr = strdup(optarg); + if (*host_addr == NULL) { + errx(1, "Can't alloc memory for host addr string"); + } + break; + case 'm': + errno = 0; + + tmpll = strtoll(optarg, &ep, 10); + if (tmpll < 0 || errno != 0 || *ep != '\0') { + errx(1, "max clients value %s is invalid", optarg); + } + *max_clients = (size_t)tmpll; + break; + case 'p': + *host_port = strtol(optarg, &ep, 10); + if (*host_port <= 0 || *host_port > ((uint16_t)~0) || *ep != '\0') { + errx(1, "host port must be in range 0-65535"); + } + break; + case 'S': + cli_parse_long_opt(advanced_settings, optarg); + break; + case 's': + if (strcasecmp(optarg, "on") == 0) { + *tls_supported = QNETD_DEFAULT_TLS_SUPPORTED; + } else if (strcasecmp(optarg, "off") == 0) { + *tls_supported = TLV_TLS_UNSUPPORTED; + } else if (strcasecmp(optarg, "req") == 0) { + *tls_supported = TLV_TLS_REQUIRED; + } else { + errx(1, "tls must be one of on, off, req"); + } + break; + case 'v': + display_version(); + exit(1); + break; + case 'h': + case '?': + usage(); + exit(1); + break; + } + } +} + +int +main(int argc, char * const argv[]) +{ + struct qnetd_instance instance; + struct qnetd_advanced_settings advanced_settings; + char *host_addr; + uint16_t host_port; + int foreground; + int debug_log; + int bump_log_priority; + enum tlv_tls_supported tls_supported; + int client_cert_required; + size_t max_clients; + PRIntn address_family; + int lock_file; + int another_instance_running; + + if (qnetd_advanced_settings_init(&advanced_settings) != 0) { + errx(1, "Can't alloc memory for advanced settings"); + } + + cli_parse(argc, argv, &host_addr, &host_port, &foreground, &debug_log, &bump_log_priority, + &tls_supported, &client_cert_required, &max_clients, &address_family, &advanced_settings); + + if (foreground) { + qnetd_log_init(QNETD_LOG_TARGET_STDERR); + } else { + qnetd_log_init(QNETD_LOG_TARGET_SYSLOG); + } + + qnetd_log_set_debug(debug_log); + qnetd_log_set_priority_bump(bump_log_priority); + + /* + * Daemonize + */ + if (!foreground) { + utils_tty_detach(); + } + + if ((lock_file = utils_flock(advanced_settings.lock_file, getpid(), + &another_instance_running)) == -1) { + if (another_instance_running) { + qnetd_log(LOG_ERR, "Another instance is running"); + } else { + qnetd_log_err(LOG_ERR, "Can't acquire lock"); + } + + exit(1); + } + + qnetd_log(LOG_DEBUG, "Initializing nss"); + if (nss_sock_init_nss((tls_supported != TLV_TLS_UNSUPPORTED ? + advanced_settings.nss_db_dir : NULL)) != 0) { + qnetd_err_nss(); + } + + if (SSL_ConfigServerSessionIDCache(0, 0, 0, NULL) != SECSuccess) { + qnetd_err_nss(); + } + + if (qnetd_instance_init(&instance, tls_supported, client_cert_required, + max_clients, &advanced_settings) == -1) { + qnetd_log(LOG_ERR, "Can't initialize qnetd"); + exit(1); + } + instance.host_addr = host_addr; + instance.host_port = host_port; + + if (tls_supported != TLV_TLS_UNSUPPORTED && qnetd_instance_init_certs(&instance) == -1) { + qnetd_err_nss(); + } + + qnetd_log(LOG_DEBUG, "Initializing local socket"); + if (qnetd_ipc_init(&instance) != 0) { + return (1); + } + + qnetd_log(LOG_DEBUG, "Creating listening socket"); + instance.server.socket = nss_sock_create_listen_socket(instance.host_addr, + instance.host_port, address_family); + if (instance.server.socket == NULL) { + qnetd_err_nss(); + } + + if (nss_sock_set_non_blocking(instance.server.socket) != 0) { + qnetd_err_nss(); + } + + if (PR_Listen(instance.server.socket, instance.advanced_settings->listen_backlog) != + PR_SUCCESS) { + qnetd_err_nss(); + } + + global_instance = &instance; + signal_handlers_register(); + + qnetd_log(LOG_DEBUG, "Registering algorithms"); + if (qnetd_algorithm_register_all() != 0) { + exit(1); + } + + qnetd_log(LOG_DEBUG, "QNetd ready to provide service"); + +#ifdef HAVE_LIBSYSTEMD + sd_notify(0, "READY=1"); +#endif + + /* + * MAIN LOOP + */ + while (qnetd_poll(&instance) == 0) { + } + + /* + * Cleanup + */ + qnetd_ipc_destroy(&instance); + + if (PR_Close(instance.server.socket) != PR_SUCCESS) { + qnetd_warn_nss(); + } + + CERT_DestroyCertificate(instance.server.cert); + SECKEY_DestroyPrivateKey(instance.server.private_key); + + SSL_ClearSessionCache(); + + SSL_ShutdownServerSessionIDCache(); + + qnetd_instance_destroy(&instance); + + qnetd_advanced_settings_destroy(&advanced_settings); + + if (NSS_Shutdown() != SECSuccess) { + qnetd_warn_nss(); + } + + if (PR_Cleanup() != PR_SUCCESS) { + qnetd_warn_nss(); + } + + qnetd_log_close(); + + return (0); +} diff --git a/qdevices/dynar-getopt-lex.c b/qdevices/dynar-getopt-lex.c new file mode 100644 index 0000000..3ed3a8c --- /dev/null +++ b/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 + +#include "dynar-getopt-lex.h" + +void +dynar_getopt_lex_init(struct dynar_getopt_lex *lex, struct dynar *input) +{ + + memset(lex, 0, sizeof(*lex)); + lex->input = input; + dynar_init(&lex->option, dynar_max_size(input)); + dynar_init(&lex->value, dynar_max_size(input)); +} + +void +dynar_getopt_lex_destroy(struct dynar_getopt_lex *lex) +{ + + dynar_destroy(&lex->option); + dynar_destroy(&lex->value); + memset(lex, 0, sizeof(*lex)); +} + +/* + * 0 - no error + * -1 - Can't add character + */ +int +dynar_getopt_lex_token_next(struct dynar_getopt_lex *lex) +{ + size_t pos; + size_t size; + char *str; + char ch; + int state; + + dynar_clean(&lex->option); + dynar_clean(&lex->value); + + size = dynar_size(lex->input); + str = dynar_data(lex->input); + + state = 1; + pos = lex->pos; + + while (state != 0 && pos < size) { + ch = str[pos]; + + switch (state) { + case 1: + /* + * Read option name, wait for = or , + */ + if (ch == '=') { + pos++; + state = 2; + } else if (ch == ',') { + pos++; + state = 0; + } else { + pos++; + if (dynar_cat(&lex->option, &ch, sizeof(ch)) != 0) { + return (-1); + } + } + break; + case 2: + /* + * Wait for end of str or , + */ + if (ch == ',') { + pos++; + state = 0; + } else { + pos++; + if (dynar_cat(&lex->value, &ch, sizeof(ch)) != 0) { + return (-1); + } + } + break; + } + } + + ch = '\0'; + if (dynar_cat(&lex->option, &ch, sizeof(ch)) != 0) { + return (-1); + } + if (dynar_cat(&lex->value, &ch, sizeof(ch)) != 0) { + return (-1); + } + + lex->pos = pos; + + return (0); +} diff --git a/qdevices/dynar-getopt-lex.h b/qdevices/dynar-getopt-lex.h new file mode 100644 index 0000000..b8ce2f9 --- /dev/null +++ b/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_ */ diff --git a/qdevices/dynar-simple-lex.c b/qdevices/dynar-simple-lex.c new file mode 100644 index 0000000..bd5080e --- /dev/null +++ b/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 + +#include "dynar-simple-lex.h" + +/* + * Simple_lex is going to be used in protocol and it's not good idea to depend on locale + */ +static int +dynar_simple_lex_is_space(char ch) +{ + return (ch == ' ' || ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\v'); +} + +void +dynar_simple_lex_init(struct dynar_simple_lex *lex, struct dynar *input, + enum dynar_simple_lex_type lex_type) +{ + + memset(lex, 0, sizeof(*lex)); + lex->input = input; + lex->lex_type = lex_type; + dynar_init(&lex->token, dynar_max_size(input)); +} + +void +dynar_simple_lex_destroy(struct dynar_simple_lex *lex) +{ + + dynar_destroy(&lex->token); + memset(lex, 0, sizeof(*lex)); +} + +struct dynar * +dynar_simple_lex_token_next(struct dynar_simple_lex *lex) +{ + size_t pos; + size_t size; + char *str; + char ch, ch2; + int add_char; + int state; + + dynar_clean(&lex->token); + + size = dynar_size(lex->input); + str = dynar_data(lex->input); + + state = 1; + pos = lex->pos; + + while (state != 0) { + if (pos < size) { + ch = str[pos]; + } else { + ch = '\0'; + } + + add_char = 0; + + switch (state) { + case 1: + /* + * Skip spaces. Newline is special and means end of processing + */ + if (pos >= size || ch == '\n' || ch == '\r') { + state = 0; + } else if (dynar_simple_lex_is_space(ch)) { + pos++; + } else { + state = 2; + } + break; + case 2: + /* + * Read word + */ + if (pos >= size) { + state = 0; + } else if ((lex->lex_type == DYNAR_SIMPLE_LEX_TYPE_BACKSLASH || + lex->lex_type == DYNAR_SIMPLE_LEX_TYPE_QUOTE) && ch == '\\') { + pos++; + state = 3; + } else if (lex->lex_type == DYNAR_SIMPLE_LEX_TYPE_QUOTE && + ch == '"') { + pos++; + state = 4; + } else if (dynar_simple_lex_is_space(ch)) { + state = 0; + } else { + pos++; + add_char = 1; + } + break; + case 3: + /* + * Process backslash + */ + if (pos >= size || ch == '\n' || ch == '\r') { + /* + * End of string. Do not include backslash (it's just ignored) + */ + state = 0; + } else { + add_char = 1; + state = 2; + pos++; + } + break; + case 4: + /* + * Quote word + */ + if (pos >= size) { + state = 0; + } else if (ch == '\\') { + state = 5; + pos++; + } else if (ch == '"') { + state = 2; + pos++; + } else if (ch == '\n' || ch == '\r') { + state = 0; + } else { + pos++; + add_char = 1; + } + break; + case 5: + /* + * Quote word backslash + */ + if (pos >= size || ch == '\n' || ch == '\r') { + /* + * End of string. Do not include backslash (it's just ignored) + */ + state = 0; + } else if (ch == '\\' || ch == '"') { + add_char = 1; + state = 4; + pos++; + } else { + ch2 = '\\'; + if (dynar_cat(&lex->token, &ch2, sizeof(ch2)) != 0) { + return (NULL); + } + + add_char = 1; + state = 4; + pos++; + } + break; + } + + if (add_char) { + if (dynar_cat(&lex->token, &ch, sizeof(ch)) != 0) { + return (NULL); + } + } + } + + ch = '\0'; + if (dynar_cat(&lex->token, &ch, sizeof(ch)) != 0) { + return (NULL); + } + + lex->pos = pos; + + return (&lex->token); +} diff --git a/qdevices/dynar-simple-lex.h b/qdevices/dynar-simple-lex.h new file mode 100644 index 0000000..029c4a0 --- /dev/null +++ b/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_ */ diff --git a/qdevices/dynar-str.c b/qdevices/dynar-str.c new file mode 100644 index 0000000..597392e --- /dev/null +++ b/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 +#include +#include + +#include "dynar-str.h" + +int +dynar_str_cpy(struct dynar *dest, const char *str) +{ + + if (strlen(str) > dynar_max_size(dest)) { + return (-1); + } + + dynar_clean(dest); + + return (dynar_str_cat(dest, str)); +} + +int +dynar_str_cat(struct dynar *dest, const char *str) +{ + + return (dynar_cat(dest, str, strlen(str))); +} + +int +dynar_str_prepend(struct dynar *dest, const char *str) +{ + + return (dynar_prepend(dest, str, strlen(str))); +} + +int +dynar_str_vcatf(struct dynar *dest, const char *format, va_list ap) +{ + int to_write; + int written; + va_list ap_copy; + size_t allocated; + char buf; + char *p; + + /* + * Find out how much bytes is needed + */ + va_copy(ap_copy, ap); + to_write = vsnprintf(&buf, sizeof(buf), format, ap_copy); + va_end(ap_copy); + + if (to_write < 0) { + return (-1); + } + + if ((size_t)to_write < sizeof(buf)) { + /* + * Writing 1 byte string (snprintf writes also '\0') means string is empty + */ + + return (0); + } + + allocated = to_write + 1; + if (dynar_prealloc(dest, allocated) != 0) { + return (-1); + } + + p = dynar_data(dest) + dynar_size(dest); + + va_copy(ap_copy, ap); + written = vsnprintf(p, allocated, format, ap_copy); + va_end(ap_copy); + + if (written < 0) { + return (-1); + } + + if ((size_t)written >= allocated) { + return (-1); + } + + dest->size += written; + + return (written); +} + +int +dynar_str_catf(struct dynar *dest, const char *format, ...) +{ + va_list ap; + int res; + + va_start(ap, format); + res = dynar_str_vcatf(dest, format, ap); + va_end(ap); + + return (res); +} + +int +dynar_str_quote_cat(struct dynar *dest, const char *str) +{ + size_t zi; + + if (dynar_str_cat(dest, "\"") != 0) { + return (-1); + } + + for (zi = 0; zi < strlen(str); zi++) { + if (str[zi] == '"' || str[zi] == '\\') { + if (dynar_str_cat(dest, "\\") != 0) { + return (-1); + } + } + + if (dynar_cat(dest, &str[zi], sizeof(str[zi])) != 0) { + return (-1); + } + } + + if (dynar_str_cat(dest, "\"") != 0) { + return (-1); + } + + return (0); +} + +int +dynar_str_quote_cpy(struct dynar *dest, const char *str) +{ + + dynar_clean(dest); + + return (dynar_str_quote_cat(dest, str)); +} diff --git a/qdevices/dynar-str.h b/qdevices/dynar-str.h new file mode 100644 index 0000000..98ec187 --- /dev/null +++ b/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 + +#include "dynar.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int dynar_str_cpy(struct dynar *dest, const char *str); + +extern int dynar_str_cat(struct dynar *dest, const char *str); + +extern int dynar_str_catf(struct dynar *dest, const char *format, ...) + __attribute__((__format__(__printf__, 2, 3))); + +extern int dynar_str_vcatf(struct dynar *dest, const char *format, va_list ap) + __attribute__((__format__(__printf__, 2, 0))); + +extern int dynar_str_prepend(struct dynar *dest, const char *str); + +extern int dynar_str_quote_cat(struct dynar *dest, const char *str); + +extern int dynar_str_quote_cpy(struct dynar *dest, const char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* _DYNAR_STR_H_ */ diff --git a/qdevices/dynar.c b/qdevices/dynar.c new file mode 100644 index 0000000..b8e7775 --- /dev/null +++ b/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 + +#include +#include + +#include "dynar.h" + +void +dynar_init(struct dynar *array, size_t maximum_size) +{ + + memset(array, 0, sizeof(*array)); + array->maximum_size = maximum_size; +} + +void +dynar_set_max_size(struct dynar *array, size_t maximum_size) +{ + + array->maximum_size = maximum_size; +} + +int +dynar_set_size(struct dynar *array, size_t size) +{ + + if (size > dynar_max_size(array)) { + dynar_set_max_size(array, size); + } + + if (size > dynar_size(array)) { + if (dynar_prealloc(array, size - dynar_size(array)) == -1) { + return (-1); + } + } + + array->size = size; + + return (0); +} + +void +dynar_destroy(struct dynar *array) +{ + + free(array->data); + dynar_init(array, array->maximum_size); +} + +void +dynar_clean(struct dynar *array) +{ + + array->size = 0; +} + +size_t +dynar_size(const struct dynar *array) +{ + + return (array->size); +} + +size_t +dynar_max_size(const struct dynar *array) +{ + + return (array->maximum_size); +} + +char * +dynar_data(const struct dynar *array) +{ + + return (array->data); +} + +static int +dynar_realloc(struct dynar *array, size_t new_array_size) +{ + char *new_data; + + if (new_array_size > array->maximum_size) { + return (-1); + } + + new_data = realloc(array->data, new_array_size); + + if (new_data == NULL) { + return (-1); + } + + array->allocated = new_array_size; + array->data = new_data; + + return (0); +} + +int +dynar_prealloc(struct dynar *array, size_t size) +{ + size_t new_size; + + if (array->size + size > array->maximum_size) { + return (-1); + } + + if (array->size + size > array->allocated) { + new_size = (array->allocated + size) * 2; + if (new_size > array->maximum_size) { + new_size = array->maximum_size; + } + + if (dynar_realloc(array, new_size) == -1) { + return (-1); + } + } + + return (0); +} + +int +dynar_cat(struct dynar *array, const void *src, size_t size) +{ + + if (dynar_prealloc(array, size) != 0) { + return (-1); + } + + memmove(array->data + array->size, src, size); + array->size += size; + + return (0); +} + +int +dynar_prepend(struct dynar *array, const void *src, size_t size) +{ + + if (dynar_prealloc(array, size) != 0) { + return (-1); + } + + memmove(array->data + size, array->data, array->size); + memmove(array->data, src, size); + array->size += size; + + return (0); +} diff --git a/qdevices/dynar.h b/qdevices/dynar.h new file mode 100644 index 0000000..26f3ac5 --- /dev/null +++ b/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 +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Dynamic array structure + */ +struct dynar { + char *data; + size_t size; + size_t allocated; + size_t maximum_size; +}; + +extern void dynar_init(struct dynar *array, size_t maximum_size); + +extern void dynar_destroy(struct dynar *array); + +extern void dynar_clean(struct dynar *array); + +extern size_t dynar_size(const struct dynar *array); + +extern size_t dynar_max_size(const struct dynar *array); + +extern void dynar_set_max_size(struct dynar *array, size_t maximum_size); + +extern char *dynar_data(const struct dynar *array); + +extern int dynar_cat(struct dynar *array, const void *src, size_t size); + +extern int dynar_prealloc(struct dynar *array, size_t size); + +extern int dynar_prepend(struct dynar *array, const void *src, size_t size); + +extern int dynar_set_size(struct dynar *array, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* _DYNAR_H_ */ diff --git a/qdevices/msg.c b/qdevices/msg.c new file mode 100644 index 0000000..8f2b13b --- /dev/null +++ b/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 +#include + +#include +#include +#include + +#include "msg.h" + +#define MSG_TYPE_LENGTH 2 +#define MSG_LENGTH_LENGTH 4 + +#define MSG_STATIC_SUPPORTED_MESSAGES_SIZE 18 + +enum msg_type msg_static_supported_messages[MSG_STATIC_SUPPORTED_MESSAGES_SIZE] = { + MSG_TYPE_PREINIT, + MSG_TYPE_PREINIT_REPLY, + MSG_TYPE_STARTTLS, + MSG_TYPE_INIT, + MSG_TYPE_INIT_REPLY, + MSG_TYPE_SERVER_ERROR, + MSG_TYPE_SET_OPTION, + MSG_TYPE_SET_OPTION_REPLY, + MSG_TYPE_ECHO_REQUEST, + MSG_TYPE_ECHO_REPLY, + MSG_TYPE_NODE_LIST, + MSG_TYPE_NODE_LIST_REPLY, + MSG_TYPE_ASK_FOR_VOTE, + MSG_TYPE_ASK_FOR_VOTE_REPLY, + MSG_TYPE_VOTE_INFO, + MSG_TYPE_VOTE_INFO_REPLY, + MSG_TYPE_HEURISTICS_CHANGE, + MSG_TYPE_HEURISTICS_CHANGE_REPLY, +}; + +size_t +msg_get_header_length(void) +{ + return (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH); +} + +static void +msg_add_type(struct dynar *msg, enum msg_type type) +{ + uint16_t ntype; + + ntype = htons((uint16_t)type); + dynar_cat(msg, &ntype, sizeof(ntype)); +} + +enum msg_type +msg_get_type(const struct dynar *msg) +{ + uint16_t ntype; + uint16_t type; + + memcpy(&ntype, dynar_data(msg), sizeof(ntype)); + type = ntohs(ntype); + + return (type); +} + +/* + * We don't know size of message before call of this function, so zero is + * added. Real value is set afterwards by msg_set_len. + */ +static void +msg_add_len(struct dynar *msg) +{ + uint32_t len; + + len = 0; + dynar_cat(msg, &len, sizeof(len)); +} + +static void +msg_set_len(struct dynar *msg, uint32_t len) +{ + uint32_t nlen; + + nlen = htonl(len); + memcpy(dynar_data(msg) + MSG_TYPE_LENGTH, &nlen, sizeof(nlen)); +} + +/* + * Used only for echo reply msg. All other messages should use msg_add_type. + */ +static void +msg_set_type(struct dynar *msg, enum msg_type type) +{ + uint16_t ntype; + + ntype = htons((uint16_t)type); + memcpy(dynar_data(msg), &ntype, sizeof(ntype)); +} + +uint32_t +msg_get_len(const struct dynar *msg) +{ + uint32_t nlen; + uint32_t len; + + memcpy(&nlen, dynar_data(msg) + MSG_TYPE_LENGTH, sizeof(nlen)); + len = ntohl(nlen); + + return (len); +} + + +size_t +msg_create_preinit(struct dynar *msg, const char *cluster_name, int add_msg_seq_number, + uint32_t msg_seq_number) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_PREINIT); + msg_add_len(msg); + + if (add_msg_seq_number) { + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + } + + if (tlv_add_cluster_name(msg, cluster_name) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_preinit_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number, + enum tlv_tls_supported tls_supported, int tls_client_cert_required) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_PREINIT_REPLY); + msg_add_len(msg); + + if (add_msg_seq_number) { + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + } + + if (tlv_add_tls_supported(msg, tls_supported) == -1) { + goto small_buf_err; + } + + if (tlv_add_tls_client_cert_required(msg, tls_client_cert_required) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_starttls(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_STARTTLS); + msg_add_len(msg); + + if (add_msg_seq_number) { + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_server_error(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number, + enum tlv_reply_error_code reply_error_code) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_SERVER_ERROR); + msg_add_len(msg); + + if (add_msg_seq_number) { + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + } + + if (tlv_add_reply_error_code(msg, reply_error_code) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +static uint16_t * +msg_convert_msg_type_array_to_u16_array(const enum msg_type *msg_type_array, size_t array_size) +{ + uint16_t *u16a; + size_t i; + + u16a = malloc(sizeof(*u16a) * array_size); + if (u16a == NULL) { + return (NULL); + } + + for (i = 0; i < array_size; i++) { + u16a[i] = (uint16_t)msg_type_array[i]; + } + + return (u16a); +} + +size_t +msg_create_init(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number, + enum tlv_decision_algorithm_type decision_algorithm, + const enum msg_type *supported_msgs, size_t no_supported_msgs, + const enum tlv_opt_type *supported_opts, size_t no_supported_opts, uint32_t node_id, + uint32_t heartbeat_interval, const struct tlv_tie_breaker *tie_breaker, + const struct tlv_ring_id *ring_id) +{ + uint16_t *u16a; + int res; + + u16a = NULL; + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_INIT); + msg_add_len(msg); + + if (add_msg_seq_number) { + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + } + + if (supported_msgs != NULL && no_supported_msgs > 0) { + u16a = msg_convert_msg_type_array_to_u16_array(supported_msgs, no_supported_msgs); + + if (u16a == NULL) { + goto small_buf_err; + } + + res = tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_MESSAGES, u16a, no_supported_msgs); + + free(u16a); + + if (res == -1) { + goto small_buf_err; + } + } + + if (supported_opts != NULL && no_supported_opts > 0) { + if (tlv_add_supported_options(msg, supported_opts, no_supported_opts) == -1) { + goto small_buf_err; + } + } + + if (tlv_add_node_id(msg, node_id) == -1) { + goto small_buf_err; + } + + if (tlv_add_decision_algorithm(msg, decision_algorithm) == -1) { + goto small_buf_err; + } + + if (tlv_add_heartbeat_interval(msg, heartbeat_interval) == -1) { + goto small_buf_err; + } + + if (tlv_add_tie_breaker(msg, tie_breaker) == -1) { + goto small_buf_err; + } + + if (tlv_add_ring_id(msg, ring_id) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_init_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number, + enum tlv_reply_error_code reply_error_code, + const enum msg_type *supported_msgs, size_t no_supported_msgs, + const enum tlv_opt_type *supported_opts, size_t no_supported_opts, + size_t server_maximum_request_size, size_t server_maximum_reply_size, + const enum tlv_decision_algorithm_type *supported_decision_algorithms, + size_t no_supported_decision_algorithms) +{ + uint16_t *u16a; + int res; + + u16a = NULL; + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_INIT_REPLY); + msg_add_len(msg); + + if (tlv_add_reply_error_code(msg, reply_error_code) == -1) { + goto small_buf_err; + } + + if (supported_msgs != NULL && no_supported_msgs > 0) { + u16a = msg_convert_msg_type_array_to_u16_array(supported_msgs, no_supported_msgs); + + if (u16a == NULL) { + goto small_buf_err; + } + + res = tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_MESSAGES, u16a, no_supported_msgs); + + free(u16a); + + if (res == -1) { + goto small_buf_err; + } + } + + if (supported_opts != NULL && no_supported_opts > 0) { + if (tlv_add_supported_options(msg, supported_opts, no_supported_opts) == -1) { + goto small_buf_err; + } + } + + if (add_msg_seq_number) { + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + } + + if (tlv_add_server_maximum_request_size(msg, server_maximum_request_size) == -1) { + goto small_buf_err; + } + + if (tlv_add_server_maximum_reply_size(msg, server_maximum_reply_size) == -1) { + goto small_buf_err; + } + + if (supported_decision_algorithms != NULL && no_supported_decision_algorithms > 0) { + if (tlv_add_supported_decision_algorithms(msg, supported_decision_algorithms, + no_supported_decision_algorithms) == -1) { + goto small_buf_err; + } + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_set_option(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number, + int add_heartbeat_interval, uint32_t heartbeat_interval) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_SET_OPTION); + msg_add_len(msg); + + if (add_msg_seq_number) { + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + } + + if (add_heartbeat_interval) { + if (tlv_add_heartbeat_interval(msg, heartbeat_interval) == -1) { + goto small_buf_err; + } + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_set_option_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number, + uint32_t heartbeat_interval) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_SET_OPTION_REPLY); + msg_add_len(msg); + + if (add_msg_seq_number) { + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + } + + if (tlv_add_heartbeat_interval(msg, heartbeat_interval) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_echo_request(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_ECHO_REQUEST); + msg_add_len(msg); + + if (add_msg_seq_number) { + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_echo_reply(struct dynar *msg, const struct dynar *echo_request_msg) +{ + + dynar_clean(msg); + + if (dynar_cat(msg, dynar_data(echo_request_msg), dynar_size(echo_request_msg)) == -1) { + goto small_buf_err; + } + + msg_set_type(msg, MSG_TYPE_ECHO_REPLY); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_node_list(struct dynar *msg, + uint32_t msg_seq_number, enum tlv_node_list_type node_list_type, + int add_ring_id, const struct tlv_ring_id *ring_id, + int add_config_version, uint64_t config_version, + int add_quorate, enum tlv_quorate quorate, + int add_heuristics, enum tlv_heuristics heuristics, + const struct node_list *nodes) +{ + struct node_list_entry *node_info; + struct tlv_node_info tlv_ni; + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_NODE_LIST); + msg_add_len(msg); + + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + + if (tlv_add_node_list_type(msg, node_list_type) == -1) { + goto small_buf_err; + } + + if (add_ring_id) { + if (tlv_add_ring_id(msg, ring_id) == -1) { + goto small_buf_err; + } + } + + if (add_config_version) { + if (tlv_add_config_version(msg, config_version) == -1) { + goto small_buf_err; + } + } + + if (add_quorate) { + if (tlv_add_quorate(msg, quorate) == -1) { + goto small_buf_err; + } + } + + TAILQ_FOREACH(node_info, nodes, entries) { + node_list_entry_to_tlv_node_info(node_info, &tlv_ni); + + if (tlv_add_node_info(msg, &tlv_ni) == -1) { + goto small_buf_err; + } + } + + if (add_heuristics && heuristics != TLV_HEURISTICS_UNDEFINED) { + if (tlv_add_heuristics(msg, heuristics) == -1) { + goto small_buf_err; + } + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_node_list_reply(struct dynar *msg, uint32_t msg_seq_number, + enum tlv_node_list_type node_list_type, const struct tlv_ring_id *ring_id, + enum tlv_vote vote) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_NODE_LIST_REPLY); + msg_add_len(msg); + + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + + if (tlv_add_node_list_type(msg, node_list_type) == -1) { + goto small_buf_err; + } + + if (tlv_add_ring_id(msg, ring_id) == -1) { + goto small_buf_err; + } + + if (tlv_add_vote(msg, vote) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_ask_for_vote(struct dynar *msg, uint32_t msg_seq_number) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_ASK_FOR_VOTE); + msg_add_len(msg); + + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_ask_for_vote_reply(struct dynar *msg, uint32_t msg_seq_number, + const struct tlv_ring_id *ring_id, enum tlv_vote vote) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_ASK_FOR_VOTE_REPLY); + msg_add_len(msg); + + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + + if (tlv_add_vote(msg, vote) == -1) { + goto small_buf_err; + } + + if (tlv_add_ring_id(msg, ring_id) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_vote_info(struct dynar *msg, uint32_t msg_seq_number, const struct tlv_ring_id *ring_id, + enum tlv_vote vote) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_VOTE_INFO); + msg_add_len(msg); + + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + + if (tlv_add_vote(msg, vote) == -1) { + goto small_buf_err; + } + + if (tlv_add_ring_id(msg, ring_id) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_vote_info_reply(struct dynar *msg, uint32_t msg_seq_number) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_VOTE_INFO_REPLY); + msg_add_len(msg); + + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_heuristics_change(struct dynar *msg, uint32_t msg_seq_number, + enum tlv_heuristics heuristics) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_HEURISTICS_CHANGE); + msg_add_len(msg); + + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + + if (tlv_add_heuristics(msg, heuristics) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +size_t +msg_create_heuristics_change_reply(struct dynar *msg, uint32_t msg_seq_number, + const struct tlv_ring_id *ring_id, enum tlv_heuristics heuristics, enum tlv_vote vote) +{ + + dynar_clean(msg); + + msg_add_type(msg, MSG_TYPE_HEURISTICS_CHANGE_REPLY); + msg_add_len(msg); + + if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) { + goto small_buf_err; + } + + if (tlv_add_vote(msg, vote) == -1) { + goto small_buf_err; + } + + if (tlv_add_ring_id(msg, ring_id) == -1) { + goto small_buf_err; + } + + if (tlv_add_heuristics(msg, heuristics) == -1) { + goto small_buf_err; + } + + msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH)); + + return (dynar_size(msg)); + +small_buf_err: + return (0); +} + +int +msg_is_valid_msg_type(const struct dynar *msg) +{ + enum msg_type type; + size_t i; + + type = msg_get_type(msg); + + for (i = 0; i < MSG_STATIC_SUPPORTED_MESSAGES_SIZE; i++) { + if (msg_static_supported_messages[i] == type) { + return (1); + } + } + + return (0); +} + +void +msg_decoded_init(struct msg_decoded *decoded_msg) +{ + + memset(decoded_msg, 0, sizeof(*decoded_msg)); + + node_list_init(&decoded_msg->nodes); +} + +void +msg_decoded_destroy(struct msg_decoded *decoded_msg) +{ + + free(decoded_msg->cluster_name); + free(decoded_msg->supported_messages); + free(decoded_msg->supported_options); + free(decoded_msg->supported_decision_algorithms); + node_list_free(&decoded_msg->nodes); + + msg_decoded_init(decoded_msg); +} + +/* + * 0 - No error + * -1 - option with invalid length + * -2 - Unable to allocate memory + * -3 - Inconsistent msg (tlv len > msg size) + * -4 - invalid option content + */ +int +msg_decode(const struct dynar *msg, struct msg_decoded *decoded_msg) +{ + struct tlv_iterator tlv_iter; + uint16_t *u16a; + uint32_t u32; + uint64_t u64; + struct tlv_ring_id ring_id; + struct tlv_node_info node_info; + struct tlv_tie_breaker tie_breaker; + size_t zi; + enum tlv_opt_type opt_type; + int iter_res; + int res; + + msg_decoded_destroy(decoded_msg); + + decoded_msg->type = msg_get_type(msg); + + tlv_iter_init(msg, msg_get_header_length(), &tlv_iter); + + while ((iter_res = tlv_iter_next(&tlv_iter)) > 0) { + opt_type = tlv_iter_get_type(&tlv_iter); + + switch (opt_type) { + case TLV_OPT_MSG_SEQ_NUMBER: + if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) { + return (res); + } + + decoded_msg->seq_number_set = 1; + decoded_msg->seq_number = u32; + break; + case TLV_OPT_CLUSTER_NAME: + if ((res = tlv_iter_decode_str(&tlv_iter, &decoded_msg->cluster_name, + &decoded_msg->cluster_name_len)) != 0) { + return (-2); + } + break; + case TLV_OPT_TLS_SUPPORTED: + if ((res = tlv_iter_decode_tls_supported(&tlv_iter, + &decoded_msg->tls_supported)) != 0) { + return (res); + } + + decoded_msg->tls_supported_set = 1; + break; + case TLV_OPT_TLS_CLIENT_CERT_REQUIRED: + if ((res = tlv_iter_decode_client_cert_required(&tlv_iter, + &decoded_msg->tls_client_cert_required)) != 0) { + return (res); + } + + decoded_msg->tls_client_cert_required_set = 1; + break; + case TLV_OPT_SUPPORTED_MESSAGES: + free(decoded_msg->supported_messages); + + if ((res = tlv_iter_decode_u16_array(&tlv_iter, &u16a, + &decoded_msg->no_supported_messages)) != 0) { + return (res); + } + + decoded_msg->supported_messages = + malloc(sizeof(enum msg_type) * decoded_msg->no_supported_messages); + + if (decoded_msg->supported_messages == NULL) { + free(u16a); + return (-2); + } + + for (zi = 0; zi < decoded_msg->no_supported_messages; zi++) { + decoded_msg->supported_messages[zi] = (enum msg_type)u16a[zi]; + } + + free(u16a); + break; + case TLV_OPT_SUPPORTED_OPTIONS: + free(decoded_msg->supported_options); + + if ((res = tlv_iter_decode_supported_options(&tlv_iter, + &decoded_msg->supported_options, + &decoded_msg->no_supported_options)) != 0) { + return (res); + } + break; + case TLV_OPT_REPLY_ERROR_CODE: + if ((res = tlv_iter_decode_reply_error_code(&tlv_iter, + &decoded_msg->reply_error_code)) != 0) { + return (res); + } + + decoded_msg->reply_error_code_set = 1; + break; + case TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE: + if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) { + return (res); + } + + decoded_msg->server_maximum_request_size_set = 1; + decoded_msg->server_maximum_request_size = u32; + break; + case TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE: + if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) { + return (res); + } + + decoded_msg->server_maximum_reply_size_set = 1; + decoded_msg->server_maximum_reply_size = u32; + break; + case TLV_OPT_NODE_ID: + if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) { + return (res); + } + + decoded_msg->node_id_set = 1; + decoded_msg->node_id = u32; + break; + case TLV_OPT_SUPPORTED_DECISION_ALGORITHMS: + free(decoded_msg->supported_decision_algorithms); + + if ((res = tlv_iter_decode_supported_decision_algorithms(&tlv_iter, + &decoded_msg->supported_decision_algorithms, + &decoded_msg->no_supported_decision_algorithms)) != 0) { + return (res); + } + break; + case TLV_OPT_DECISION_ALGORITHM: + if ((res = tlv_iter_decode_decision_algorithm(&tlv_iter, + &decoded_msg->decision_algorithm)) != 0) { + return (res); + } + + decoded_msg->decision_algorithm_set = 1; + break; + case TLV_OPT_HEARTBEAT_INTERVAL: + if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) { + return (res); + } + + decoded_msg->heartbeat_interval_set = 1; + decoded_msg->heartbeat_interval = u32; + break; + case TLV_OPT_RING_ID: + if ((res = tlv_iter_decode_ring_id(&tlv_iter, &ring_id)) != 0) { + return (res); + } + + decoded_msg->ring_id_set = 1; + memcpy(&decoded_msg->ring_id, &ring_id, sizeof(ring_id)); + break; + case TLV_OPT_CONFIG_VERSION: + if ((res = tlv_iter_decode_u64(&tlv_iter, &u64)) != 0) { + return (res); + } + + decoded_msg->config_version_set = 1; + decoded_msg->config_version = u64; + break; + case TLV_OPT_DATA_CENTER_ID: + if ((res = tlv_iter_decode_u32(&tlv_iter, &u32)) != 0) { + return (res); + } + + decoded_msg->data_center_id = u32; + break; + case TLV_OPT_NODE_STATE: + if ((res = tlv_iter_decode_node_state(&tlv_iter, + &decoded_msg->node_state)) != 0) { + return (res); + } + break; + case TLV_OPT_NODE_INFO: + if ((res = tlv_iter_decode_node_info(&tlv_iter, &node_info)) != 0) { + return (res); + } + + if (node_list_add_from_node_info(&decoded_msg->nodes, &node_info) == NULL) { + return (-2); + } + break; + case TLV_OPT_NODE_LIST_TYPE: + if ((res = tlv_iter_decode_node_list_type(&tlv_iter, + &decoded_msg->node_list_type)) != 0) { + return (res); + } + + decoded_msg->node_list_type_set = 1; + break; + case TLV_OPT_VOTE: + if ((res = tlv_iter_decode_vote(&tlv_iter, &decoded_msg->vote)) != 0) { + return (res); + } + + decoded_msg->vote_set = 1; + break; + case TLV_OPT_QUORATE: + if ((res = tlv_iter_decode_quorate(&tlv_iter, + &decoded_msg->quorate)) != 0) { + return (res); + } + + decoded_msg->quorate_set = 1; + break; + case TLV_OPT_TIE_BREAKER: + if ((res = tlv_iter_decode_tie_breaker(&tlv_iter, &tie_breaker)) != 0) { + return (res); + } + + decoded_msg->tie_breaker_set = 1; + memcpy(&decoded_msg->tie_breaker, &tie_breaker, sizeof(tie_breaker)); + break; + case TLV_OPT_HEURISTICS: + if ((res = tlv_iter_decode_heuristics(&tlv_iter, + &decoded_msg->heuristics)) != 0) { + return (res); + } + break; + /* + * Default is not defined intentionally. Compiler shows warning when + * new tlv option is added. Also protocol ignores unknown options so + * no extra work is needed. + */ + } + } + + if (iter_res != 0) { + return (-3); + } + + return (0); +} + +void +msg_get_supported_messages(enum msg_type **supported_messages, size_t *no_supported_messages) +{ + + *supported_messages = msg_static_supported_messages; + *no_supported_messages = MSG_STATIC_SUPPORTED_MESSAGES_SIZE; +} + +const char * +msg_type_to_str(enum msg_type type) +{ + + switch (type) { + case MSG_TYPE_PREINIT: return ("Preinit"); break; + case MSG_TYPE_PREINIT_REPLY: return ("Preinit reply"); break; + case MSG_TYPE_STARTTLS: return ("StartTLS"); break; + case MSG_TYPE_INIT: return ("Init"); break; + case MSG_TYPE_INIT_REPLY: return ("Init reply"); break; + case MSG_TYPE_SERVER_ERROR: return ("Server error"); break; + case MSG_TYPE_SET_OPTION: return ("Set option"); break; + case MSG_TYPE_SET_OPTION_REPLY: return ("Set option reply"); break; + case MSG_TYPE_ECHO_REQUEST: return ("Echo request"); break; + case MSG_TYPE_ECHO_REPLY: return ("Echo reply"); break; + case MSG_TYPE_NODE_LIST: return ("Node list"); break; + case MSG_TYPE_NODE_LIST_REPLY: return ("Node list reply"); break; + case MSG_TYPE_ASK_FOR_VOTE: return ("Ask for vote"); break; + case MSG_TYPE_ASK_FOR_VOTE_REPLY: return ("Ask for vote reply"); break; + case MSG_TYPE_VOTE_INFO: return ("Vote info"); break; + case MSG_TYPE_VOTE_INFO_REPLY: return ("Vote info reply"); break; + case MSG_TYPE_HEURISTICS_CHANGE: return ("Heuristics change"); break; + case MSG_TYPE_HEURISTICS_CHANGE_REPLY: return ("Heuristics change reply"); break; + } + + return ("Unknown message type"); +} diff --git a/qdevices/msg.h b/qdevices/msg.h new file mode 100644 index 0000000..bb2b390 --- /dev/null +++ b/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 +#include + +#include "dynar.h" +#include "tlv.h" +#include "node-list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum msg_type { + MSG_TYPE_PREINIT = 0, + MSG_TYPE_PREINIT_REPLY = 1, + MSG_TYPE_STARTTLS = 2, + MSG_TYPE_INIT = 3, + MSG_TYPE_INIT_REPLY = 4, + MSG_TYPE_SERVER_ERROR = 5, + MSG_TYPE_SET_OPTION = 6, + MSG_TYPE_SET_OPTION_REPLY = 7, + MSG_TYPE_ECHO_REQUEST = 8, + MSG_TYPE_ECHO_REPLY = 9, + MSG_TYPE_NODE_LIST = 10, + MSG_TYPE_NODE_LIST_REPLY = 11, + MSG_TYPE_ASK_FOR_VOTE = 12, + MSG_TYPE_ASK_FOR_VOTE_REPLY = 13, + MSG_TYPE_VOTE_INFO = 14, + MSG_TYPE_VOTE_INFO_REPLY = 15, + MSG_TYPE_HEURISTICS_CHANGE = 16, + MSG_TYPE_HEURISTICS_CHANGE_REPLY = 17, +}; + +struct msg_decoded { + enum msg_type type; + uint8_t seq_number_set; + uint32_t seq_number; /* Only valid if seq_number_set != 0 */ + size_t cluster_name_len; + /* Valid only if != NULL. Trailing \0 is added but not counted in cluster_name_len */ + char *cluster_name; + uint8_t tls_supported_set; + enum tlv_tls_supported tls_supported; /* Valid only if tls_supported_set != 0. */ + uint8_t tls_client_cert_required_set; + uint8_t tls_client_cert_required; /* Valid only if tls_client_cert_required_set != 0 */ + size_t no_supported_messages; + enum msg_type *supported_messages; /* Valid only if != NULL */ + size_t no_supported_options; + enum tlv_opt_type *supported_options; /* Valid only if != NULL */ + uint8_t reply_error_code_set; + enum tlv_reply_error_code reply_error_code; /* Valid only if reply_error_code_set != 0 */ + uint8_t server_maximum_request_size_set; + /* Valid only if server_maximum_request_size_set != 0 */ + size_t server_maximum_request_size; + uint8_t server_maximum_reply_size_set; + size_t server_maximum_reply_size; /* Valid only if server_maximum_reply_size_set != 0 */ + uint8_t node_id_set; + uint32_t node_id; + size_t no_supported_decision_algorithms; + /* Valid only if != NULL */ + enum tlv_decision_algorithm_type *supported_decision_algorithms; + uint8_t decision_algorithm_set; + /* Valid only if decision_algorithm_set != 0 */ + enum tlv_decision_algorithm_type decision_algorithm; + uint8_t heartbeat_interval_set; + uint32_t heartbeat_interval; /* Valid only if heartbeat_interval_set != 0 */ + uint8_t ring_id_set; + struct tlv_ring_id ring_id; /* Valid only if ring_id_set != 0 */ + uint8_t config_version_set; + uint64_t config_version; /* Valid only if config_version_set != 0 */ + uint32_t data_center_id; /* Valid only if != 0 */ + enum tlv_node_state node_state; /* Valid only if != TLV_NODE_STATE_NOT_SET */ + struct node_list nodes; /* Valid only if node_list_is_empty(nodes) != 0 */ + int node_list_type_set; + enum tlv_node_list_type node_list_type; /* Valid only if node_list_type_set != 0 */ + int vote_set; + enum tlv_vote vote; /* Valid only if vote_set != 0 */ + int quorate_set; + enum tlv_quorate quorate; /* Valid only if quorate_set != 0 */ + int tie_breaker_set; + struct tlv_tie_breaker tie_breaker; + enum tlv_heuristics heuristics; /* Always valid but can be TLV_HEURISTICS_UNDEFINED */ +}; + +extern size_t msg_create_preinit(struct dynar *msg, const char *cluster_name, + int add_msg_seq_number, uint32_t msg_seq_number); + +extern size_t msg_create_preinit_reply(struct dynar *msg, int add_msg_seq_number, + uint32_t msg_seq_number, enum tlv_tls_supported tls_supported, int tls_client_cert_required); + +extern size_t msg_create_starttls(struct dynar *msg, int add_msg_seq_number, + uint32_t msg_seq_number); + +extern size_t msg_create_init(struct dynar *msg, int add_msg_seq_number, + uint32_t msg_seq_number, enum tlv_decision_algorithm_type decision_algorithm, + const enum msg_type *supported_msgs, size_t no_supported_msgs, + const enum tlv_opt_type *supported_opts, size_t no_supported_opts, uint32_t node_id, + uint32_t heartbeat_interval, const struct tlv_tie_breaker *tie_breaker, + const struct tlv_ring_id *ring_id); + +extern size_t msg_create_server_error(struct dynar *msg, int add_msg_seq_number, + uint32_t msg_seq_number, enum tlv_reply_error_code reply_error_code); + +extern size_t msg_create_init_reply(struct dynar *msg, int add_msg_seq_number, + uint32_t msg_seq_number, enum tlv_reply_error_code reply_error_code, + const enum msg_type *supported_msgs, size_t no_supported_msgs, + const enum tlv_opt_type *supported_opts, size_t no_supported_opts, + size_t server_maximum_request_size, size_t server_maximum_reply_size, + const enum tlv_decision_algorithm_type *supported_decision_algorithms, + size_t no_supported_decision_algorithms); + +extern size_t msg_create_set_option(struct dynar *msg, + int add_msg_seq_number, uint32_t msg_seq_number, + int add_heartbeat_interval, uint32_t heartbeat_interval); + +extern size_t msg_create_set_option_reply(struct dynar *msg, + int add_msg_seq_number, uint32_t msg_seq_number, uint32_t heartbeat_interval); + +extern size_t msg_create_echo_request(struct dynar *msg, int add_msg_seq_number, + uint32_t msg_seq_number); + +extern size_t msg_create_echo_reply(struct dynar *msg, + const struct dynar *echo_request_msg); + +extern size_t msg_create_node_list(struct dynar *msg, + uint32_t msg_seq_number, enum tlv_node_list_type node_list_type, + int add_ring_id, const struct tlv_ring_id *ring_id, + int add_config_version, uint64_t config_version, + int add_quorate, enum tlv_quorate quorate, + int add_heuristics, enum tlv_heuristics heuristics, + const struct node_list *nodes); + +extern size_t msg_create_node_list_reply(struct dynar *msg, uint32_t msg_seq_number, + enum tlv_node_list_type node_list_type, const struct tlv_ring_id *ring_id, + enum tlv_vote vote); + +extern size_t msg_create_ask_for_vote(struct dynar *msg, uint32_t msg_seq_number); + +extern size_t msg_create_ask_for_vote_reply(struct dynar *msg, uint32_t msg_seq_number, + const struct tlv_ring_id *ring_id, enum tlv_vote vote); + +extern size_t msg_create_vote_info(struct dynar *msg, uint32_t msg_seq_number, + const struct tlv_ring_id *ring_id, enum tlv_vote vote); + +extern size_t msg_create_vote_info_reply(struct dynar *msg, uint32_t msg_seq_number); + +extern size_t msg_create_heuristics_change(struct dynar *msg, uint32_t msg_seq_number, + enum tlv_heuristics heuristics); + +extern size_t msg_create_heuristics_change_reply(struct dynar *msg, + uint32_t msg_seq_number, const struct tlv_ring_id *ring_id, enum tlv_heuristics heuristics, + enum tlv_vote vote); + +extern size_t msg_get_header_length(void); + +extern uint32_t msg_get_len(const struct dynar *msg); + +extern enum msg_type msg_get_type(const struct dynar *msg); + +extern int msg_is_valid_msg_type(const struct dynar *msg); + +extern void msg_decoded_init(struct msg_decoded *decoded_msg); + +extern void msg_decoded_destroy(struct msg_decoded *decoded_msg); + +extern int msg_decode(const struct dynar *msg, struct msg_decoded *decoded_msg); + +extern void msg_get_supported_messages(enum msg_type **supported_messages, + size_t *no_supported_messages); + +extern const char * msg_type_to_str(enum msg_type type); + +#ifdef __cplusplus +} +#endif + +#endif /* _MSG_H_ */ diff --git a/qdevices/msgio.c b/qdevices/msgio.c new file mode 100644 index 0000000..cedff34 --- /dev/null +++ b/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); +} diff --git a/qdevices/msgio.h b/qdevices/msgio.h new file mode 100644 index 0000000..aacb7a8 --- /dev/null +++ b/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 + +#include "dynar.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern ssize_t msgio_send(PRFileDesc *sock, const char *msg, size_t msg_len, + size_t *start_pos); + +extern ssize_t msgio_send_blocking(PRFileDesc *sock, const char *msg, size_t msg_len); + +extern int msgio_write(PRFileDesc *sock, const struct dynar *msg, size_t *already_sent_bytes); + +extern int msgio_read(PRFileDesc *sock, struct dynar *msg, size_t *already_received_bytes, + int *skipping_msg); + +#ifdef __cplusplus +} +#endif + +#endif /* _MSGIO_H_ */ diff --git a/qdevices/node-list.c b/qdevices/node-list.c new file mode 100644 index 0000000..ec0efc4 --- /dev/null +++ b/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 +#include +#include + +#include "node-list.h" + +void +node_list_init(struct node_list *list) +{ + + TAILQ_INIT(list); +} + +struct node_list_entry * +node_list_add(struct node_list *list, uint32_t node_id, uint32_t data_center_id, + enum tlv_node_state node_state) +{ + struct node_list_entry *node; + + node = (struct node_list_entry *)malloc(sizeof(*node)); + if (node == NULL) { + return (NULL); + } + + memset(node, 0, sizeof(*node)); + + node->node_id = node_id; + node->data_center_id = data_center_id; + node->node_state = node_state; + + TAILQ_INSERT_TAIL(list, node, entries); + + return (node); +} + +struct node_list_entry * +node_list_add_from_node_info(struct node_list *list, const struct tlv_node_info *node_info) +{ + + return (node_list_add(list, node_info->node_id, node_info->data_center_id, + node_info->node_state)); +} + +int +node_list_clone(struct node_list *dst_list, const struct node_list *src_list) +{ + struct node_list_entry *node_entry; + + node_list_init(dst_list); + + TAILQ_FOREACH(node_entry, src_list, entries) { + if (node_list_add(dst_list, node_entry->node_id, node_entry->data_center_id, + node_entry->node_state) == NULL) { + node_list_free(dst_list); + + return (-1); + } + } + + return (0); +} + +void +node_list_entry_to_tlv_node_info(const struct node_list_entry *node, + struct tlv_node_info *node_info) +{ + + node_info->node_id = node->node_id; + node_info->data_center_id = node->data_center_id; + node_info->node_state = node->node_state; +} + +void +node_list_free(struct node_list *list) +{ + struct node_list_entry *node; + struct node_list_entry *node_next; + + node = TAILQ_FIRST(list); + + while (node != NULL) { + node_next = TAILQ_NEXT(node, entries); + + free(node); + + node = node_next; + } + + TAILQ_INIT(list); +} + +void +node_list_del(struct node_list *list, struct node_list_entry *node) +{ + + TAILQ_REMOVE(list, node, entries); + + free(node); +} + +int +node_list_is_empty(const struct node_list *list) +{ + + return (TAILQ_EMPTY(list)); +} + +struct node_list_entry * +node_list_find_node_id(const struct node_list *list, uint32_t node_id) +{ + struct node_list_entry *node_entry; + + TAILQ_FOREACH(node_entry, list, entries) { + if (node_entry->node_id == node_id) { + return (node_entry); + } + } + + return (NULL); +} + +int +node_list_eq(const struct node_list *list1, const struct node_list *list2) +{ + struct node_list_entry *node1_entry; + struct node_list_entry *node2_entry; + struct node_list tmp_list; + int res; + + res = 1; + + if (node_list_clone(&tmp_list, list2) != 0) { + return (-1); + } + + TAILQ_FOREACH(node1_entry, list1, entries) { + node2_entry = node_list_find_node_id(&tmp_list, node1_entry->node_id); + if (node2_entry == NULL) { + res = 0; + goto return_res; + } + + if (node1_entry->node_id != node2_entry->node_id || + node1_entry->data_center_id != node2_entry->data_center_id || + node1_entry->node_state != node2_entry->node_state) { + res = 0; + goto return_res; + } + + node_list_del(&tmp_list, node2_entry); + } + + if (!node_list_is_empty(&tmp_list)) { + res = 0; + goto return_res; + } + +return_res: + node_list_free(&tmp_list); + + return (res); +} + +size_t +node_list_size(const struct node_list *nlist) +{ + struct node_list_entry *node_entry; + size_t res; + + res = 0; + + TAILQ_FOREACH(node_entry, nlist, entries) { + res++; + } + + return (res); +} diff --git a/qdevices/node-list.h b/qdevices/node-list.h new file mode 100644 index 0000000..b05b62d --- /dev/null +++ b/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 + +#include +#include + +#include "tlv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct node_list_entry { + uint32_t node_id; + uint32_t data_center_id; + enum tlv_node_state node_state; + TAILQ_ENTRY(node_list_entry) entries; +}; + +TAILQ_HEAD(node_list, node_list_entry); + +extern void node_list_init(struct node_list *list); + +extern struct node_list_entry *node_list_add(struct node_list *list, + uint32_t node_id, uint32_t data_center_id, enum tlv_node_state node_state); + +extern struct node_list_entry *node_list_add_from_node_info( + struct node_list *list, const struct tlv_node_info *node_info); + +extern int node_list_clone(struct node_list *dst_list, + const struct node_list *src_list); + +extern void node_list_free(struct node_list *list); + +extern void node_list_del(struct node_list *list, + struct node_list_entry *node); + +extern int node_list_is_empty(const struct node_list *list); + +extern void node_list_entry_to_tlv_node_info( + const struct node_list_entry *node, struct tlv_node_info *node_info); + +extern struct node_list_entry * node_list_find_node_id(const struct node_list *list, + uint32_t node_id); + +extern int node_list_eq(const struct node_list *list1, + const struct node_list *list2); + +extern size_t node_list_size(const struct node_list *nlist); + +#ifdef __cplusplus +} +#endif + +#endif /* _NODE_LIST_H_ */ diff --git a/qdevices/nss-sock.c b/qdevices/nss-sock.c new file mode 100644 index 0000000..f08ac88 --- /dev/null +++ b/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 + +#include "nss-sock.h" + +int +nss_sock_init_nss(char *config_dir) +{ + if (config_dir == NULL) { + if (NSS_NoDB_Init(NULL) != SECSuccess) { + return (-1); + } + } else { + if (NSS_Init(config_dir) != SECSuccess) { + return (-1); + } + } + + if (NSS_SetDomesticPolicy() != SECSuccess) { + return (-1); + } + + return (0); +} + +/* + * Set NSS socket non-blocking + */ +int +nss_sock_set_non_blocking(PRFileDesc *sock) +{ + PRSocketOptionData sock_opt; + + memset(&sock_opt, 0, sizeof(sock_opt)); + sock_opt.option = PR_SockOpt_Nonblocking; + sock_opt.value.non_blocking = PR_TRUE; + if (PR_SetSocketOption(sock, &sock_opt) != PR_SUCCESS) { + return (-1); + } + + return (0); +} + +/* + * Create TCP socket with af family. If reuse_addr is set, socket option + * for reuse address is set. + */ +static PRFileDesc * +nss_sock_create_socket(PRIntn af, int reuse_addr) +{ + PRFileDesc *sock; + PRSocketOptionData socket_option; + + sock = PR_OpenTCPSocket(af); + if (sock == NULL) { + return (NULL); + } + + if (reuse_addr) { + socket_option.option = PR_SockOpt_Reuseaddr; + socket_option.value.reuse_addr = PR_TRUE; + if (PR_SetSocketOption(sock, &socket_option) != PR_SUCCESS) { + return (NULL); + } + } + + return (sock); +} + +/* + * Create listen socket and bind it to address. hostname can be NULL and then + * any address is used. Address family (af) can be ether PR_AF_INET6, + * PR_AF_INET or PR_AF_UNSPEC. + */ +PRFileDesc * +nss_sock_create_listen_socket(const char *hostname, uint16_t port, PRIntn af) +{ + PRNetAddr addr; + PRFileDesc *sock; + PRAddrInfo *addr_info; + void *addr_iter; + + sock = NULL; + + if (hostname == NULL) { + memset(&addr, 0, sizeof(addr)); + + if (PR_InitializeNetAddr(PR_IpAddrAny, port, &addr) != PR_SUCCESS) { + return (NULL); + } + if (af == PR_AF_UNSPEC) { + af = PR_AF_INET6; + } + addr.raw.family = af; + + sock = nss_sock_create_socket(af, 1); + if (sock == NULL) { + return (NULL); + } + + if (PR_Bind(sock, &addr) != PR_SUCCESS) { + PR_Close(sock); + + return (NULL); + } + } else { + addr_info = PR_GetAddrInfoByName(hostname, af, PR_AI_ADDRCONFIG); + if (addr_info == NULL) { + return (NULL); + } + + addr_iter = NULL; + + while ((addr_iter = PR_EnumerateAddrInfo(addr_iter, addr_info, port, + &addr)) != NULL) { + if (af == PR_AF_UNSPEC || addr.raw.family == af) { + sock = nss_sock_create_socket(addr.raw.family, 1); + if (sock == NULL) { + continue; + } + + if (PR_Bind(sock, &addr) != PR_SUCCESS) { + PR_Close(sock); + sock = NULL; + + continue; + } + + /* + * Socket is sucesfully bound + */ + break; + } + } + + PR_FreeAddrInfo(addr_info); + + if (sock == NULL) { + /* + * No address succeeded + */ + PR_SetError(PR_ADDRESS_NOT_AVAILABLE_ERROR, 0); + + return (NULL); + } + } + + return (sock); +} + +PRFileDesc * +nss_sock_create_client_socket(const char *hostname, uint16_t port, PRIntn af, + PRIntervalTime timeout) +{ + PRNetAddr addr; + PRFileDesc *sock; + PRAddrInfo *addr_info; + void *addr_iter; + PRStatus res; + int connect_failed; + PRIntn tmp_af; + + sock = NULL; + connect_failed = 0; + + tmp_af = af; + if (af == PR_AF_INET6) { + tmp_af = PR_AF_UNSPEC; + } + + addr_info = PR_GetAddrInfoByName(hostname, tmp_af, PR_AI_ADDRCONFIG); + if (addr_info == NULL) { + return (NULL); + } + + addr_iter = NULL; + + while ((addr_iter = PR_EnumerateAddrInfo(addr_iter, addr_info, port, &addr)) != NULL) { + if (af != PR_AF_UNSPEC && addr.raw.family != af) { + continue; + } + + sock = nss_sock_create_socket(addr.raw.family, 0); + if (sock == NULL) { + continue; + } + + if ((res = PR_Connect(sock, &addr, timeout)) != PR_SUCCESS) { + PR_Close(sock); + sock = NULL; + connect_failed = 1; + } + + /* + * Connection attempt finished + */ + break; + } + + PR_FreeAddrInfo(addr_info); + + if (sock == NULL && !connect_failed) { + PR_SetError(PR_ADDRESS_NOT_AVAILABLE_ERROR, 0); + } + + return (sock); +} + +int +nss_sock_non_blocking_client_init(const char *host_name, uint16_t port, PRIntn af, + struct nss_sock_non_blocking_client *client) +{ + PRIntn tmp_af; + + client->destroyed = 1; + + if ((client->host_name = strdup(host_name)) == NULL) { + PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); + + return (-1); + } + + client->port = port; + client->af = af; + + tmp_af = af; + if (af == PR_AF_INET6) { + tmp_af = PR_AF_UNSPEC; + } + + client->addr_info = PR_GetAddrInfoByName(client->host_name, tmp_af, PR_AI_ADDRCONFIG); + if (client->addr_info == NULL) { + free(client->host_name); + + return (-1); + } + client->addr_iter = NULL; + client->connect_attempts = 0; + client->socket = NULL; + client->destroyed = 0; + + return (0); +} + +int +nss_sock_non_blocking_client_try_next(struct nss_sock_non_blocking_client *client) +{ + PRNetAddr addr; + PRStatus res; + + if (client->destroyed) { + PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); + return (-1); + } + + if (client->socket != NULL) { + PR_Close(client->socket); + client->socket = NULL; + } + + while ((client->addr_iter = PR_EnumerateAddrInfo(client->addr_iter, client->addr_info, + client->port, &addr)) != NULL) { + if (client->af != PR_AF_UNSPEC && addr.raw.family != client->af) { + continue; + } + + client->socket = nss_sock_create_socket(addr.raw.family, 0); + if (client->socket == NULL) { + continue; + } + + if (nss_sock_set_non_blocking(client->socket) == -1) { + PR_Close(client->socket); + client->socket = NULL; + continue; + } + + res = PR_Connect(client->socket, &addr, PR_INTERVAL_NO_TIMEOUT); + if (res == PR_SUCCESS || PR_GetError() == PR_IN_PROGRESS_ERROR) { + return (0); + } + + PR_Close(client->socket); + client->socket = NULL; + + if (client->connect_attempts < INT_MAX) { + client->connect_attempts++; + } + } + + if (client->connect_attempts == 0) { + PR_SetError(PR_ADDRESS_NOT_AVAILABLE_ERROR, 0); + } + + return (-1); +} + +void +nss_sock_non_blocking_client_destroy(struct nss_sock_non_blocking_client *client) +{ + + if (client->destroyed) { + return ; + } + + if (client->addr_info != NULL) { + PR_FreeAddrInfo(client->addr_info); + client->addr_info = NULL; + } + + free(client->host_name); + client->host_name = NULL; + + client->destroyed = 1; +} + +/* + * -1 = Client connect failed + * 0 = Client connect still in progress + * 1 = Client successfuly connected + */ +int +nss_sock_non_blocking_client_succeeded(const PRPollDesc *pfd) +{ + int res; + + res = -1; + + if (PR_GetConnectStatus(pfd) == PR_SUCCESS) { + res = 1; + } else { + if (PR_GetError() == PR_IN_PROGRESS_ERROR) { + res = 0; + } else { + res = -1; + } + } + + return (res); +} + +/* + * Start client side SSL connection. This can block. + * + * ssl_url is expected server URL, bad_cert_hook is callback called when server certificate + * verification fails. + */ +PRFileDesc * +nss_sock_start_ssl_as_client(PRFileDesc *input_sock, const char *ssl_url, + SSLBadCertHandler bad_cert_hook, SSLGetClientAuthData client_auth_hook, + void *client_auth_hook_arg, int force_handshake, int *reset_would_block) +{ + PRFileDesc *ssl_sock; + + if (force_handshake) { + *reset_would_block = 0; + } + + ssl_sock = SSL_ImportFD(NULL, input_sock); + if (ssl_sock == NULL) { + return (NULL); + } + + if (SSL_SetURL(ssl_sock, ssl_url) != SECSuccess) { + return (NULL); + } + + if ((SSL_OptionSet(ssl_sock, SSL_SECURITY, PR_TRUE) != SECSuccess) || + (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) || + (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess)) { + return (NULL); + } + if (bad_cert_hook != NULL && SSL_BadCertHook(ssl_sock, bad_cert_hook, NULL) != SECSuccess) { + return (NULL); + } + + if (client_auth_hook != NULL && + (SSL_GetClientAuthDataHook(ssl_sock, client_auth_hook, + client_auth_hook_arg) != SECSuccess)) { + return (NULL); + } + + if (SSL_ResetHandshake(ssl_sock, PR_FALSE) != SECSuccess) { + return (NULL); + } + + if (force_handshake && SSL_ForceHandshake(ssl_sock) != SECSuccess) { + if (PR_GetError() == PR_WOULD_BLOCK_ERROR) { + /* + * Mask would block error. + */ + *reset_would_block = 1; + } else { + return (NULL); + } + } + + return (ssl_sock); +} + +PRFileDesc * +nss_sock_start_ssl_as_server(PRFileDesc *input_sock, CERTCertificate *server_cert, + SECKEYPrivateKey *server_key, int require_client_cert, int force_handshake, + int *reset_would_block) +{ + PRFileDesc *ssl_sock; + + if (force_handshake) { + *reset_would_block = 0; + } + + ssl_sock = SSL_ImportFD(NULL, input_sock); + if (ssl_sock == NULL) { + return (NULL); + } + + if (SSL_ConfigSecureServer(ssl_sock, server_cert, server_key, + NSS_FindCertKEAType(server_cert)) != SECSuccess) { + return (NULL); + } + + if ((SSL_OptionSet(ssl_sock, SSL_SECURITY, PR_TRUE) != SECSuccess) || + (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess) || + (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess) || + (SSL_OptionSet(ssl_sock, SSL_REQUEST_CERTIFICATE, require_client_cert) != SECSuccess) || + (SSL_OptionSet(ssl_sock, SSL_REQUIRE_CERTIFICATE, require_client_cert) != SECSuccess)) { + return (NULL); + } + + if (SSL_ResetHandshake(ssl_sock, PR_TRUE) != SECSuccess) { + return (NULL); + } + + if (force_handshake && SSL_ForceHandshake(ssl_sock) != SECSuccess) { + if (PR_GetError() == PR_WOULD_BLOCK_ERROR) { + /* + * Mask would block error. + */ + *reset_would_block = 1; + } else { + return (NULL); + } + } + + return (ssl_sock); +} diff --git a/qdevices/nss-sock.h b/qdevices/nss-sock.h new file mode 100644 index 0000000..cc16d96 --- /dev/null +++ b/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 +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct nss_sock_non_blocking_client { + char *host_name; + uint16_t port; + PRIntn af; + PRFileDesc *socket; + PRAddrInfo *addr_info; + void *addr_iter; + unsigned int connect_attempts; + int destroyed; +}; + +extern int nss_sock_init_nss(char *config_dir); + +extern PRFileDesc *nss_sock_create_listen_socket(const char *hostname, uint16_t port, + PRIntn af); + +extern int nss_sock_set_non_blocking(PRFileDesc *sock); + +extern PRFileDesc *nss_sock_create_client_socket(const char *hostname, uint16_t port, + PRIntn af, PRIntervalTime timeout); + +extern PRFileDesc *nss_sock_start_ssl_as_client(PRFileDesc *input_sock, const char *ssl_url, + SSLBadCertHandler bad_cert_hook, SSLGetClientAuthData client_auth_hook, + void *client_auth_hook_arg, int force_handshake, int *reset_would_block); + +extern PRFileDesc *nss_sock_start_ssl_as_server(PRFileDesc *input_sock, + CERTCertificate *server_cert, SECKEYPrivateKey *server_key, int require_client_cert, + int force_handshake, int *reset_would_block); + +extern int nss_sock_non_blocking_client_init(const char *host_name, + uint16_t port, PRIntn af, struct nss_sock_non_blocking_client *client); + +extern int nss_sock_non_blocking_client_try_next( + struct nss_sock_non_blocking_client *client); + +extern void nss_sock_non_blocking_client_destroy( + struct nss_sock_non_blocking_client *client); + +extern int nss_sock_non_blocking_client_succeeded(const PRPollDesc *pfd); + +#ifdef __cplusplus +} +#endif + +#endif /* _NSS_SOCK_H_ */ diff --git a/qdevices/pr-poll-array.c b/qdevices/pr-poll-array.c new file mode 100644 index 0000000..a2ba45f --- /dev/null +++ b/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 +#include + +#include +#include +#include + +#include "pr-poll-array.h" + +void +pr_poll_array_init(struct pr_poll_array *poll_array, size_t user_data_size) +{ + + memset(poll_array, 0, sizeof(*poll_array)); + poll_array->user_data_size = user_data_size; +} + +void +pr_poll_array_destroy(struct pr_poll_array *poll_array) +{ + + free(poll_array->array); + free(poll_array->user_data_array); + pr_poll_array_init(poll_array, poll_array->user_data_size); +} + +void +pr_poll_array_clean(struct pr_poll_array *poll_array) +{ + + poll_array->items = 0; +} + +static int +pr_poll_array_realloc(struct pr_poll_array *poll_array, + ssize_t new_array_size) +{ + PRPollDesc *new_array; + char *new_user_data_array; + + new_array = realloc(poll_array->array, + sizeof(PRPollDesc) * new_array_size); + + if (new_array == NULL) { + return (-1); + } + + poll_array->allocated = new_array_size; + poll_array->array = new_array; + + if (poll_array->user_data_size > 0) { + new_user_data_array = realloc(poll_array->user_data_array, + poll_array->user_data_size * new_array_size); + + if (new_user_data_array == NULL) { + return (-1); + } + + poll_array->user_data_array = new_user_data_array; + } + + return (0); +} + +ssize_t +pr_poll_array_size(struct pr_poll_array *poll_array) +{ + + return (poll_array->items); +} + +ssize_t +pr_poll_array_add(struct pr_poll_array *poll_array, PRPollDesc **pfds, void **user_data) +{ + + if (pr_poll_array_size(poll_array) >= poll_array->allocated) { + if (pr_poll_array_realloc(poll_array, (poll_array->allocated * 2) + 1)) { + return (-1); + } + } + + *pfds = &poll_array->array[pr_poll_array_size(poll_array)]; + memset(*pfds, 0, sizeof(**pfds)); + + *user_data = poll_array->user_data_array + (poll_array->items * poll_array->user_data_size); + memset(*user_data, 0, poll_array->user_data_size); + + poll_array->items++; + + return (poll_array->items - 1); +} + +void +pr_poll_array_gc(struct pr_poll_array *poll_array) +{ + + if (poll_array->allocated > (pr_poll_array_size(poll_array) * 3) + 1) { + pr_poll_array_realloc(poll_array, (pr_poll_array_size(poll_array) * 2) + 1); + } +} + +PRPollDesc * +pr_poll_array_get(const struct pr_poll_array *poll_array, ssize_t pos) +{ + + if (pos >= poll_array->items) { + return (NULL); + } + + return (&poll_array->array[pos]); +} + +void * +pr_poll_array_get_user_data(const struct pr_poll_array *poll_array, ssize_t pos) +{ + + if (pos >= poll_array->items) { + return (NULL); + } + + return (poll_array->user_data_array + (pos * poll_array->user_data_size)); +} diff --git a/qdevices/pr-poll-array.h b/qdevices/pr-poll-array.h new file mode 100644 index 0000000..221754c --- /dev/null +++ b/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 +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct pr_poll_array { + PRPollDesc *array; + char *user_data_array; + size_t user_data_size; + ssize_t allocated; + ssize_t items; +}; + +extern void pr_poll_array_init(struct pr_poll_array *poll_array, size_t user_data_size); + +extern void pr_poll_array_destroy(struct pr_poll_array *poll_array); + +extern void pr_poll_array_clean(struct pr_poll_array *poll_array); + +extern ssize_t pr_poll_array_size(struct pr_poll_array *poll_array); + +extern ssize_t pr_poll_array_add(struct pr_poll_array *poll_array, PRPollDesc **pfds, + void **user_data); + +extern PRPollDesc *pr_poll_array_get(const struct pr_poll_array *poll_array, + ssize_t pos); + +extern void *pr_poll_array_get_user_data(const struct pr_poll_array *poll_array, + ssize_t pos); + +extern void pr_poll_array_gc(struct pr_poll_array *poll_array); + +#ifdef __cplusplus +} +#endif + +#endif /* _PR_POLL_ARRAY_H_ */ diff --git a/qdevices/process-list.c b/qdevices/process-list.c new file mode 100644 index 0000000..ad2d598 --- /dev/null +++ b/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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "dynar.h" +#include "dynar-str.h" +#include "dynar-simple-lex.h" +#include "process-list.h" + +static void process_list_free_argv(size_t no_params, char **argv); + +static void process_list_entry_free(struct process_list_entry *entry); + +static char **process_list_parse_command(const char *command, size_t *no_params); + +static int process_list_entry_exec(const struct process_list *plist, + struct process_list_entry *entry); + +void +process_list_init(struct process_list *plist, size_t max_list_entries, int use_execvp, + process_list_notify_fn_t notify_fn, void *notify_fn_user_data) +{ + + memset(plist, 0, sizeof(*plist)); + + plist->max_list_entries = max_list_entries; + plist->allocated_list_entries = 0; + plist->use_execvp = use_execvp; + plist->notify_fn = notify_fn; + plist->notify_fn_user_data = notify_fn_user_data; + + TAILQ_INIT(&plist->active_list); + TAILQ_INIT(&plist->to_kill_list); +} + +static void +process_list_free_argv(size_t no_params, char **argv) +{ + size_t zi; + + for (zi = 0; zi < no_params; zi++) { + free(argv[zi]); + } + free(argv); +} + +static void +process_list_entry_free(struct process_list_entry *entry) +{ + + process_list_free_argv(entry->exec_argc, entry->exec_argv); + free(entry->name); + free(entry); +} + +static char ** +process_list_parse_command(const char *command, size_t *no_params) +{ + struct dynar command_dstr; + struct dynar_simple_lex lex; + struct dynar *token; + int finished; + char **res_argv; + size_t zi; + + res_argv = NULL; + + dynar_init(&command_dstr, strlen(command) + 1); + if (dynar_str_cpy(&command_dstr, command) != 0) { + return (NULL); + } + + dynar_simple_lex_init(&lex, &command_dstr, DYNAR_SIMPLE_LEX_TYPE_QUOTE); + *no_params = 0; + finished = 0; + + while (!finished) { + token = dynar_simple_lex_token_next(&lex); + if (token == NULL) { + goto exit_res; + } + + if (strcmp(dynar_data(token), "") == 0) { + finished = 1; + } else { + (*no_params)++; + } + } + + if (*no_params < 1) { + goto exit_res; + } + + dynar_simple_lex_destroy(&lex); + + res_argv = malloc(sizeof(char *) * (*no_params + 1)); + if (res_argv == NULL) { + goto exit_res; + } + memset(res_argv, 0, sizeof(char *) * (*no_params + 1)); + + dynar_simple_lex_init(&lex, &command_dstr, DYNAR_SIMPLE_LEX_TYPE_QUOTE); + + finished = 0; + zi = 0; + while (!finished) { + token = dynar_simple_lex_token_next(&lex); + if (token == NULL) { + process_list_free_argv(*no_params, res_argv); + res_argv = NULL; + goto exit_res; + } + + if (strcmp(dynar_data(token), "") == 0) { + finished = 1; + } else { + res_argv[zi] = strdup(dynar_data(token)); + if (res_argv[zi] == NULL) { + process_list_free_argv(*no_params, res_argv); + res_argv = NULL; + } + zi++; + } + } + + if (zi != *no_params) { + /* + * If this happens it means something is seriously broken (memory corrupted) + */ + process_list_free_argv(*no_params, res_argv); + res_argv = NULL; + goto exit_res; + } + +exit_res: + dynar_simple_lex_destroy(&lex); + dynar_destroy(&command_dstr); + return (res_argv); +} + +struct process_list_entry * +process_list_add(struct process_list *plist, const char *name, const char *command) +{ + struct process_list_entry *entry; + + if (plist->allocated_list_entries + 1 > plist->max_list_entries) { + return (NULL); + } + + /* + * Alloc new entry + */ + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + return (NULL); + } + + memset(entry, 0, sizeof(*entry)); + entry->name = strdup(name); + if (entry->name == NULL) { + process_list_entry_free(entry); + + return (NULL); + } + + entry->state = PROCESS_LIST_ENTRY_STATE_INITIALIZED; + entry->exec_argv = process_list_parse_command(command, &entry->exec_argc); + if (entry->exec_argv == NULL) { + process_list_entry_free(entry); + + return (NULL); + } + + plist->allocated_list_entries++; + TAILQ_INSERT_TAIL(&plist->active_list, entry, entries); + + return (entry); +} + +void +process_list_free(struct process_list *plist) +{ + struct process_list_entry *entry; + struct process_list_entry *entry_next; + + entry = TAILQ_FIRST(&plist->active_list); + + while (entry != NULL) { + entry_next = TAILQ_NEXT(entry, entries); + + process_list_entry_free(entry); + + entry = entry_next; + } + + entry = TAILQ_FIRST(&plist->to_kill_list); + + while (entry != NULL) { + entry_next = TAILQ_NEXT(entry, entries); + + process_list_entry_free(entry); + + entry = entry_next; + } + + plist->allocated_list_entries = 0; + + TAILQ_INIT(&plist->active_list); + TAILQ_INIT(&plist->to_kill_list); +} + +static void +process_list_entry_exec_helper_set_stdfd(void) +{ + int devnull; + + devnull = open("/dev/null", O_RDWR); + if (devnull == -1) { + err(1, "Can't open /dev/null"); + } + + if (dup2(devnull, 0) < 0 || dup2(devnull, 1) < 0 || dup2(devnull, 2) < 0) { + close(devnull); + err(1, "Can't dup2 stdin/out/err to /dev/null"); + } + + close(devnull); +} + +static int +process_list_entry_exec(const struct process_list *plist, struct process_list_entry *entry) +{ + pid_t pid; + + if (entry->state != PROCESS_LIST_ENTRY_STATE_INITIALIZED) { + return (-1); + } + + pid = fork(); + if (pid == -1) { + return (-1); + } else if (pid == 0) { + process_list_entry_exec_helper_set_stdfd(); + + if (!plist->use_execvp) { + execv(entry->exec_argv[0], entry->exec_argv); + } else { + execvp(entry->exec_argv[0], entry->exec_argv); + } + + /* + * Exec returned -> exec failed + */ + err(1, "Can't execute command %s (%s)", entry->name, entry->exec_argv[0]); + } else { + entry->pid = pid; + entry->state = PROCESS_LIST_ENTRY_STATE_RUNNING; + + if (plist->notify_fn != NULL) { + plist->notify_fn(PROCESS_LIST_NOTIFY_REASON_EXECUTED, entry, + plist->notify_fn_user_data); + } + } + + return (0); +} + +int +process_list_exec_initialized(struct process_list *plist) +{ + struct process_list_entry *entry; + + TAILQ_FOREACH(entry, &plist->active_list, entries) { + if (entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED) { + if (process_list_entry_exec(plist, entry) != 0) { + return (-1); + } + } + } + + return (0); +} + +static int +process_list_entry_waitpid(const struct process_list *plist, struct process_list_entry *entry) +{ + pid_t wpid_res; + int status; + + if (entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED || + entry->state == PROCESS_LIST_ENTRY_STATE_FINISHED) { + return (0); + } + + wpid_res = waitpid(entry->pid, &status, WNOHANG); + if (wpid_res == -1) { + return (-1); + } + + if (wpid_res == 0) { + /* + * No change + */ + return (0); + } + + entry->exit_status = status; + + if (entry->state == PROCESS_LIST_ENTRY_STATE_RUNNING) { + if (plist->notify_fn != NULL) { + plist->notify_fn(PROCESS_LIST_NOTIFY_REASON_FINISHED, entry, + plist->notify_fn_user_data); + } + } + + entry->state = PROCESS_LIST_ENTRY_STATE_FINISHED; + + return (0); +} + +int +process_list_waitpid(struct process_list *plist) +{ + struct process_list_entry *entry; + struct process_list_entry *entry_next; + + TAILQ_FOREACH(entry, &plist->active_list, entries) { + if (process_list_entry_waitpid(plist, entry) != 0) { + return (-1); + } + } + + entry = TAILQ_FIRST(&plist->to_kill_list); + + while (entry != NULL) { + entry_next = TAILQ_NEXT(entry, entries); + + if (process_list_entry_waitpid(plist, entry) != 0) { + return (-1); + } + + if (entry->state == PROCESS_LIST_ENTRY_STATE_FINISHED) { + /* + * Process finished -> remove it from list + */ + TAILQ_REMOVE(&plist->to_kill_list, entry, entries); + process_list_entry_free(entry); + plist->allocated_list_entries--; + } + + entry = entry_next; + } + + return (0); +} + +size_t +process_list_get_no_running(struct process_list *plist) +{ + struct process_list_entry *entry; + size_t res; + + res = 0; + + TAILQ_FOREACH(entry, &plist->active_list, entries) { + if (entry->state == PROCESS_LIST_ENTRY_STATE_RUNNING) { + res++; + } + } + + return (res); +} + +/* + * -1 = Not all processes finished + * 0 = All processes finished sucesfully + * 1 - All processes finished but some of them not sucesfully + */ +int +process_list_get_summary_result(struct process_list *plist) +{ + struct process_list_entry *entry; + int res; + + res = 0; + + TAILQ_FOREACH(entry, &plist->active_list, entries) { + if (entry->state != PROCESS_LIST_ENTRY_STATE_FINISHED) { + return (-1); + } + + if (!WIFEXITED(entry->exit_status) || WEXITSTATUS(entry->exit_status) != 0) { + res = 1; + } + } + + return (res); +} + +/* + * 0 = All processes finished sucesfully + * 1 = Some process finished and failed + * -1 = Not all processed finished and none of finished failed + */ +int +process_list_get_summary_result_short(struct process_list *plist) +{ + struct process_list_entry *entry; + int res; + + res = 0; + + TAILQ_FOREACH(entry, &plist->active_list, entries) { + if (entry->state == PROCESS_LIST_ENTRY_STATE_FINISHED) { + if (!WIFEXITED(entry->exit_status) || WEXITSTATUS(entry->exit_status) != 0) { + return (1); + } + } else { + res = -1; + } + } + + return (res); +} + +static void +process_list_move_entry_to_kill_list(struct process_list *plist, struct process_list_entry *entry) +{ + + TAILQ_REMOVE(&plist->active_list, entry, entries); + TAILQ_INSERT_TAIL(&plist->to_kill_list, entry, entries); +} + +void +process_list_move_active_entries_to_kill_list(struct process_list *plist) +{ + struct process_list_entry *entry; + struct process_list_entry *entry_next; + + entry = TAILQ_FIRST(&plist->active_list); + + while (entry != NULL) { + entry_next = TAILQ_NEXT(entry, entries); + + if (entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED || + entry->state == PROCESS_LIST_ENTRY_STATE_FINISHED) { + TAILQ_REMOVE(&plist->active_list, entry, entries); + process_list_entry_free(entry); + plist->allocated_list_entries--; + } else { + process_list_move_entry_to_kill_list(plist, entry); + } + + entry = entry_next; + } +} + +static int +process_list_process_kill_list_entry(struct process_list *plist, struct process_list_entry *entry) +{ + int sig_to_send; + enum process_list_entry_state new_state; + int res; + + sig_to_send = 0; + new_state = PROCESS_LIST_ENTRY_STATE_INITIALIZED; + + switch (entry->state) { + case PROCESS_LIST_ENTRY_STATE_INITIALIZED: + /* + * This shouldn't happen. If it does, process_list_move_active_entries_to_kill_list + * doesn't work as expected or there is some kind of memory corruption. + */ + assert(entry->state != PROCESS_LIST_ENTRY_STATE_INITIALIZED); + break; + case PROCESS_LIST_ENTRY_STATE_FINISHED: + /* + * This shouldn't happen. If it does, process_list_waitpid + * doesn't work as expected or there is some kind of memory corruption. + */ + assert(entry->state != PROCESS_LIST_ENTRY_STATE_FINISHED); + break; + case PROCESS_LIST_ENTRY_STATE_RUNNING: + sig_to_send = SIGTERM; + new_state = PROCESS_LIST_ENTRY_STATE_SIGTERM_SENT; + break; + case PROCESS_LIST_ENTRY_STATE_SIGTERM_SENT: + sig_to_send = SIGKILL; + new_state = PROCESS_LIST_ENTRY_STATE_SIGKILL_SENT; + break; + case PROCESS_LIST_ENTRY_STATE_SIGKILL_SENT: + sig_to_send = SIGKILL; + new_state = PROCESS_LIST_ENTRY_STATE_SIGKILL_SENT; + break; + } + + res = 0; + + if (kill(entry->pid, sig_to_send) == -1) { + if (errno == EPERM || errno == EINVAL) { + res = -1; + } + } + + entry->state = new_state; + + return (res); +} + +int +process_list_process_kill_list(struct process_list *plist) +{ + struct process_list_entry *entry; + + if (process_list_waitpid(plist) != 0) { + return (-1); + } + + TAILQ_FOREACH(entry, &plist->to_kill_list, entries) { + if (process_list_process_kill_list_entry(plist, entry) != 0) { + return (-1); + } + } + + return (0); +} + +size_t +process_list_get_kill_list_items(struct process_list *plist) +{ + struct process_list_entry *entry; + size_t res; + + res = 0; + + TAILQ_FOREACH(entry, &plist->to_kill_list, entries) { + res++; + } + + return (res); +} + +int +process_list_killall(struct process_list *plist, uint32_t timeout) +{ + uint32_t action_timeout; + int i; + + process_list_move_active_entries_to_kill_list(plist); + + action_timeout = timeout / 10; + if (action_timeout < 1) { + action_timeout = 1; + } + + for (i = 0; i < 10; i++) { + /* + * Make sure all process got signal (quick phase) + */ + if (process_list_process_kill_list(plist) != 0) { + return (-1); + } + } + + for (i = 0; i < 10 && process_list_get_kill_list_items(plist) > 0; i++) { + if (process_list_process_kill_list(plist) != 0) { + return (-1); + } + + poll(NULL, 0, action_timeout); + } + + if (process_list_get_kill_list_items(plist) > 0) { + return (-1); + } + + return (0); +} diff --git a/qdevices/process-list.h b/qdevices/process-list.h new file mode 100644 index 0000000..ea2873f --- /dev/null +++ b/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 +#include + +#include "dynar.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum process_list_entry_state { + PROCESS_LIST_ENTRY_STATE_INITIALIZED, + PROCESS_LIST_ENTRY_STATE_RUNNING, + PROCESS_LIST_ENTRY_STATE_FINISHED, + PROCESS_LIST_ENTRY_STATE_SIGTERM_SENT, + PROCESS_LIST_ENTRY_STATE_SIGKILL_SENT, +}; + +enum process_list_notify_reason { + PROCESS_LIST_NOTIFY_REASON_EXECUTED, + PROCESS_LIST_NOTIFY_REASON_FINISHED, +}; + +struct process_list_entry { + char *name; + enum process_list_entry_state state; + char **exec_argv; + size_t exec_argc; + pid_t pid; + int exit_status; + + TAILQ_ENTRY(process_list_entry) entries; +}; + +typedef void (*process_list_notify_fn_t) (enum process_list_notify_reason reason, + const struct process_list_entry *entry, void *user_data); + +struct process_list { + int use_execvp; + size_t max_list_entries; + size_t allocated_list_entries; + process_list_notify_fn_t notify_fn; + void *notify_fn_user_data; + + TAILQ_HEAD(, process_list_entry) active_list; + TAILQ_HEAD(, process_list_entry) to_kill_list; +}; + + +extern void process_list_init(struct process_list *plist, + size_t max_list_entries, int use_execvp, process_list_notify_fn_t notify_fn, + void *notify_fn_user_data); + +extern struct process_list_entry *process_list_add(struct process_list *plist, + const char *name, const char *command); + +extern void process_list_free(struct process_list *plist); + +extern int process_list_exec_initialized(struct process_list *plist); + +extern int process_list_waitpid(struct process_list *plist); + +extern size_t process_list_get_no_running(struct process_list *plist); + +extern int process_list_get_summary_result(struct process_list *plist); + +extern int process_list_get_summary_result_short( + struct process_list *plist); + +extern void process_list_move_active_entries_to_kill_list( + struct process_list *plist); + +extern int process_list_process_kill_list(struct process_list *plist); + +extern size_t process_list_get_kill_list_items(struct process_list *plist); + +extern int process_list_killall(struct process_list *plist, + uint32_t timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* _PROCESS_LIST_H_ */ diff --git a/qdevices/qdevice-advanced-settings.c b/qdevices/qdevice-advanced-settings.c new file mode 100644 index 0000000..e3d7a9a --- /dev/null +++ b/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 +#include +#include + +#include "dynar.h" +#include "dynar-getopt-lex.h" +#include "dynar-str.h" +#include "qdevice-config.h" +#include "qnet-config.h" +#include "qdevice-advanced-settings.h" +#include "utils.h" + +int +qdevice_advanced_settings_init(struct qdevice_advanced_settings *settings) +{ + + memset(settings, 0, sizeof(*settings)); + if ((settings->lock_file = strdup(QDEVICE_DEFAULT_LOCK_FILE)) == NULL) { + return (-1); + } + if ((settings->local_socket_file = strdup(QDEVICE_DEFAULT_LOCAL_SOCKET_FILE)) == NULL) { + return (-1); + } + settings->local_socket_backlog = QDEVICE_DEFAULT_LOCAL_SOCKET_BACKLOG; + settings->max_cs_try_again = QDEVICE_DEFAULT_MAX_CS_TRY_AGAIN; + if ((settings->votequorum_device_name = strdup(QDEVICE_DEFAULT_VOTEQUORUM_DEVICE_NAME)) == NULL) { + return (-1); + } + settings->ipc_max_clients = QDEVICE_DEFAULT_IPC_MAX_CLIENTS; + settings->ipc_max_receive_size = QDEVICE_DEFAULT_IPC_MAX_RECEIVE_SIZE; + settings->ipc_max_send_size = QDEVICE_DEFAULT_IPC_MAX_SEND_SIZE; + + settings->heuristics_ipc_max_send_buffers = QDEVICE_DEFAULT_HEURISTICS_IPC_MAX_SEND_BUFFERS; + settings->heuristics_ipc_max_send_receive_size = QDEVICE_DEFAULT_HEURISTICS_IPC_MAX_SEND_RECEIVE_SIZE; + + settings->heuristics_min_timeout = QDEVICE_DEFAULT_HEURISTICS_MIN_TIMEOUT; + settings->heuristics_max_timeout = QDEVICE_DEFAULT_HEURISTICS_MAX_TIMEOUT; + settings->heuristics_min_interval = QDEVICE_DEFAULT_HEURISTICS_MIN_INTERVAL; + settings->heuristics_max_interval = QDEVICE_DEFAULT_HEURISTICS_MAX_INTERVAL; + + settings->heuristics_max_execs = QDEVICE_DEFAULT_HEURISTICS_MAX_EXECS; + + settings->heuristics_use_execvp = QDEVICE_DEFAULT_HEURISTICS_USE_EXECVP; + settings->heuristics_max_processes = QDEVICE_DEFAULT_HEURISTICS_MAX_PROCESSES; + settings->heuristics_kill_list_interval = QDEVICE_DEFAULT_HEURISTICS_KILL_LIST_INTERVAL; + + if ((settings->net_nss_db_dir = strdup(QDEVICE_NET_DEFAULT_NSS_DB_DIR)) == NULL) { + return (-1); + } + settings->net_initial_msg_receive_size = QDEVICE_NET_DEFAULT_INITIAL_MSG_RECEIVE_SIZE; + settings->net_initial_msg_send_size = QDEVICE_NET_DEFAULT_INITIAL_MSG_SEND_SIZE; + settings->net_min_msg_send_size = QDEVICE_NET_DEFAULT_MIN_MSG_SEND_SIZE; + settings->net_max_msg_receive_size = QDEVICE_NET_DEFAULT_MAX_MSG_RECEIVE_SIZE; + settings->net_max_send_buffers = QDEVICE_NET_DEFAULT_MAX_SEND_BUFFERS; + if ((settings->net_nss_qnetd_cn = strdup(QDEVICE_NET_DEFAULT_NSS_QNETD_CN)) == NULL) { + return (-1); + } + if ((settings->net_nss_client_cert_nickname = + strdup(QDEVICE_NET_DEFAULT_NSS_CLIENT_CERT_NICKNAME)) == NULL) { + return (-1); + } + settings->net_heartbeat_interval_min = QDEVICE_NET_DEFAULT_HEARTBEAT_INTERVAL_MIN; + settings->net_heartbeat_interval_max = QDEVICE_NET_DEFAULT_HEARTBEAT_INTERVAL_MAX; + settings->net_min_connect_timeout = QDEVICE_NET_DEFAULT_MIN_CONNECT_TIMEOUT; + settings->net_max_connect_timeout = QDEVICE_NET_DEFAULT_MAX_CONNECT_TIMEOUT; + settings->net_test_algorithm_enabled = QDEVICE_NET_DEFAULT_TEST_ALGORITHM_ENABLED; + + settings->master_wins = QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_MODEL; + + return (0); +} + +void +qdevice_advanced_settings_destroy(struct qdevice_advanced_settings *settings) +{ + + free(settings->local_socket_file); + free(settings->lock_file); + free(settings->votequorum_device_name); + free(settings->net_nss_db_dir); + free(settings->net_nss_qnetd_cn); + free(settings->net_nss_client_cert_nickname); +} + +/* + * 0 - No error + * -1 - Unknown option + * -2 - Incorrect value + */ +int +qdevice_advanced_settings_set(struct qdevice_advanced_settings *settings, + const char *option, const char *value) +{ + long long int tmpll; + char *ep; + + if (strcasecmp(option, "lock_file") == 0) { + free(settings->lock_file); + + if ((settings->lock_file = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "local_socket_file") == 0) { + free(settings->local_socket_file); + + if ((settings->local_socket_file = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "local_socket_backlog") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_LOCAL_SOCKET_BACKLOG || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->local_socket_backlog = (int)tmpll; + } else if (strcasecmp(option, "max_cs_try_again") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_MAX_CS_TRY_AGAIN || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->max_cs_try_again = (int)tmpll; + } else if (strcasecmp(option, "votequorum_device_name") == 0) { + free(settings->votequorum_device_name); + + if ((settings->votequorum_device_name = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "ipc_max_clients") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_IPC_MAX_CLIENTS || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->ipc_max_clients = (size_t)tmpll; + } else if (strcasecmp(option, "ipc_max_receive_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_IPC_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->ipc_max_receive_size = (size_t)tmpll; + } else if (strcasecmp(option, "ipc_max_send_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_IPC_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->ipc_max_send_size = (size_t)tmpll; + } else if (strcasecmp(option, "heuristics_ipc_max_send_buffers") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_HEURISTICS_IPC_MAX_SEND_BUFFERS || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heuristics_ipc_max_send_buffers = (size_t)tmpll; + } else if (strcasecmp(option, "heuristics_ipc_max_send_receive_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_HEURISTICS_IPC_MAX_SEND_RECEIVE_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heuristics_ipc_max_send_receive_size = (size_t)tmpll; + } else if (strcasecmp(option, "heuristics_min_timeout") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_HEURISTICS_TIMEOUT || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heuristics_min_timeout = (uint32_t)tmpll; + } else if (strcasecmp(option, "heuristics_max_timeout") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_HEURISTICS_TIMEOUT || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heuristics_max_timeout = (uint32_t)tmpll; + } else if (strcasecmp(option, "heuristics_min_interval") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_HEURISTICS_INTERVAL || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heuristics_min_interval = (uint32_t)tmpll; + } else if (strcasecmp(option, "heuristics_max_interval") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_HEURISTICS_INTERVAL || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heuristics_max_interval = (uint32_t)tmpll; + } else if (strcasecmp(option, "heuristics_max_execs") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_HEURISTICS_MAX_EXECS || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heuristics_max_execs = (size_t)tmpll; + } else if (strcasecmp(option, "heuristics_use_execvp") == 0) { + if ((tmpll = utils_parse_bool_str(value)) == -1) { + return (-2); + } + + settings->heuristics_use_execvp = (uint8_t)tmpll; + } else if (strcasecmp(option, "heuristics_max_processes") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_HEURISTICS_MAX_PROCESSES || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heuristics_max_processes = (size_t)tmpll; + } else if (strcasecmp(option, "heuristics_kill_list_interval") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_MIN_HEURISTICS_KILL_LIST_INTERVAL || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heuristics_kill_list_interval = (uint32_t)tmpll; + } else if (strcasecmp(option, "net_nss_db_dir") == 0) { + free(settings->net_nss_db_dir); + + if ((settings->net_nss_db_dir = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "net_initial_msg_receive_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->net_initial_msg_receive_size = (size_t)tmpll; + } else if (strcasecmp(option, "net_initial_msg_send_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->net_initial_msg_send_size = (size_t)tmpll; + } else if (strcasecmp(option, "net_min_msg_send_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->net_min_msg_send_size = (size_t)tmpll; + } else if (strcasecmp(option, "net_max_msg_receive_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->net_max_msg_receive_size = (size_t)tmpll; + } else if (strcasecmp(option, "net_max_send_buffers") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_NET_MIN_MAX_SEND_BUFFERS || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->net_max_send_buffers = (size_t)tmpll; + } else if (strcasecmp(option, "net_nss_qnetd_cn") == 0) { + free(settings->net_nss_qnetd_cn); + + if ((settings->net_nss_qnetd_cn = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "net_nss_client_cert_nickname") == 0) { + free(settings->net_nss_client_cert_nickname); + + if ((settings->net_nss_client_cert_nickname = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "net_heartbeat_interval_min") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_NET_MIN_HEARTBEAT_INTERVAL || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->net_heartbeat_interval_min = (uint32_t)tmpll; + } else if (strcasecmp(option, "net_heartbeat_interval_max") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_NET_MIN_HEARTBEAT_INTERVAL || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->net_heartbeat_interval_max = (uint32_t)tmpll; + } else if (strcasecmp(option, "net_min_connect_timeout") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_NET_MIN_CONNECT_TIMEOUT || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->net_min_connect_timeout = (uint32_t)tmpll; + } else if (strcasecmp(option, "net_max_connect_timeout") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QDEVICE_NET_MIN_CONNECT_TIMEOUT || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->net_max_connect_timeout = (uint32_t)tmpll; + } else if (strcasecmp(option, "net_test_algorithm_enabled") == 0) { + if ((tmpll = utils_parse_bool_str(value)) == -1) { + return (-2); + } + + settings->net_test_algorithm_enabled = (uint8_t)tmpll; + } else if (strcasecmp(option, "master_wins") == 0) { + tmpll = utils_parse_bool_str(value); + + if (tmpll == 0) { + settings->master_wins = QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_OFF; + } else if (tmpll == 1) { + settings->master_wins = QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_ON; + } else if (strcasecmp(value, "model") == 0) { + settings->master_wins = QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_MODEL; + } else { + return (-2); + } + } else { + return (-1); + } + + return (0); +} diff --git a/qdevices/qdevice-advanced-settings.h b/qdevices/qdevice-advanced-settings.h new file mode 100644 index 0000000..b8bf604 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-cmap.c b/qdevices/qdevice-cmap.c new file mode 100644 index 0000000..e9a99c3 --- /dev/null +++ b/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 +#include + +#include +#include +#include +#include +#include + +#include "qdevice-config.h" +#include "qdevice-cmap.h" +#include "qdevice-log.h" +#include "qdevice-log-debug.h" +#include "qdevice-model.h" +#include "utils.h" + +static uint32_t +qdevice_cmap_autogenerate_node_id(const char *addr, int clear_node_high_byte) +{ + struct addrinfo *ainfo; + struct addrinfo ahints; + int ret, i; + + memset(&ahints, 0, sizeof(ahints)); + ahints.ai_socktype = SOCK_DGRAM; + ahints.ai_protocol = IPPROTO_UDP; + /* + * Hardcoded AF_INET because autogenerated nodeid is valid only for ipv4 + */ + ahints.ai_family = AF_INET; + + ret = getaddrinfo(addr, NULL, &ahints, &ainfo); + if (ret != 0) + return (0); + + if (ainfo->ai_family != AF_INET) { + + freeaddrinfo(ainfo); + + return (0); + } + + memcpy(&i, &((struct sockaddr_in *)ainfo->ai_addr)->sin_addr, sizeof(struct in_addr)); + freeaddrinfo(ainfo); + + ret = htonl(i); + + if (clear_node_high_byte) { + ret &= 0x7FFFFFFF; + } + + return (ret); +} + +int +qdevice_cmap_get_nodelist(cmap_handle_t cmap_handle, struct node_list *list) +{ + cs_error_t cs_err; + cmap_iter_handle_t iter_handle; + char key_name[CMAP_KEYNAME_MAXLEN + 1]; + char tmp_key[CMAP_KEYNAME_MAXLEN + 1]; + int res; + int ret_value; + unsigned int node_pos; + uint32_t node_id; + uint32_t data_center_id; + char *tmp_str; + char *addr0_str; + int clear_node_high_byte; + + ret_value = 0; + + node_list_init(list); + + cs_err = cmap_iter_init(cmap_handle, "nodelist.node.", &iter_handle); + if (cs_err != CS_OK) { + return (-1); + } + + while ((cs_err = cmap_iter_next(cmap_handle, iter_handle, key_name, NULL, NULL)) == CS_OK) { + res = sscanf(key_name, "nodelist.node.%u.%s", &node_pos, tmp_key); + if (res != 2) { + continue; + } + + if (strcmp(tmp_key, "ring0_addr") != 0) { + continue; + } + + snprintf(tmp_key, CMAP_KEYNAME_MAXLEN, "nodelist.node.%u.nodeid", node_pos); + cs_err = cmap_get_uint32(cmap_handle, tmp_key, &node_id); + + if (cs_err == CS_ERR_NOT_EXIST) { + /* + * Nodeid doesn't exists -> autogenerate node id + */ + clear_node_high_byte = 0; + + if (cmap_get_string(cmap_handle, "totem.clear_node_high_bit", + &tmp_str) == CS_OK) { + if (strcmp (tmp_str, "yes") == 0) { + clear_node_high_byte = 1; + } + + free(tmp_str); + } + + if (cmap_get_string(cmap_handle, key_name, &addr0_str) != CS_OK) { + return (-1); + } + + node_id = qdevice_cmap_autogenerate_node_id(addr0_str, + clear_node_high_byte); + + free(addr0_str); + } else if (cs_err != CS_OK) { + ret_value = -1; + + goto iter_finalize; + } + + snprintf(tmp_key, CMAP_KEYNAME_MAXLEN, "nodelist.node.%u.datacenterid", node_pos); + if (cmap_get_uint32(cmap_handle, tmp_key, &data_center_id) != CS_OK) { + data_center_id = 0; + } + + if (node_list_add(list, node_id, data_center_id, TLV_NODE_STATE_NOT_SET) == NULL) { + ret_value = -1; + + goto iter_finalize; + } + } + +iter_finalize: + cmap_iter_finalize(cmap_handle, iter_handle); + + if (ret_value != 0) { + node_list_free(list); + } + + return (ret_value); +} + +int +qdevice_cmap_get_config_version(cmap_handle_t cmap_handle, uint64_t *config_version) +{ + int res; + + if (cmap_get_uint64(cmap_handle, "totem.config_version", config_version) == CS_OK) { + res = 0; + } else { + *config_version = 0; + res = -1; + } + + return (res); +} + +int +qdevice_cmap_store_config_node_list(struct qdevice_instance *instance) +{ + int res; + + node_list_free(&instance->config_node_list); + + if (qdevice_cmap_get_nodelist(instance->cmap_handle, &instance->config_node_list) != 0) { + qdevice_log(LOG_ERR, "Can't get configuration node list."); + + return (-1); + } + + res = qdevice_cmap_get_config_version(instance->cmap_handle, &instance->config_node_list_version); + instance->config_node_list_version_set = (res == 0); + + return (0); +} + +void +qdevice_cmap_init(struct qdevice_instance *instance) +{ + cs_error_t res; + int no_retries; + + no_retries = 0; + + while ((res = cmap_initialize(&instance->cmap_handle)) == CS_ERR_TRY_AGAIN && + no_retries++ < instance->advanced_settings->max_cs_try_again) { + (void)poll(NULL, 0, 1000); + } + + if (res != CS_OK) { + errx(1, "Failed to initialize the cmap API. Error %s", cs_strerror(res)); + } + + if ((res = cmap_context_set(instance->cmap_handle, (void *)instance)) != CS_OK) { + errx(1, "Can't set cmap context. Error %s", cs_strerror(res)); + } + + cmap_fd_get(instance->cmap_handle, &instance->cmap_poll_fd); +} + +static void +qdevice_cmap_node_list_event(struct qdevice_instance *instance) +{ + struct node_list nlist; + int config_version_set; + uint64_t config_version; + + qdevice_log(LOG_DEBUG, "Node list configuration possibly changed"); + if (qdevice_cmap_get_nodelist(instance->cmap_handle, &nlist) != 0) { + qdevice_log(LOG_ERR, "Can't get configuration node list."); + + if (qdevice_model_get_config_node_list_failed(instance) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_model_get_config_node_list_failed returned error -> exit"); + exit(2); + } + + return ; + } + + config_version_set = (qdevice_cmap_get_config_version(instance->cmap_handle, + &config_version) == 0); + + if (node_list_eq(&instance->config_node_list, &nlist)) { + return ; + } + + qdevice_log(LOG_DEBUG, "Node list changed"); + if (config_version_set) { + qdevice_log(LOG_DEBUG, " config_version = "UTILS_PRI_CONFIG_VERSION, config_version); + } + qdevice_log_debug_dump_node_list(&nlist); + + if (qdevice_model_config_node_list_changed(instance, &nlist, + config_version_set, config_version) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_model_config_node_list_changed returned error -> exit"); + exit(2); + } + + node_list_free(&instance->config_node_list); + if (node_list_clone(&instance->config_node_list, &nlist) != 0) { + qdevice_log(LOG_ERR, "Can't allocate instance->config_node_list clone"); + + node_list_free(&nlist); + + if (qdevice_model_get_config_node_list_failed(instance) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_model_get_config_node_list_failed returned error -> exit"); + exit(2); + } + + return ; + } + + instance->config_node_list_version_set = config_version_set; + + if (config_version_set) { + instance->config_node_list_version = config_version; + } +} + +static void +qdevice_cmap_logging_event(struct qdevice_instance *instance) +{ + + qdevice_log(LOG_DEBUG, "Logging configuration possibly changed"); + qdevice_log_configure(instance); +} + +static void +qdevice_cmap_heuristics_event(struct qdevice_instance *instance) +{ + + qdevice_log(LOG_DEBUG, "Heuristics configuration possibly changed"); + if (qdevice_instance_configure_from_cmap_heuristics(instance) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_instance_configure_from_cmap_heuristics returned error -> exit"); + exit(2); + } +} + +static void +qdevice_cmap_reload_cb(cmap_handle_t cmap_handle, cmap_track_handle_t cmap_track_handle, + int32_t event, const char *key_name, + struct cmap_notify_value new_value, struct cmap_notify_value old_value, + void *user_data) +{ + cs_error_t cs_res; + uint8_t reload; + struct qdevice_instance *instance; + const char *node_list_prefix_str; + const char *logging_prefix_str; + const char *heuristics_prefix_str; + struct qdevice_cmap_change_events events; + + memset(&events, 0, sizeof(events)); + node_list_prefix_str = "nodelist."; + logging_prefix_str = "logging."; + heuristics_prefix_str = "quorum.device.heuristics."; + + if (cmap_context_get(cmap_handle, (const void **)&instance) != CS_OK) { + qdevice_log(LOG_ERR, "Fatal error. Can't get cmap context"); + exit(1); + } + + /* + * Wait for full reload + */ + if (strcmp(key_name, "config.totemconfig_reload_in_progress") == 0 && + new_value.type == CMAP_VALUETYPE_UINT8 && new_value.len == sizeof(reload)) { + reload = 1; + if (memcmp(new_value.data, &reload, sizeof(reload)) == 0) { + /* + * Ignore nodelist changes + */ + instance->cmap_reload_in_progress = 1; + return ; + } else { + instance->cmap_reload_in_progress = 0; + events.node_list = 1; + events.logging = 1; + events.heuristics = 1; + } + } + + if (instance->cmap_reload_in_progress) { + return ; + } + + if (((cs_res = cmap_get_uint8(cmap_handle, "config.totemconfig_reload_in_progress", + &reload)) == CS_OK) && reload == 1) { + return ; + } + + if (strncmp(key_name, node_list_prefix_str, strlen(node_list_prefix_str)) == 0) { + events.node_list = 1; + } + + if (strncmp(key_name, logging_prefix_str, strlen(logging_prefix_str)) == 0) { + events.logging = 1; + } + + if (strncmp(key_name, heuristics_prefix_str, strlen(heuristics_prefix_str)) == 0) { + events.heuristics = 1; + } + + if (events.logging) { + qdevice_cmap_logging_event(instance); + } + + if (events.node_list) { + qdevice_cmap_node_list_event(instance); + } + + if (events.heuristics) { + qdevice_cmap_heuristics_event(instance); + } + + /* + * Inform model about change + */ + if (qdevice_model_cmap_changed(instance, &events) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_model_cmap_changed returned error -> exit"); + exit(2); + } +} + +int +qdevice_cmap_add_track(struct qdevice_instance *instance) +{ + cs_error_t res; + + res = cmap_track_add(instance->cmap_handle, "config.totemconfig_reload_in_progress", + CMAP_TRACK_ADD | CMAP_TRACK_MODIFY, qdevice_cmap_reload_cb, + NULL, &instance->cmap_reload_track_handle); + + if (res != CS_OK) { + qdevice_log(LOG_ERR, "Can't initialize cmap totemconfig_reload_in_progress tracking"); + return (-1); + } + + res = cmap_track_add(instance->cmap_handle, "nodelist.", + CMAP_TRACK_ADD | CMAP_TRACK_DELETE | CMAP_TRACK_MODIFY | CMAP_TRACK_PREFIX, + qdevice_cmap_reload_cb, + NULL, &instance->cmap_nodelist_track_handle); + + if (res != CS_OK) { + qdevice_log(LOG_ERR, "Can't initialize cmap nodelist tracking"); + return (-1); + } + + res = cmap_track_add(instance->cmap_handle, "logging.", + CMAP_TRACK_ADD | CMAP_TRACK_DELETE | CMAP_TRACK_MODIFY | CMAP_TRACK_PREFIX, + qdevice_cmap_reload_cb, + NULL, &instance->cmap_logging_track_handle); + + if (res != CS_OK) { + qdevice_log(LOG_ERR, "Can't initialize logging tracking"); + return (-1); + } + + res = cmap_track_add(instance->cmap_handle, "quorum.device.heuristics.", + CMAP_TRACK_ADD | CMAP_TRACK_DELETE | CMAP_TRACK_MODIFY | CMAP_TRACK_PREFIX, + qdevice_cmap_reload_cb, + NULL, &instance->cmap_heuristics_track_handle); + + if (res != CS_OK) { + qdevice_log(LOG_ERR, "Can't initialize logging tracking"); + return (-1); + } + + return (0); +} + +int +qdevice_cmap_del_track(struct qdevice_instance *instance) +{ + cs_error_t res; + + res = cmap_track_delete(instance->cmap_handle, instance->cmap_reload_track_handle); + if (res != CS_OK) { + qdevice_log(LOG_WARNING, "Can't delete cmap totemconfig_reload_in_progress tracking"); + } + + res = cmap_track_delete(instance->cmap_handle, instance->cmap_nodelist_track_handle); + if (res != CS_OK) { + qdevice_log(LOG_WARNING, "Can't delete cmap nodelist tracking"); + } + + res = cmap_track_delete(instance->cmap_handle, instance->cmap_logging_track_handle); + if (res != CS_OK) { + qdevice_log(LOG_WARNING, "Can't delete cmap logging tracking"); + } + + res = cmap_track_delete(instance->cmap_handle, instance->cmap_heuristics_track_handle); + if (res != CS_OK) { + qdevice_log(LOG_WARNING, "Can't delete cmap heuristics tracking"); + } + + return (0); +} + +void +qdevice_cmap_destroy(struct qdevice_instance *instance) +{ + cs_error_t res; + + res = cmap_finalize(instance->cmap_handle); + + if (res != CS_OK) { + qdevice_log(LOG_WARNING, "Can't finalize cmap. Error %s", cs_strerror(res)); + } +} + +int +qdevice_cmap_dispatch(struct qdevice_instance *instance) +{ + cs_error_t res; + + /* + * dispatch can block if corosync is during sync phase + */ + if (instance->sync_in_progress) { + return (0); + } + + res = cmap_dispatch(instance->cmap_handle, CS_DISPATCH_ALL); + + if (res != CS_OK && res != CS_ERR_TRY_AGAIN) { + qdevice_log(LOG_ERR, "Can't dispatch cmap messages"); + + return (-1); + } + + return (0); +} diff --git a/qdevices/qdevice-cmap.h b/qdevices/qdevice-cmap.h new file mode 100644 index 0000000..b6d8fcb --- /dev/null +++ b/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 + +#include +#include +#include "node-list.h" +#include "qdevice-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct qdevice_cmap_change_events { + unsigned int logging : 1; + unsigned int node_list : 1; + unsigned int heuristics : 1; +}; + +extern int qdevice_cmap_get_nodelist(cmap_handle_t cmap_handle, + struct node_list *list); + +extern int qdevice_cmap_get_config_version(cmap_handle_t cmap_handle, + uint64_t *config_version); + +extern void qdevice_cmap_init(struct qdevice_instance *instance); + +extern int qdevice_cmap_add_track(struct qdevice_instance *instance); + +extern int qdevice_cmap_del_track(struct qdevice_instance *instance); + +extern void qdevice_cmap_destroy(struct qdevice_instance *instance); + +extern int qdevice_cmap_dispatch(struct qdevice_instance *instance); + +extern int qdevice_cmap_store_config_node_list(struct qdevice_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_CMAP_H_ */ diff --git a/qdevices/qdevice-config.h b/qdevices/qdevice-config.h new file mode 100644 index 0000000..d47e5e5 --- /dev/null +++ b/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 + +#include +#include + +#include +#include +#include +#include "qdevice-heuristics-mode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * There are "hardcoded" defines for qdevice. It's not so good + * idea to change them as long as you are not 100% sure what you are doing. Also + * most of them can be changed in CLI via advanced_settings (-S). + */ +#define QDEVICE_DEFAULT_LOCK_FILE LOCALSTATEDIR"/run/corosync-qdevice/corosync-qdevice.pid" +#define QDEVICE_DEFAULT_LOCAL_SOCKET_FILE LOCALSTATEDIR"/run/corosync-qdevice/corosync-qdevice.sock" +#define QDEVICE_DEFAULT_LOCAL_SOCKET_BACKLOG 10 +#define QDEVICE_MIN_LOCAL_SOCKET_BACKLOG 1 + +#define QDEVICE_DEFAULT_MAX_CS_TRY_AGAIN 10 +#define QDEVICE_MIN_MAX_CS_TRY_AGAIN 1 + +#define QDEVICE_PROGRAM_NAME "corosync-qdevice" +#define QDEVICE_LOG_SUBSYS "QDEVICE" +#define QDEVICE_LOG_DEFAULT_TO_STDERR 1 +#define QDEVICE_LOG_DEFAULT_TO_SYSLOG 1 +#define QDEVICE_LOG_DEFAULT_TO_LOGFILE 0 +#define QDEVICE_LOG_DEFAULT_SYSLOG_FACILITY LOG_DAEMON +#define QDEVICE_LOG_DEFAULT_SYSLOG_PRIORITY LOG_INFO +#define QDEVICE_LOG_DEFAULT_DEBUG 0 +#define QDEVICE_LOG_DEFAULT_FILELINE 0 +#define QDEVICE_LOG_DEFAULT_TIMESTAMP 0 +#define QDEVICE_LOG_DEFAULT_FUNCTION_NAME 0 + +#define QDEVICE_DEFAULT_VOTEQUORUM_DEVICE_NAME "Qdevice" + +#define QDEVICE_DEFAULT_IPC_MAX_CLIENTS 10 +#define QDEVICE_MIN_IPC_MAX_CLIENTS 0 +#define QDEVICE_DEFAULT_IPC_MAX_RECEIVE_SIZE (4*1024) +#define QDEVICE_DEFAULT_IPC_MAX_SEND_SIZE (64*1024) +#define QDEVICE_MIN_IPC_RECEIVE_SEND_SIZE 1024 + +#define QDEVICE_DEFAULT_HEURISTICS_IPC_MAX_SEND_BUFFERS 128 +#define QDEVICE_MIN_HEURISTICS_IPC_MAX_SEND_BUFFERS 10 +#define QDEVICE_DEFAULT_HEURISTICS_IPC_MAX_SEND_RECEIVE_SIZE (4 * 1024) +#define QDEVICE_MIN_HEURISTICS_IPC_MAX_SEND_RECEIVE_SIZE 1024 + +#define QDEVICE_DEFAULT_HEURISTICS_MIN_TIMEOUT (1 * 1000) +#define QDEVICE_DEFAULT_HEURISTICS_MAX_TIMEOUT (2 * 60 * 1000) +#define QDEVICE_MIN_HEURISTICS_TIMEOUT 250 +#define QDEVICE_DEFAULT_HEURISTICS_MIN_INTERVAL QDEVICE_DEFAULT_HEURISTICS_MIN_TIMEOUT +#define QDEVICE_DEFAULT_HEURISTICS_MAX_INTERVAL (60 * 60 * 1000) +#define QDEVICE_MIN_HEURISTICS_INTERVAL QDEVICE_MIN_HEURISTICS_TIMEOUT + +#define QDEVICE_DEFAULT_HEURISTICS_MODE QDEVICE_HEURISTICS_MODE_DISABLED + +#define QDEVICE_DEFAULT_HEURISTICS_MAX_EXECS 32 +#define QDEVICE_MIN_HEURISTICS_MAX_EXECS 1 + +#define QDEVICE_DEFAULT_HEURISTICS_USE_EXECVP 0 + +#define QDEVICE_DEFAULT_HEURISTICS_MAX_PROCESSES (QDEVICE_DEFAULT_HEURISTICS_MAX_EXECS * 5) +#define QDEVICE_MIN_HEURISTICS_MAX_PROCESSES 1 + +#define QDEVICE_DEFAULT_HEURISTICS_KILL_LIST_INTERVAL (5 * 1000) +#define QDEVICE_MIN_HEURISTICS_KILL_LIST_INTERVAL QDEVICE_MIN_HEURISTICS_TIMEOUT + +#define QDEVICE_TOOL_PROGRAM_NAME "corosync-qdevice-tool" + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_CONFIG_H_ */ diff --git a/qdevices/qdevice-heuristics-cmd-str.h b/qdevices/qdevice-heuristics-cmd-str.h new file mode 100644 index 0000000..13d7ecc --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics-cmd.c b/qdevices/qdevice-heuristics-cmd.c new file mode 100644 index 0000000..1306e94 --- /dev/null +++ b/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 + +#include "dynar.h" +#include "dynar-str.h" +#include "qdevice-heuristics-exec-result.h" +#include "qdevice-heuristics-cmd.h" +#include "qdevice-heuristics-cmd-str.h" +#include "qdevice-heuristics-io.h" +#include "qdevice-log.h" + +static int +qdevice_heuristics_cmd_process_exec_result(struct qdevice_heuristics_instance *instance, + struct dynar *data) +{ + uint32_t seq_number; + char *str; + enum qdevice_heuristics_exec_result exec_result; + + str = dynar_data(data); + + if (sscanf(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT_ADD_SPACE "%"PRIu32" %u", &seq_number, + &exec_result) != 2) { + qdevice_log(LOG_CRIT, "Can't parse exec result command (sscanf)"); + + return (-1); + } + + qdevice_log(LOG_DEBUG, + "Received heuristics exec result command with seq_no \"%"PRIu32"\" and result \"%s\"", seq_number, + qdevice_heuristics_exec_result_to_str(exec_result)); + + if (!instance->waiting_for_result) { + qdevice_log(LOG_DEBUG, "Received exec result is not expected. Ignoring."); + + return (0); + } + + if (seq_number != instance->expected_reply_seq_number) { + qdevice_log(LOG_DEBUG, "Received heuristics exec result seq number %"PRIu32 + " is not expected one (expected %"PRIu32"). Ignoring.", seq_number, + instance->expected_reply_seq_number); + + return (0); + } + + instance->waiting_for_result = 0; + + if (qdevice_heuristics_result_notifier_notify(&instance->exec_result_notifier_list, + (void *)instance, seq_number, exec_result) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_heuristics_result_notifier_notify returned non-zero result"); + return (-1); + } + + return (0); +} + +/* + * 1 - Line processed + * 0 - No line to process - everything processed + * -1 - Error + */ +static int +qdevice_heuristics_cmd_process_one_line(struct qdevice_heuristics_instance *instance, + struct dynar *data) +{ + char *str; + size_t str_len; + size_t nl_pos; + size_t zi; + + str = dynar_data(data); + str_len = dynar_size(data); + + /* + * Find valid line + */ + for (zi = 0; zi < str_len && str[zi] != '\r' && str[zi] != '\n'; zi++) ; + + if (zi >= str_len) { + /* + * Command is not yet fully readed + */ + return (0); + } + + nl_pos = zi; + + str[nl_pos] = '\0'; + + if (strncmp(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT_ADD_SPACE, + strlen(QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT_ADD_SPACE)) == 0) { + if (qdevice_heuristics_cmd_process_exec_result(instance, data) != 0) { + return (-1); + } + } else { + qdevice_log(LOG_CRIT, + "Heuristics worker sent unknown command \"%s\"", str); + + return (-1); + } + + /* + * Find place where is begining of new "valid" line + */ + for (zi = nl_pos + 1; zi < str_len && (str[zi] == '\0' || str[zi] == '\n' || str[zi] == '\r'); zi++) ; + + memmove(str, str + zi, str_len - zi); + if (dynar_set_size(data, str_len - zi) == -1) { + qdevice_log(LOG_CRIT, + "qdevice_heuristics_cmd_process_one_line: Can't set dynar size"); + return (-1); + } + + return (1); +} + +/* + * 0 - No error + * -1 - Error + */ +static int +qdevice_heuristics_cmd_process(struct qdevice_heuristics_instance *instance) +{ + int res; + + while ((res = + qdevice_heuristics_cmd_process_one_line(instance, &instance->cmd_in_buffer)) == 1) ; + + return (res); +} + +/* + * 0 - No error + * 1 - Error + */ +int +qdevice_heuristics_cmd_read_from_pipe(struct qdevice_heuristics_instance *instance) +{ + int res; + int ret; + + res = qdevice_heuristics_io_read(instance->pipe_cmd_recv, &instance->cmd_in_buffer); + + ret = 0; + + switch (res) { + case 0: + /* + * Partial read + */ + break; + case -1: + qdevice_log(LOG_ERR, "Lost connection with heuristics worker"); + ret = -1; + break; + case -2: + qdevice_log(LOG_ERR, "Heuristics worker sent too long cmd."); + ret = -1; + break; + case -3: + qdevice_log(LOG_ERR, "Unhandled error when reading from heuristics worker cmd fd"); + ret = -1; + break; + case 1: + /* + * At least one cmd line received + */ + ret = qdevice_heuristics_cmd_process(instance); + break; + } + + return (ret); +} + +/* + * 0 - No error + * 1 - Error + */ +int +qdevice_heuristics_cmd_write(struct qdevice_heuristics_instance *instance) +{ + struct send_buffer_list_entry *send_buffer; + int res; + + send_buffer = send_buffer_list_get_active(&instance->cmd_out_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_CRIT, "send_buffer_list_get_active in qdevice_heuristics_cmd_write returned NULL"); + + return (-1); + } + + res = qdevice_heuristics_io_write(instance->pipe_cmd_send, &send_buffer->buffer, + &send_buffer->msg_already_sent_bytes); + + if (res == 1) { + send_buffer_list_delete(&instance->cmd_out_buffer_list, send_buffer); + } + + if (res == -1) { + qdevice_log(LOG_CRIT, "qdevice_heuristics_io_write returned -1 (write returned 0)"); + + return (-1); + } + + if (res == -2) { + qdevice_log(LOG_CRIT, "Unhandled error in during sending message to heuristics " + "worker (qdevice_heuristics_io_write returned -2)"); + + return (-1); + } + + return (0); +} + +static int +qdevice_heuristics_cmd_remove_newlines(struct dynar *str) +{ + size_t len; + size_t zi; + char *buf; + + len = dynar_size(str); + buf = dynar_data(str); + + for (zi = 0; zi < len ; zi++) { + if (buf[zi] == '\n' || buf[zi] == '\r') { + buf[zi] = ' '; + } + } + + return (0); +} + +int +qdevice_heuristics_cmd_write_exec_list(struct qdevice_heuristics_instance *instance, + const struct qdevice_heuristics_exec_list *new_exec_list) +{ + struct send_buffer_list_entry *send_buffer; + struct qdevice_heuristics_exec_list_entry *entry; + + send_buffer = send_buffer_list_get_new(&instance->cmd_out_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't alloc send list for cmd change exec list"); + + return (-1); + } + + if (dynar_str_cpy(&send_buffer->buffer, QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_CLEAR) == -1 || + dynar_str_cat(&send_buffer->buffer, "\n") == -1) { + qdevice_log(LOG_ERR, "Can't alloc list clear message"); + + send_buffer_list_discard_new(&instance->cmd_out_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&instance->cmd_out_buffer_list, send_buffer); + + if (new_exec_list == NULL) { + return (0); + } + + /* + * new_exec_list is not NULL, send it + */ + TAILQ_FOREACH(entry, new_exec_list, entries) { + send_buffer = send_buffer_list_get_new(&instance->cmd_out_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't alloc send list for cmd change exec list"); + + return (-1); + } + + if (dynar_str_cpy(&send_buffer->buffer, + QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_ADD_SPACE) == -1 || + dynar_str_cat(&send_buffer->buffer, entry->name) == -1 || + dynar_str_cat(&send_buffer->buffer, " ") == -1 || + dynar_str_cat(&send_buffer->buffer, entry->command) == -1 || + qdevice_heuristics_cmd_remove_newlines(&send_buffer->buffer) == -1 || + dynar_str_cat(&send_buffer->buffer, "\n") == -1) { + qdevice_log(LOG_ERR, "Can't alloc list add message"); + + send_buffer_list_discard_new(&instance->cmd_out_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&instance->cmd_out_buffer_list, send_buffer); + } + + return (0); +} + +int +qdevice_heuristics_cmd_write_exec(struct qdevice_heuristics_instance *instance, + uint32_t timeout, uint32_t seq_number) +{ + struct send_buffer_list_entry *send_buffer; + + send_buffer = send_buffer_list_get_new(&instance->cmd_out_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't alloc send list for cmd change exec list"); + + return (-1); + } + + if (dynar_str_cpy(&send_buffer->buffer, QDEVICE_HEURISTICS_CMD_STR_EXEC_ADD_SPACE) == -1 || + dynar_str_catf(&send_buffer->buffer, "%"PRIu32" %"PRIu32"\n", timeout, seq_number) == -1) { + qdevice_log(LOG_ERR, "Can't alloc exec message"); + + send_buffer_list_discard_new(&instance->cmd_out_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&instance->cmd_out_buffer_list, send_buffer); + + return (0); +} diff --git a/qdevices/qdevice-heuristics-cmd.h b/qdevices/qdevice-heuristics-cmd.h new file mode 100644 index 0000000..100b051 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics-exec-list.c b/qdevices/qdevice-heuristics-exec-list.c new file mode 100644 index 0000000..b752400 --- /dev/null +++ b/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 +#include +#include + +#include "qdevice-heuristics-exec-list.h" + +void +qdevice_heuristics_exec_list_init(struct qdevice_heuristics_exec_list *list) +{ + + TAILQ_INIT(list); +} + +struct qdevice_heuristics_exec_list_entry * +qdevice_heuristics_exec_list_add(struct qdevice_heuristics_exec_list *list, + char *name, char *command) +{ + struct qdevice_heuristics_exec_list_entry *entry; + + entry = (struct qdevice_heuristics_exec_list_entry *)malloc(sizeof(*entry)); + if (entry == NULL) { + return (NULL); + } + + memset(entry, 0, sizeof(*entry)); + + entry->name = strdup(name); + if (entry->name == NULL) { + free(entry); + + return (NULL); + } + + entry->command = strdup(command); + if (entry->command == NULL) { + free(entry->name); + free(entry); + + return (NULL); + } + + TAILQ_INSERT_TAIL(list, entry, entries); + + return (entry); +} + +void +qdevice_heuristics_exec_list_free(struct qdevice_heuristics_exec_list *list) +{ + struct qdevice_heuristics_exec_list_entry *entry; + struct qdevice_heuristics_exec_list_entry *entry_next; + + entry = TAILQ_FIRST(list); + + while (entry != NULL) { + entry_next = TAILQ_NEXT(entry, entries); + + free(entry->name); + free(entry->command); + free(entry); + + entry = entry_next; + } + + TAILQ_INIT(list); +} + +size_t +qdevice_heuristics_exec_list_size(const struct qdevice_heuristics_exec_list *list) +{ + struct qdevice_heuristics_exec_list_entry *entry; + size_t res; + + res = 0; + + TAILQ_FOREACH(entry, list, entries) { + res++; + } + + return (res); +} + +int +qdevice_heuristics_exec_list_clone(struct qdevice_heuristics_exec_list *dst_list, + const struct qdevice_heuristics_exec_list *src_list) +{ + struct qdevice_heuristics_exec_list_entry *entry; + + qdevice_heuristics_exec_list_init(dst_list); + + TAILQ_FOREACH(entry, src_list, entries) { + if (qdevice_heuristics_exec_list_add(dst_list, entry->name, entry->command) == NULL) { + qdevice_heuristics_exec_list_free(dst_list); + + return (-1); + } + } + + return (0); +} + +void +qdevice_heuristics_exec_list_del(struct qdevice_heuristics_exec_list *list, + struct qdevice_heuristics_exec_list_entry *entry) +{ + + TAILQ_REMOVE(list, entry, entries); + + free(entry->name); + free(entry->command); + free(entry); +} + +int +qdevice_heuristics_exec_list_is_empty(const struct qdevice_heuristics_exec_list *list) +{ + + return (TAILQ_EMPTY(list)); +} + +struct qdevice_heuristics_exec_list_entry * +qdevice_heuristics_exec_list_find_name(const struct qdevice_heuristics_exec_list *list, + const char *name) +{ + struct qdevice_heuristics_exec_list_entry *entry; + + TAILQ_FOREACH(entry, list, entries) { + if (strcmp(entry->name, name) == 0) { + return (entry); + } + } + + return (NULL); +} + +int +qdevice_heuristics_exec_list_eq(const struct qdevice_heuristics_exec_list *list1, + const struct qdevice_heuristics_exec_list *list2) +{ + struct qdevice_heuristics_exec_list_entry *entry1; + struct qdevice_heuristics_exec_list_entry *entry2; + struct qdevice_heuristics_exec_list tmp_list; + int res; + + res = 1; + + if (qdevice_heuristics_exec_list_clone(&tmp_list, list2) != 0) { + return (-1); + } + + TAILQ_FOREACH(entry1, list1, entries) { + entry2 = qdevice_heuristics_exec_list_find_name(&tmp_list, entry1->name); + if (entry2 == NULL) { + res = 0; + goto return_res; + } + + if (strcmp(entry1->command, entry2->command) != 0) { + res = 0; + goto return_res; + } + + qdevice_heuristics_exec_list_del(&tmp_list, entry2); + } + + if (!qdevice_heuristics_exec_list_is_empty(&tmp_list)) { + res = 0; + goto return_res; + } + +return_res: + qdevice_heuristics_exec_list_free(&tmp_list); + + return (res); +} diff --git a/qdevices/qdevice-heuristics-exec-list.h b/qdevices/qdevice-heuristics-exec-list.h new file mode 100644 index 0000000..b80067c --- /dev/null +++ b/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 + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct qdevice_heuristics_exec_list_entry { + char *name; + char *command; + TAILQ_ENTRY(qdevice_heuristics_exec_list_entry) entries; +}; + +TAILQ_HEAD(qdevice_heuristics_exec_list, qdevice_heuristics_exec_list_entry); + +extern void qdevice_heuristics_exec_list_init( + struct qdevice_heuristics_exec_list *list); + +extern struct qdevice_heuristics_exec_list_entry *qdevice_heuristics_exec_list_add( + struct qdevice_heuristics_exec_list *list, char *name, char *command); + +extern void qdevice_heuristics_exec_list_free( + struct qdevice_heuristics_exec_list *list); + +extern size_t qdevice_heuristics_exec_list_size( + const struct qdevice_heuristics_exec_list *list); + +extern int qdevice_heuristics_exec_list_clone( + struct qdevice_heuristics_exec_list *dst_list, + const struct qdevice_heuristics_exec_list *src_list); + +extern void qdevice_heuristics_exec_list_del( + struct qdevice_heuristics_exec_list *list, struct qdevice_heuristics_exec_list_entry *entry); + +extern int qdevice_heuristics_exec_list_is_empty( + const struct qdevice_heuristics_exec_list *list); + +extern struct qdevice_heuristics_exec_list_entry *qdevice_heuristics_exec_list_find_name( + const struct qdevice_heuristics_exec_list *list, const char *name); + +extern int qdevice_heuristics_exec_list_eq( + const struct qdevice_heuristics_exec_list *list1, + const struct qdevice_heuristics_exec_list *list2); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_HEURISTICS_EXEC_LIST_H_ */ diff --git a/qdevices/qdevice-heuristics-exec-result.c b/qdevices/qdevice-heuristics-exec-result.c new file mode 100644 index 0000000..8d00ae0 --- /dev/null +++ b/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"); +} diff --git a/qdevices/qdevice-heuristics-exec-result.h b/qdevices/qdevice-heuristics-exec-result.h new file mode 100644 index 0000000..12933b3 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics-instance.c b/qdevices/qdevice-heuristics-instance.c new file mode 100644 index 0000000..2cfce20 --- /dev/null +++ b/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 + +#include "qdevice-heuristics-instance.h" +#include "qdevice-heuristics-exec-list.h" + +int +qdevice_heuristics_instance_init(struct qdevice_heuristics_instance *instance) +{ + + memset(instance, 0, sizeof(*instance)); + + qdevice_heuristics_exec_list_init(&instance->exec_list); + qdevice_heuristics_result_notifier_list_init(&instance->exec_result_notifier_list); + + return (0); +} + +int +qdevice_heuristics_instance_destroy(struct qdevice_heuristics_instance *instance) +{ + + qdevice_heuristics_result_notifier_list_free(&instance->exec_result_notifier_list); + qdevice_heuristics_exec_list_free(&instance->exec_list); + + return (0); +} diff --git a/qdevices/qdevice-heuristics-instance.h b/qdevices/qdevice-heuristics-instance.h new file mode 100644 index 0000000..51e7ea1 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics-io.c b/qdevices/qdevice-heuristics-io.c new file mode 100644 index 0000000..9485e06 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#include "qdevice-heuristics-io.h" + +#define QDEVICE_HEURISTICS_IO_BUFFER_SIZE 256 + +ssize_t +qdevice_heuristics_io_blocking_write(int fd, const void *buf, size_t count) +{ + ssize_t bytes_written; + ssize_t tmp_bytes_written; + + bytes_written = 0; + + do { + tmp_bytes_written = write(fd, (const char *)buf + bytes_written, + (count - bytes_written > SSIZE_MAX) ? SSIZE_MAX : count - bytes_written); + if (tmp_bytes_written == -1) { + if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) { + return (-1); + } + } else { + bytes_written += tmp_bytes_written; + } + } while ((size_t)bytes_written != count); + + return (bytes_written); +} + +/* + * 1 Full line readed (at least one \n found) + * 0 Partial read (no error) + * -1 End of connection + * -2 Buffer too long + * -3 Unhandled error + */ +int +qdevice_heuristics_io_read(int fd, struct dynar *dest) +{ + char buf[QDEVICE_HEURISTICS_IO_BUFFER_SIZE]; + ssize_t readed; + int res; + size_t zi; + + res = 0; + readed = read(fd, buf, sizeof(buf)); + if (readed > 0) { + if (dynar_cat(dest, buf, readed) == -1) { + res = -2; + goto exit_err; + } + + for (zi = 0; zi < (size_t)readed; zi++) { + if (buf[zi] == '\n') { + res = 1; + } + } + } + + if (readed == 0) { + res = -1; + } + + if (readed < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { + res = -3; + } + +exit_err: + return (res); +} + +/* + * 1 All data succesfully sent + * 0 Partial send (no error) + * -1 send returned 0, + * -2 Unhandled error + */ +int +qdevice_heuristics_io_write(int fd, const struct dynar *msg, size_t *already_sent_bytes) +{ + ssize_t sent; + size_t to_send; + int res; + + res = 0; + + to_send = dynar_size(msg) - *already_sent_bytes; + if (to_send > QDEVICE_HEURISTICS_IO_BUFFER_SIZE) { + to_send = QDEVICE_HEURISTICS_IO_BUFFER_SIZE; + } + + sent = write(fd, dynar_data(msg) + *already_sent_bytes, + to_send); + + if (sent > 0) { + *already_sent_bytes += sent; + + if (*already_sent_bytes == dynar_size(msg)) { + return (1); + } + } + + if (sent == 0) { + res = -1; + } + + if (sent < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { + res = -2; + } + + return (res); +} diff --git a/qdevices/qdevice-heuristics-io.h b/qdevices/qdevice-heuristics-io.h new file mode 100644 index 0000000..b85d147 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics-log.c b/qdevices/qdevice-heuristics-log.c new file mode 100644 index 0000000..319da81 --- /dev/null +++ b/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); +} diff --git a/qdevices/qdevice-heuristics-log.h b/qdevices/qdevice-heuristics-log.h new file mode 100644 index 0000000..4fa8628 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics-mode.c b/qdevices/qdevice-heuristics-mode.c new file mode 100644 index 0000000..f7e4425 --- /dev/null +++ b/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 +#include +#include + +#include "qdevice-heuristics-mode.h" + +const char* +qdevice_heuristics_mode_to_str(enum qdevice_heuristics_mode mode) +{ + switch (mode) { + case QDEVICE_HEURISTICS_MODE_DISABLED: return ("Disabled"); break; + case QDEVICE_HEURISTICS_MODE_ENABLED: return ("Enabled"); break; + case QDEVICE_HEURISTICS_MODE_SYNC: return ("Enabled only on sync"); break; + } + + return ("Undefined"); +} diff --git a/qdevices/qdevice-heuristics-mode.h b/qdevices/qdevice-heuristics-mode.h new file mode 100644 index 0000000..bf6236c --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics-result-notifier.c b/qdevices/qdevice-heuristics-result-notifier.c new file mode 100644 index 0000000..a7adf1e --- /dev/null +++ b/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 +#include + +#include "qdevice-heuristics-result-notifier.h" + +void +qdevice_heuristics_result_notifier_list_init(struct qdevice_heuristics_result_notifier_list *notifier_list) +{ + + TAILQ_INIT(notifier_list); +} + +struct qdevice_heuristics_result_notifier_item * +qdevice_heuristics_result_notifier_list_get(struct qdevice_heuristics_result_notifier_list *notifier_list, + qdevice_heuristics_result_notifier_callback callback) +{ + struct qdevice_heuristics_result_notifier_item *item; + + TAILQ_FOREACH(item, notifier_list, entries) { + if (item->callback == callback) { + return (item); + } + } + + return (NULL); +} + +struct qdevice_heuristics_result_notifier_item * +qdevice_heuristics_result_notifier_list_add(struct qdevice_heuristics_result_notifier_list *notifier_list, + qdevice_heuristics_result_notifier_callback callback) +{ + struct qdevice_heuristics_result_notifier_item *item; + + item = qdevice_heuristics_result_notifier_list_get(notifier_list, callback); + if (item != NULL) { + return (item); + } + + item = (struct qdevice_heuristics_result_notifier_item *)malloc(sizeof(*item)); + if (item == NULL) { + return (NULL); + } + memset(item, 0, sizeof(*item)); + item->callback = callback; + item->active = 0; + + TAILQ_INSERT_TAIL(notifier_list, item, entries); + + return (item); +} + +int +qdevice_heuristics_result_notifier_list_set_active(struct qdevice_heuristics_result_notifier_list *notifier_list, + qdevice_heuristics_result_notifier_callback callback, int active) +{ + struct qdevice_heuristics_result_notifier_item *item; + + item = qdevice_heuristics_result_notifier_list_get(notifier_list, callback); + if (item == NULL) { + return (-1); + } + + item->active = active; + + return (0); +} + +void +qdevice_heuristics_result_notifier_list_free(struct qdevice_heuristics_result_notifier_list *notifier_list) +{ + struct qdevice_heuristics_result_notifier_item *item; + struct qdevice_heuristics_result_notifier_item *item_next; + + item = TAILQ_FIRST(notifier_list); + while (item != NULL) { + item_next = TAILQ_NEXT(item, entries); + + free(item); + item = item_next; + } +} + +int +qdevice_heuristics_result_notifier_notify(struct qdevice_heuristics_result_notifier_list *notifier_list, + void *heuristics_instance, uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result) +{ + struct qdevice_heuristics_result_notifier_item *item; + + TAILQ_FOREACH(item, notifier_list, entries) { + if (!item->active) { + continue ; + } + + if (item->callback(heuristics_instance, seq_number, exec_result) != 0) { + return (-1); + } + } + + return (0); +} diff --git a/qdevices/qdevice-heuristics-result-notifier.h b/qdevices/qdevice-heuristics-result-notifier.h new file mode 100644 index 0000000..d32d787 --- /dev/null +++ b/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 +#include +#include +#include + +#include "qdevice-heuristics-exec-result.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*qdevice_heuristics_result_notifier_callback)(void *heuristics_instance, uint32_t seq_number, + enum qdevice_heuristics_exec_result exec_result); + +struct qdevice_heuristics_result_notifier_item { + qdevice_heuristics_result_notifier_callback callback; + int active; + TAILQ_ENTRY(qdevice_heuristics_result_notifier_item) entries; +}; + +TAILQ_HEAD(qdevice_heuristics_result_notifier_list, qdevice_heuristics_result_notifier_item); + +extern void qdevice_heuristics_result_notifier_list_init( + struct qdevice_heuristics_result_notifier_list *notifier_list); + +extern struct qdevice_heuristics_result_notifier_item *qdevice_heuristics_result_notifier_list_add( + struct qdevice_heuristics_result_notifier_list *notifier_list, + qdevice_heuristics_result_notifier_callback callback); + +extern struct qdevice_heuristics_result_notifier_item *qdevice_heuristics_result_notifier_list_get( + struct qdevice_heuristics_result_notifier_list *notifier_list, + qdevice_heuristics_result_notifier_callback callback); + +extern int qdevice_heuristics_result_notifier_list_set_active( + struct qdevice_heuristics_result_notifier_list *notifier_list, + qdevice_heuristics_result_notifier_callback callback, int active); + +extern void qdevice_heuristics_result_notifier_list_free( + struct qdevice_heuristics_result_notifier_list *notifier_list); + +extern int qdevice_heuristics_result_notifier_notify( + struct qdevice_heuristics_result_notifier_list *notifier_list, + void *heuristics_instance, uint32_t seq_number, + enum qdevice_heuristics_exec_result exec_result); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_HEURISTICS_RESULT_NOTIFIER_H_ */ diff --git a/qdevices/qdevice-heuristics-worker-cmd.c b/qdevices/qdevice-heuristics-worker-cmd.c new file mode 100644 index 0000000..3745faf --- /dev/null +++ b/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 +#include +#include + +#include "dynar.h" +#include "dynar-str.h" +#include "qdevice-heuristics-io.h" +#include "qdevice-heuristics-worker.h" +#include "qdevice-heuristics-worker-cmd.h" +#include "qdevice-heuristics-cmd-str.h" +#include "qdevice-heuristics-worker-log.h" + +static int +qdevice_heuristics_worker_cmd_process_exec_list_add(struct qdevice_heuristics_worker_instance *instance, + struct dynar *data) +{ + size_t zi; + char *exec_name; + char *exec_command; + char *str; + + str = dynar_data(data); + + /* + * Skip to first space + */ + for (zi = 0; str[zi] != ' ' && str[zi] != '\0'; zi++) ; + + if (str[zi] == '\0') { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't find first space"); + return (-1); + } + + /* + * Skip to the end of spaces + */ + for (; str[zi] == ' '; zi++) ; + + if (str[zi] == '\0') { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't find start of exec name"); + return (-1); + } + + /* + * Found exec name + */ + exec_name = str + zi; + + /* + * Skip to the next spaces + */ + for (; str[zi] != ' ' && str[zi] != '\0'; zi++) ; + + if (str[zi] == '\0') { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't find second space"); + return (-1); + } + + /* + * Put trailing \0 into exec_name + */ + str[zi] = '\0'; + zi++; + + /* + * Skip to the end of next spaces + */ + for (; str[zi] == ' '; zi++) ; + + if (str[zi] == '\0') { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't find start of exec command"); + return (-1); + } + + /* + * Found exec_command + */ + exec_command = str + zi; + + qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG, + "qdevice_heuristics_worker_cmd_process_one_line: Received exec-list-add command " + "with name \"%s\" and command \"%s\"", exec_name, exec_command); + + if (qdevice_heuristics_exec_list_add(&instance->exec_list, exec_name, exec_command) == NULL) { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_cmd_process_exec_list_add: Can't alloc exec list entry"); + return (-1); + } + + return (0); +} + +static int +qdevice_heuristics_worker_cmd_process_exec(struct qdevice_heuristics_worker_instance *instance, + struct dynar *data) +{ + uint32_t timeout; + uint32_t seq_number; + char *str; + struct qdevice_heuristics_exec_list_entry *exec_list_entry; + struct process_list_entry *plist_entry; + + str = dynar_data(data); + + if (sscanf(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_ADD_SPACE "%"PRIu32" %"PRIu32, &timeout, + &seq_number) != 2) { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_cmd_process_exec: Can't parse command (sscanf)"); + return (-1); + } + + qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG, + "qdevice_heuristics_worker_cmd_process_exec: Received exec command " + "with seq_no \"%"PRIu32"\" and timeout \"%"PRIu32"\"", seq_number, timeout); + + if (instance->exec_timeout_timer != NULL) { + process_list_move_active_entries_to_kill_list(&instance->main_process_list); + + timer_list_delete(&instance->main_timer_list, instance->exec_timeout_timer); + instance->exec_timeout_timer = NULL; + } + + instance->last_exec_seq_number = seq_number; + + if (qdevice_heuristics_exec_list_is_empty(&instance->exec_list)) { + if (qdevice_heuristics_worker_cmd_write_exec_result(instance, + instance->last_exec_seq_number, + QDEVICE_HEURISTICS_EXEC_RESULT_DISABLED) != 0) { + return (-1); + } + } else { + /* + * Initialize process list (from exec list) + */ + TAILQ_FOREACH(exec_list_entry, &instance->exec_list, entries) { + plist_entry = process_list_add(&instance->main_process_list, + exec_list_entry->name, exec_list_entry->command); + + if (plist_entry == NULL) { + qdevice_heuristics_worker_log_printf(instance, LOG_ERR, + "qdevice_heuristics_worker_cmd_process_exec: Can't allocate " + "process list entry"); + + process_list_move_active_entries_to_kill_list( + &instance->main_process_list); + + if (qdevice_heuristics_worker_cmd_write_exec_result(instance, + instance->last_exec_seq_number, + QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) { + return (-1); + } + + return (0); + } + } + + if (process_list_exec_initialized(&instance->main_process_list) != 0) { + qdevice_heuristics_worker_log_printf(instance, LOG_ERR, + "qdevice_heuristics_worker_cmd_process_exec: Can't execute " + "process list"); + + process_list_move_active_entries_to_kill_list(&instance->main_process_list); + + if (qdevice_heuristics_worker_cmd_write_exec_result(instance, + instance->last_exec_seq_number, + QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) { + return (-1); + } + + return (0); + } + + instance->exec_timeout_timer = timer_list_add(&instance->main_timer_list, + timeout, qdevice_heuristics_worker_exec_timeout_timer_callback, + (void *)instance, NULL); + if (instance->exec_timeout_timer == NULL) { + qdevice_heuristics_worker_log_printf(instance, LOG_ERR, + "qdevice_heuristics_worker_cmd_process_exec: Can't add exec timeout " + "timer to timer list"); + + process_list_move_active_entries_to_kill_list(&instance->main_process_list); + + if (qdevice_heuristics_worker_cmd_write_exec_result(instance, + instance->last_exec_seq_number, + QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) { + return (-1); + } + + return (0); + } + } + + return (0); +} + +/* + * 1 - Line processed + * 0 - No line to process - everything processed + * -1 - Error + */ +static int +qdevice_heuristics_worker_cmd_process_one_line(struct qdevice_heuristics_worker_instance *instance, + struct dynar *data) +{ + char *str; + size_t str_len; + size_t nl_pos; + size_t zi; + + str = dynar_data(data); + str_len = dynar_size(data); + + /* + * Find valid line + */ + for (zi = 0; zi < str_len && str[zi] != '\r' && str[zi] != '\n'; zi++) ; + + if (zi >= str_len) { + /* + * Command is not yet fully readed + */ + return (0); + } + + nl_pos = zi; + + str[nl_pos] = '\0'; + + if (strcmp(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_CLEAR) == 0) { + qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG, + "qdevice_heuristics_worker_cmd_process_one_line: Received exec-list-clear command"); + + qdevice_heuristics_exec_list_free(&instance->exec_list); + } else if (strncmp(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_ADD_SPACE, + strlen(QDEVICE_HEURISTICS_CMD_STR_EXEC_LIST_ADD)) == 0) { + if (qdevice_heuristics_worker_cmd_process_exec_list_add(instance, data) != 0) { + return (-1); + } + } else if (strncmp(str, QDEVICE_HEURISTICS_CMD_STR_EXEC_ADD_SPACE, + strlen(QDEVICE_HEURISTICS_CMD_STR_EXEC_ADD_SPACE)) == 0) { + if (qdevice_heuristics_worker_cmd_process_exec(instance, data) != 0) { + return (-1); + } + } else { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_cmd_process_one_line: Unknown command \"%s\" " + "received from main qdevice process", str); + + return (-1); + } + + /* + * Find place where is begining of new "valid" line + */ + for (zi = nl_pos + 1; zi < str_len && (str[zi] == '\0' || str[zi] == '\n' || str[zi] == '\r'); zi++) ; + + memmove(str, str + zi, str_len - zi); + if (dynar_set_size(data, str_len - zi) == -1) { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_cmd_process_one_line: Can't set dynar size"); + return (-1); + } + + return (1); +} + +/* + * 0 - No error + * -1 - Error + */ +static int +qdevice_heuristics_worker_cmd_process(struct qdevice_heuristics_worker_instance *instance) +{ + int res; + + while ((res = + qdevice_heuristics_worker_cmd_process_one_line(instance, &instance->cmd_in_buffer)) == 1) ; + + return (res); +} + +/* + * 0 - No error + * 1 - Error + */ +int +qdevice_heuristics_worker_cmd_read_from_pipe(struct qdevice_heuristics_worker_instance *instance) +{ + int res; + int ret; + + res = qdevice_heuristics_io_read(QDEVICE_HEURISTICS_WORKER_CMD_IN_FD, &instance->cmd_in_buffer); + + ret = 0; + + switch (res) { + case 0: + /* + * Partial read + */ + break; + case -1: + qdevice_heuristics_worker_log_printf(instance, LOG_ERR, + "Lost connection with main qdevice process"); + ret = -1; + break; + case -2: + qdevice_heuristics_worker_log_printf(instance, LOG_ERR, + "Heuristics sent too long command line"); + ret = -1; + break; + case -3: + qdevice_heuristics_worker_log_printf(instance, LOG_ERR, + "Unhandled error when reading from heuristics command in fd"); + ret = -1; + break; + case 1: + /* + * At least one log line received + */ + ret = qdevice_heuristics_worker_cmd_process(instance); + break; + } + + return (ret); +} + +int +qdevice_heuristics_worker_cmd_write_exec_result(struct qdevice_heuristics_worker_instance *instance, + uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result) +{ + if (dynar_str_cpy(&instance->cmd_out_buffer, + QDEVICE_HEURISTICS_CMD_STR_EXEC_RESULT_ADD_SPACE) != -1 && + dynar_str_catf(&instance->cmd_out_buffer, "%"PRIu32" %u\n", seq_number, + (int)exec_result) != -1) { + (void)qdevice_heuristics_io_blocking_write(QDEVICE_HEURISTICS_WORKER_CMD_OUT_FD, + dynar_data(&instance->cmd_out_buffer), dynar_size(&instance->cmd_out_buffer)); + } else { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "Can't alloc memory for exec result"); + + return (-1); + } + + return (0); +} diff --git a/qdevices/qdevice-heuristics-worker-cmd.h b/qdevices/qdevice-heuristics-worker-cmd.h new file mode 100644 index 0000000..0e05bd8 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics-worker-instance.h b/qdevices/qdevice-heuristics-worker-instance.h new file mode 100644 index 0000000..7949264 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics-worker-log.c b/qdevices/qdevice-heuristics-worker-log.c new file mode 100644 index 0000000..997ae97 --- /dev/null +++ b/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 +#include +#include + +#include "dynar.h" +#include "dynar-str.h" +#include "qdevice-heuristics-io.h" +#include "qdevice-heuristics-worker.h" +#include "qdevice-heuristics-worker-log.h" + +static int +qdevice_heuristics_worker_log_remove_newlines(struct dynar *str) +{ + size_t len; + size_t zi; + char *buf; + + len = dynar_size(str); + buf = dynar_data(str); + + for (zi = 0; zi < len ; zi++) { + if (buf[zi] == '\n' || buf[zi] == '\r') { + buf[zi] = ' '; + } + } + + return (0); +} + +void +qdevice_heuristics_worker_log_printf(struct qdevice_heuristics_worker_instance *instance, + int priority, const char *format, ...) +{ + va_list ap; + va_list ap_copy; + + va_start(ap, format); + + if (dynar_str_cpy(&instance->log_out_buffer, "") != -1 && + dynar_str_catf(&instance->log_out_buffer, "%u ", priority) != -1 && + dynar_str_vcatf(&instance->log_out_buffer, format, ap) != -1 && + qdevice_heuristics_worker_log_remove_newlines(&instance->log_out_buffer) != -1 && + dynar_str_cat(&instance->log_out_buffer, "\n") != -1) { + /* + * It was possible to log everything + */ + (void)qdevice_heuristics_io_blocking_write(QDEVICE_HEURISTICS_WORKER_LOG_OUT_FD, + dynar_data(&instance->log_out_buffer), dynar_size(&instance->log_out_buffer)); + } else { + /* + * As a fallback try to log to syslog + */ + va_copy(ap_copy, ap); + openlog("qdevice_heuristics_worker", LOG_PID, LOG_DAEMON); + syslog(LOG_ERR, "Log entry sent to syslog instead of parent process"); + vsyslog(priority, format, ap_copy); + closelog(); + va_end(ap_copy); + } + + va_end(ap); +} diff --git a/qdevices/qdevice-heuristics-worker-log.h b/qdevices/qdevice-heuristics-worker-log.h new file mode 100644 index 0000000..ab76b54 --- /dev/null +++ b/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 + +#include "qdevice-heuristics-worker-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern void qdevice_heuristics_worker_log_printf( + struct qdevice_heuristics_worker_instance *instance, + int priority, const char *format, ...) __attribute__((__format__(__printf__, 3, 0))); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_HEURISTICS_WORKER_LOG_H_ */ diff --git a/qdevices/qdevice-heuristics-worker.c b/qdevices/qdevice-heuristics-worker.c new file mode 100644 index 0000000..15f6943 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +#include "dynar-str.h" +#include "qdevice-config.h" +#include "qdevice-heuristics-io.h" +#include "qdevice-heuristics-worker.h" +#include "qdevice-heuristics-worker-instance.h" +#include "qdevice-heuristics-worker-log.h" +#include "qdevice-heuristics-worker-cmd.h" + +/* + * Declarations + */ +static int qdevice_heuristics_worker_kill_list_timer_callback(void *data1, + void *data2); + +static void qdevice_heuristics_worker_process_list_notify( + enum process_list_notify_reason reason, const struct process_list_entry *entry, + void *user_data); + +static void qdevice_heuristics_worker_signal_handlers_register(void); + + +/* + * Definitions + */ +static void +qdevice_heuristics_worker_process_list_notify(enum process_list_notify_reason reason, + const struct process_list_entry *entry, void *user_data) +{ + struct qdevice_heuristics_worker_instance *instance; + + instance = (struct qdevice_heuristics_worker_instance *)user_data; + + switch (reason) { + case PROCESS_LIST_NOTIFY_REASON_EXECUTED: + qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG, + "process %s executed", entry->name); + break; + case PROCESS_LIST_NOTIFY_REASON_FINISHED: + if (!WIFEXITED(entry->exit_status) || WEXITSTATUS(entry->exit_status) != 0) { + if (WIFEXITED(entry->exit_status)) { + qdevice_heuristics_worker_log_printf(instance, LOG_WARNING, + "process %s finished with status %d", entry->name, + WEXITSTATUS(entry->exit_status)); + } else if (WIFSIGNALED(entry->exit_status)) { + qdevice_heuristics_worker_log_printf(instance, LOG_WARNING, + "process %s killed by signal %d", entry->name, + WTERMSIG(entry->exit_status)); + } else { + qdevice_heuristics_worker_log_printf(instance, LOG_WARNING, + "process %s finished with non zero status", entry->name); + } + } else { + qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG, + "process %s sucesfully finished", entry->name); + } + break; + } +} + +static void +qdevice_heuristics_worker_signal_handlers_register(void) +{ + struct sigaction act; + + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGCHLD, &act, NULL); + + act.sa_handler = SIG_IGN; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGPIPE, &act, NULL); + + act.sa_handler = SIG_IGN; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGINT, &act, NULL); +} + +static int +qdevice_heuristics_worker_kill_list_timer_callback(void *data1, void *data2) +{ + struct qdevice_heuristics_worker_instance *instance; + size_t kill_list_size; + + instance = (struct qdevice_heuristics_worker_instance *)data1; + + if (process_list_process_kill_list(&instance->main_process_list) != 0) { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_kill_list_timer_callback: process kill list failed. " + "Shutting down worker"); + + instance->schedule_exit = 1; + return (0); + } + + kill_list_size = process_list_get_kill_list_items(&instance->main_process_list); + + if (kill_list_size > 0) { + qdevice_heuristics_worker_log_printf(instance, LOG_DEBUG, + "Still waiting for %zu processes exit", kill_list_size); + } + + /* + * Schedule this timer again + */ + return (-1); +} + +int +qdevice_heuristics_worker_exec_timeout_timer_callback(void *data1, void *data2) +{ + struct qdevice_heuristics_worker_instance *instance; + + instance = (struct qdevice_heuristics_worker_instance *)data1; + + qdevice_heuristics_worker_log_printf(instance, LOG_WARNING, + "Not all heuristics execs finished on time"); + + process_list_move_active_entries_to_kill_list(&instance->main_process_list); + + instance->exec_timeout_timer = NULL; + + if (qdevice_heuristics_worker_cmd_write_exec_result(instance, instance->last_exec_seq_number, + QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) { + instance->schedule_exit = 1; + + return (0); + } + + return (0); +} + +static int +qdevice_heuristics_worker_poll(struct qdevice_heuristics_worker_instance *instance) +{ + int poll_res; + struct pollfd poll_input_fd; + uint32_t timeout; + int plist_summary; + + /* + * Poll command input + */ + poll_input_fd.fd = QDEVICE_HEURISTICS_WORKER_CMD_IN_FD; + poll_input_fd.events = POLLIN; + poll_input_fd.revents = 0; + + timeout = timer_list_time_to_expire_ms(&instance->main_timer_list); + if (timeout > QDEVICE_MIN_HEURISTICS_TIMEOUT) { + timeout = QDEVICE_MIN_HEURISTICS_TIMEOUT; + } + + if ((poll_res = poll(&poll_input_fd, 1, timeout)) >= 0) { + if (poll_input_fd.revents & POLLIN) { + /* + * POLLIN + */ + if (qdevice_heuristics_worker_cmd_read_from_pipe(instance) != 0) { + return (-1); + } + } + + if (poll_input_fd.revents & POLLOUT) { + /* + * Pollout shouldn't happen (critical error) + */ + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_poll: POLLOUT set. Shutting down worker"); + + return (-1); + } + + if (poll_input_fd.revents & (POLLERR|POLLHUP|POLLNVAL) && + !(poll_input_fd.revents & (POLLIN|POLLOUT))) { + /* + * Qdevice closed pipe + */ + + return (-1); + } + } + + if (process_list_waitpid(&instance->main_process_list) != 0) { + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_poll: Waitpid failed. Shutting down worker"); + + return (-1); + } + + if (instance->exec_timeout_timer != NULL) { + plist_summary = process_list_get_summary_result_short(&instance->main_process_list); + + switch (plist_summary) { + case -1: + /* + * Processes not finished -> continue + */ + break; + case 0: + /* + * All processes finished sucesfully + */ + if (qdevice_heuristics_worker_cmd_write_exec_result(instance, + instance->last_exec_seq_number, QDEVICE_HEURISTICS_EXEC_RESULT_PASS) != 0) { + return (-1); + } + + process_list_move_active_entries_to_kill_list(&instance->main_process_list); + + timer_list_delete(&instance->main_timer_list, instance->exec_timeout_timer); + instance->exec_timeout_timer = NULL; + + break; + case 1: + /* + * Some processes failed + */ + if (qdevice_heuristics_worker_cmd_write_exec_result(instance, + instance->last_exec_seq_number, QDEVICE_HEURISTICS_EXEC_RESULT_FAIL) != 0) { + return (-1); + } + + process_list_move_active_entries_to_kill_list(&instance->main_process_list); + + timer_list_delete(&instance->main_timer_list, instance->exec_timeout_timer); + instance->exec_timeout_timer = NULL; + break; + default: + qdevice_heuristics_worker_log_printf(instance, LOG_CRIT, + "qdevice_heuristics_worker_poll: Unhandled " + "process_list_get_summary_result. Shutting down worker"); + + return (-1); + break; + } + } + + timer_list_expire(&instance->main_timer_list); + + if (instance->schedule_exit) { + return (-1); + } + + return (0); +} + +void +qdevice_heuristics_worker_start(size_t ipc_max_send_receive_size, int use_execvp, + size_t max_processes, uint32_t kill_list_interval) +{ + struct qdevice_heuristics_worker_instance instance; + + memset(&instance, 0, sizeof(instance)); + + instance.schedule_exit = 0; + + dynar_init(&instance.cmd_in_buffer, ipc_max_send_receive_size); + dynar_init(&instance.cmd_out_buffer, ipc_max_send_receive_size); + dynar_init(&instance.log_out_buffer, ipc_max_send_receive_size); + + process_list_init(&instance.main_process_list, max_processes, use_execvp, + qdevice_heuristics_worker_process_list_notify, (void *)&instance); + + timer_list_init(&instance.main_timer_list); + instance.kill_list_timer = timer_list_add(&instance.main_timer_list, + kill_list_interval, qdevice_heuristics_worker_kill_list_timer_callback, + (void *)&instance, NULL); + + if (instance.kill_list_timer == NULL) { + qdevice_heuristics_worker_log_printf(&instance, LOG_CRIT, + "Can't create kill list timer"); + return ; + } + + instance.exec_timeout_timer = NULL; + + qdevice_heuristics_exec_list_init(&instance.exec_list); + + qdevice_heuristics_worker_signal_handlers_register(); + + qdevice_heuristics_worker_log_printf(&instance, LOG_DEBUG, "Heuristic worker initialized"); + + while (qdevice_heuristics_worker_poll(&instance) == 0) { + } + + qdevice_heuristics_worker_log_printf(&instance, LOG_DEBUG, "Heuristic worker shutdown " + "requested"); + + qdevice_heuristics_exec_list_free(&instance.exec_list); + + timer_list_free(&instance.main_timer_list); + + qdevice_heuristics_worker_log_printf(&instance, LOG_DEBUG, + "Waiting for all processes to exit"); + + if (process_list_killall(&instance.main_process_list, kill_list_interval) != 0) { + qdevice_heuristics_worker_log_printf(&instance, LOG_WARNING, + "Not all process exited"); + } + + process_list_free(&instance.main_process_list); + + dynar_destroy(&instance.cmd_in_buffer); + dynar_destroy(&instance.cmd_out_buffer); + dynar_destroy(&instance.log_out_buffer); +} diff --git a/qdevices/qdevice-heuristics-worker.h b/qdevices/qdevice-heuristics-worker.h new file mode 100644 index 0000000..100bb97 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-heuristics.c b/qdevices/qdevice-heuristics.c new file mode 100644 index 0000000..6960329 --- /dev/null +++ b/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 +#include + +#include +#include +#include +#include +#include + +#include "qdevice-log.h" +#include "qdevice-heuristics.h" +#include "qdevice-heuristics-cmd.h" +#include "qdevice-heuristics-worker.h" +#include "qdevice-heuristics-io.h" +#include "qdevice-votequorum.h" +#include "utils.h" + +#define QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS 5 + +void +qdevice_heuristics_init(struct qdevice_heuristics_instance *instance, + struct qdevice_advanced_settings *advanced_settings) +{ + int pipe_cmd_in[2], pipe_cmd_out[2], pipe_log_out[2]; + pid_t pid; + + if (pipe(pipe_cmd_in) != 0) { + err(1, "Can't create command input pipe"); + } + + if (pipe(pipe_cmd_out) != 0) { + err(1, "Can't create command output pipe"); + } + + if (pipe(pipe_log_out) != 0) { + err(1, "Can't create logging output pipe"); + } + + pid = fork(); + if (pid == -1) { + err(1, "Can't create child process"); + } else if (pid == 0) { + /* + * Child + */ + (void)setsid(); + if (dup2(pipe_cmd_in[0], 0) == -1) { + err(1, "Can't dup2 command input pipe"); + } + close(pipe_cmd_in[1]); + close(pipe_cmd_in[0]); + if (utils_fd_set_non_blocking(0) == -1) { + err(1, "Can't set non blocking flag on command input pipe"); + } + + if (dup2(pipe_cmd_out[1], 1) == -1) { + err(1, "Can't dup2 command output pipe"); + } + close(pipe_cmd_out[0]); + close(pipe_cmd_out[1]); + + if (dup2(pipe_log_out[1], 2) == -1) { + err(1, "Can't dup2 logging output pipe"); + } + close(pipe_log_out[0]); + close(pipe_log_out[1]); + + qdevice_heuristics_worker_start(advanced_settings->heuristics_ipc_max_send_receive_size, + advanced_settings->heuristics_use_execvp, advanced_settings->heuristics_max_processes, + advanced_settings->heuristics_kill_list_interval); + + qdevice_advanced_settings_destroy(advanced_settings); + + exit(0); + } else { + close(pipe_cmd_in[0]); + close(pipe_cmd_out[1]); + close(pipe_log_out[1]); + + qdevice_heuristics_instance_init(instance); + + instance->pipe_cmd_send = pipe_cmd_in[1]; + if (utils_fd_set_non_blocking(instance->pipe_cmd_send) == -1) { + err(1, "Can't set non blocking flag on command input pipe"); + } + instance->pipe_cmd_recv = pipe_cmd_out[0]; + if (utils_fd_set_non_blocking(instance->pipe_cmd_recv) == -1) { + err(1, "Can't set non blocking flag on command output pipe"); + } + instance->pipe_log_recv = pipe_log_out[0]; + if (utils_fd_set_non_blocking(instance->pipe_cmd_recv) == -1) { + err(1, "Can't set non blocking flag on logging output pipe"); + } + instance->worker_pid = pid; + + send_buffer_list_init(&instance->cmd_out_buffer_list, + advanced_settings->heuristics_ipc_max_send_buffers, + advanced_settings->heuristics_ipc_max_send_receive_size); + dynar_init(&instance->log_in_buffer, + advanced_settings->heuristics_ipc_max_send_receive_size); + dynar_init(&instance->cmd_in_buffer, + advanced_settings->heuristics_ipc_max_send_receive_size); + } +} + +void +qdevice_heuristics_destroy(struct qdevice_heuristics_instance *instance) +{ + int status; + + /* + * Close of pipe_cmd_send result is correct and almost instant exit of worker + */ + close(instance->pipe_cmd_send); + + qdevice_log(LOG_DEBUG, "Waiting for heuristics worker to finish"); + if (waitpid(instance->worker_pid, &status, 0) == -1) { + qdevice_log_err(LOG_ERR, "Heuristics worker waitpid failed"); + } else { + /* + * Log what left in worker log buffer. Errors can be ignored + */ + (void)qdevice_heuristics_log_read_from_pipe(instance); + } + + close(instance->pipe_cmd_recv); + close(instance->pipe_log_recv); + + dynar_destroy(&instance->log_in_buffer); + dynar_destroy(&instance->cmd_in_buffer); + send_buffer_list_free(&instance->cmd_out_buffer_list); + + qdevice_heuristics_instance_destroy(instance); +} + +int +qdevice_heuristics_exec(struct qdevice_heuristics_instance *instance, int sync_in_progress) +{ + uint32_t timeout; + + instance->expected_reply_seq_number++; + instance->waiting_for_result = 1; + + if (sync_in_progress) { + timeout = instance->sync_timeout; + } else { + timeout = instance->timeout; + } + + return (qdevice_heuristics_cmd_write_exec(instance, timeout, + instance->expected_reply_seq_number)); +} + +int +qdevice_heuristics_waiting_for_result(const struct qdevice_heuristics_instance *instance) +{ + + return (instance->waiting_for_result); +} + +int +qdevice_heuristics_change_exec_list(struct qdevice_heuristics_instance *instance, + const struct qdevice_heuristics_exec_list *new_exec_list, int sync_in_progress) +{ + + if (qdevice_heuristics_cmd_write_exec_list(instance, new_exec_list) != 0) { + return (-1); + } + + qdevice_heuristics_exec_list_free(&instance->exec_list); + + if (new_exec_list != NULL) { + if (qdevice_heuristics_exec_list_clone(&instance->exec_list, new_exec_list) != 0) { + qdevice_log(LOG_ERR, "Can't clone exec list"); + + return (-1); + } + } + + if (qdevice_heuristics_waiting_for_result(instance)) { + if (qdevice_heuristics_exec(instance, sync_in_progress) != 0) { + qdevice_log(LOG_ERR, "Can't execute heuristics"); + + return (-1); + } + } + + return (0); +} + + +int +qdevice_heuristics_wait_for_initial_exec_result(struct qdevice_heuristics_instance *instance) +{ + struct pollfd pfds[QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS]; + int no_pfds; + int poll_res; + int timeout; + int i; + int case_processed; + int res; + + while (!instance->qdevice_instance_ptr->vq_node_list_initial_heuristics_finished) { + no_pfds = 0; + + assert(no_pfds < QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS); + pfds[no_pfds].fd = instance->pipe_log_recv; + pfds[no_pfds].events = POLLIN; + pfds[no_pfds].revents = 0; + no_pfds++; + + assert(no_pfds < QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS); + pfds[no_pfds].fd = instance->pipe_cmd_recv; + pfds[no_pfds].events = POLLIN; + pfds[no_pfds].revents = 0; + no_pfds++; + + assert(no_pfds < QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS); + pfds[no_pfds].fd = instance->qdevice_instance_ptr->votequorum_poll_fd; + pfds[no_pfds].events = POLLIN; + pfds[no_pfds].revents = 0; + no_pfds++; + + if (!send_buffer_list_empty(&instance->cmd_out_buffer_list)) { + assert(no_pfds < QDEVICE_HEURISTICS_WAIT_FOR_INITIAL_EXEC_RESULT_MAX_PFDS); + pfds[no_pfds].fd = instance->pipe_cmd_send; + pfds[no_pfds].events = POLLOUT; + pfds[no_pfds].revents = 0; + no_pfds++; + } + + /* + * We know this is never larger than QDEVICE_DEFAULT_HEURISTICS_MAX_TIMEOUT * 2 + */ + timeout = (int)instance->sync_timeout * 2; + + poll_res = poll(pfds, no_pfds, timeout); + if (poll_res > 0) { + for (i = 0; i < no_pfds; i++) { + if (pfds[i].revents & POLLIN) { + case_processed = 0; + switch (i) { + case 0: + case_processed = 1; + + res = qdevice_heuristics_log_read_from_pipe(instance); + if (res == -1) { + return (-1); + } + break; + case 1: + case_processed = 1; + res = qdevice_heuristics_cmd_read_from_pipe(instance); + if (res == -1) { + return (-1); + } + break; + case 2: + case_processed = 1; + res = qdevice_votequorum_dispatch(instance->qdevice_instance_ptr); + if (res == -1) { + return (-1); + } + case 3: + /* + * Read on heuristics cmd send fs shouldn't happen + */ + break; + } + + if (!case_processed) { + qdevice_log(LOG_CRIT, "Unhandled read on poll descriptor %u", i); + exit(1); + } + } + + if (pfds[i].revents & POLLOUT) { + case_processed = 0; + switch (i) { + case 0: + case 1: + case 2: + /* + * Write on heuristics log, cmd recv or vq shouldn't happen + */ + break; + case 3: + case_processed = 1; + res = qdevice_heuristics_cmd_write(instance); + if (res == -1) { + return (-1); + } + break; + } + + if (!case_processed) { + qdevice_log(LOG_CRIT, "Unhandled write on poll descriptor %u", i); + exit(1); + } + } + + if ((pfds[i].revents & (POLLERR|POLLHUP|POLLNVAL)) && + !(pfds[i].revents & (POLLIN|POLLOUT))) { + switch (i) { + case 0: + case 1: + case 3: + /* + * Closed pipe doesn't mean return of POLLIN. To display + * better log message, we call read log as if POLLIN would + * be set. + */ + res = qdevice_heuristics_log_read_from_pipe(instance); + if (res == -1) { + return (-1); + } + + qdevice_log(LOG_ERR, "POLLERR (%u) on heuristics pipe. Exiting"); + return (-1); + break; + case 2: + qdevice_log(LOG_ERR, "POLLERR (%u) on corosync socket. Exiting"); + return (-1); + break; + } + } + } + } else if (poll_res == 0) { + qdevice_log(LOG_ERR, "Timeout waiting for initial heuristics exec result"); + return (-1); + } else { + qdevice_log_err(LOG_ERR, "Initial heuristics exec result poll failed"); + return (-1); + } + } + + return (0); +} diff --git a/qdevices/qdevice-heuristics.h b/qdevices/qdevice-heuristics.h new file mode 100644 index 0000000..80e3d63 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-instance.c b/qdevices/qdevice-instance.c new file mode 100644 index 0000000..5259328 --- /dev/null +++ b/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); +} diff --git a/qdevices/qdevice-instance.h b/qdevices/qdevice-instance.h new file mode 100644 index 0000000..d137e52 --- /dev/null +++ b/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 + +#include +#include + +#include +#include + +#include "qdevice-advanced-settings.h" +#include "qdevice-heuristics.h" +#include "qdevice-model-type.h" +#include "node-list.h" +#include "unix-socket-ipc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct qdevice_instance { + cmap_handle_t cmap_handle; + int cmap_poll_fd; + int cmap_reload_in_progress; + cmap_track_handle_t cmap_reload_track_handle; + cmap_track_handle_t cmap_nodelist_track_handle; + cmap_track_handle_t cmap_logging_track_handle; + cmap_track_handle_t cmap_heuristics_track_handle; + + votequorum_handle_t votequorum_handle; + int votequorum_poll_fd; + + struct unix_socket_ipc local_ipc; + + enum qdevice_model_type model_type; + + uint32_t node_id; + uint32_t heartbeat_interval; /* Heartbeat interval during normal operation */ + uint32_t sync_heartbeat_interval; /* Heartbeat interval during corosync sync */ + + struct node_list config_node_list; + int config_node_list_version_set; + uint64_t config_node_list_version; + + /* + * Copy of votequorum_quorum_notify_fn callback paramters. + * Set after model callback is called. + */ + uint32_t vq_quorum_quorate; + uint32_t vq_quorum_node_list_entries; + votequorum_node_t *vq_quorum_node_list; + + /* + * Copy of current votequorum_nodelist_notify_fn callback parameters. + * Set after model callback qdevice_votequorum_node_list_notify_callback is called. + */ + uint8_t vq_node_list_initial_ring_id_set; + votequorum_ring_id_t vq_node_list_ring_id; + uint32_t vq_node_list_entries; + uint32_t *vq_node_list; + uint8_t vq_node_list_initial_heuristics_finished; + enum qdevice_heuristics_exec_result vq_node_list_heuristics_result; + + /* + * Copy of current votequorum_nodelist_notify_fn callback ring id + * It's set before any callback is called and used for qdevice_votequorum_poll + */ + votequorum_ring_id_t vq_poll_ring_id; + + /* + * Copy of votequorum_expectedvotes_notify_fn callback parameters. + * Set after model callback is called. + */ + uint32_t vq_expected_votes; + + time_t vq_last_poll; + int vq_last_poll_cast_vote; + + void *model_data; + + const struct qdevice_advanced_settings *advanced_settings; + + int sync_in_progress; + + struct qdevice_heuristics_instance heuristics_instance; +}; + +extern int qdevice_instance_init(struct qdevice_instance *instance, + const struct qdevice_advanced_settings *advanced_settings); + +extern int qdevice_instance_destroy(struct qdevice_instance *instance); + +extern int qdevice_instance_configure_from_cmap(struct qdevice_instance *instance); + +extern int qdevice_instance_configure_from_cmap_heuristics(struct qdevice_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_INSTANCE_H_ */ diff --git a/qdevices/qdevice-ipc-cmd.c b/qdevices/qdevice-ipc-cmd.c new file mode 100644 index 0000000..aa66157 --- /dev/null +++ b/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); +} diff --git a/qdevices/qdevice-ipc-cmd.h b/qdevices/qdevice-ipc-cmd.h new file mode 100644 index 0000000..cfda6e7 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-ipc.c b/qdevices/qdevice-ipc.c new file mode 100644 index 0000000..73aba3d --- /dev/null +++ b/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; + } +} diff --git a/qdevices/qdevice-ipc.h b/qdevices/qdevice-ipc.h new file mode 100644 index 0000000..8185799 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-log-debug.c b/qdevices/qdevice-log-debug.c new file mode 100644 index 0000000..4886ca3 --- /dev/null +++ b/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++; + } +} diff --git a/qdevices/qdevice-log-debug.h b/qdevices/qdevice-log-debug.h new file mode 100644 index 0000000..23469ba --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-log.c b/qdevices/qdevice-log.c new file mode 100644 index 0000000..44fbe58 --- /dev/null +++ b/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(); +} diff --git a/qdevices/qdevice-log.h b/qdevices/qdevice-log.h new file mode 100644 index 0000000..f167424 --- /dev/null +++ b/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 +#include +#include + +#include "qdevice-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define qdevice_log qb_log +#define qdevice_log_nss(priority, str) qdevice_log(priority, "%s (%d): %s", \ + str, PR_GetError(), PR_ErrorToString(PR_GetError(), PR_LANGUAGE_I_DEFAULT)); + +#define qdevice_log_err(priority, str) qdevice_log(priority, "%s (%d): %s", \ + str, errno, strerror(errno)); + +extern void qdevice_log_init(struct qdevice_instance *instance, int force_debug); + +extern void qdevice_log_configure(struct qdevice_instance *instance); + +extern void qdevice_log_close(struct qdevice_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_LOG_H_ */ diff --git a/qdevices/qdevice-model-net.c b/qdevices/qdevice-model-net.c new file mode 100644 index 0000000..0bfdb94 --- /dev/null +++ b/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 + +#include "qdevice-model.h" +#include "qdevice-model-net.h" +#include "qdevice-log.h" +#include "qdevice-net-cast-vote-timer.h" +#include "qdevice-net-instance.h" +#include "qdevice-net-ipc-cmd.h" +#include "qdevice-net-algorithm.h" +#include "qdevice-net-heuristics.h" +#include "qdevice-net-poll.h" +#include "qdevice-net-send.h" +#include "qdevice-net-votequorum.h" +#include "qnet-config.h" +#include "nss-sock.h" + +int +qdevice_model_net_init(struct qdevice_instance *instance) +{ + + struct qdevice_net_instance *net_instance; + + qdevice_log(LOG_DEBUG, "Initializing qdevice_net_instance"); + if (qdevice_net_instance_init_from_cmap(instance) != 0) { + return (-1); + } + + net_instance = instance->model_data; + + qdevice_log(LOG_DEBUG, "Registering algorithms"); + if (qdevice_net_algorithm_register_all() != 0) { + return (-1); + } + + qdevice_log(LOG_DEBUG, "Initializing NSS"); + if (nss_sock_init_nss((net_instance->tls_supported != TLV_TLS_UNSUPPORTED ? + instance->advanced_settings->net_nss_db_dir : NULL)) != 0) { + qdevice_log_nss(LOG_ERR, "Can't init nss"); + return (-1); + } + + if (qdevice_net_cast_vote_timer_update(net_instance, TLV_VOTE_ASK_LATER) != 0) { + qdevice_log(LOG_ERR, "Can't update cast vote timer"); + return (-1); + } + + if (qdevice_net_algorithm_init(net_instance) != 0) { + qdevice_log(LOG_ERR, "Algorithm init failed"); + return (-1); + } + + if (qdevice_net_heuristics_init(net_instance) != 0) { + qdevice_log(LOG_ERR, "Can't initialize net heuristics"); + return (-1); + } + + return (0); +} + +int +qdevice_model_net_destroy(struct qdevice_instance *instance) +{ + struct qdevice_net_instance *net_instance; + + net_instance = instance->model_data; + + qdevice_log(LOG_DEBUG, "Destroying algorithm"); + qdevice_net_algorithm_destroy(net_instance); + + qdevice_log(LOG_DEBUG, "Destroying qdevice_net_instance"); + qdevice_net_instance_destroy(net_instance); + + qdevice_log(LOG_DEBUG, "Shutting down NSS"); + SSL_ClearSessionCache(); + + if (NSS_Shutdown() != SECSuccess) { + qdevice_log_nss(LOG_WARNING, "Can't shutdown NSS"); + } + + if (PR_Cleanup() != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Can't shutdown NSPR"); + } + + free(net_instance); + + return (0); +} + +static int +qdevice_model_net_timer_connect_timeout(void *data1, void *data2) +{ + struct qdevice_net_instance *instance; + + instance = (struct qdevice_net_instance *)data1; + + qdevice_log(LOG_ERR, "Connect timeout"); + + instance->schedule_disconnect = 1; + + instance->connect_timer = NULL; + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_CONNECT_TO_THE_SERVER; + + return (0); +} + +static PRIntn +qdevice_model_net_get_af(const struct qdevice_net_instance *instance) +{ + PRIntn af; + + af = PR_AF_UNSPEC; + if (instance->force_ip_version == 4) { + af = PR_AF_INET; + } + + if (instance->force_ip_version == 6) { + af = PR_AF_INET6; + } + + return (af); +} + +int +qdevice_model_net_run(struct qdevice_instance *instance) +{ + struct qdevice_net_instance *net_instance; + int try_connect; + int res; + enum tlv_vote vote; + int delay_before_reconnect; + + net_instance = instance->model_data; + + qdevice_log(LOG_DEBUG, "Executing qdevice-net"); + + try_connect = 1; + while (try_connect) { + net_instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT; + net_instance->socket = NULL; + + net_instance->connect_timer = timer_list_add(&net_instance->main_timer_list, + net_instance->connect_timeout, qdevice_model_net_timer_connect_timeout, + (void *)net_instance, NULL); + + if (net_instance->connect_timer == NULL) { + qdevice_log(LOG_CRIT, "Can't schedule connect timer"); + + try_connect = 0; + break; + } + + qdevice_log(LOG_DEBUG, "Trying connect to qnetd server %s:%u (timeout = %ums)", + net_instance->host_addr, net_instance->host_port, net_instance->connect_timeout); + + res = nss_sock_non_blocking_client_init(net_instance->host_addr, + net_instance->host_port, qdevice_model_net_get_af(net_instance), + &net_instance->non_blocking_client); + if (res == -1) { + qdevice_log_nss(LOG_ERR, "Can't initialize non blocking client connection"); + } + + res = nss_sock_non_blocking_client_try_next(&net_instance->non_blocking_client); + if (res == -1) { + qdevice_log_nss(LOG_ERR, "Can't connect to qnetd host"); + nss_sock_non_blocking_client_destroy(&net_instance->non_blocking_client); + } + + while (qdevice_net_poll(net_instance) == 0) { + }; + + if (net_instance->connect_timer != NULL) { + timer_list_delete(&net_instance->main_timer_list, net_instance->connect_timer); + net_instance->connect_timer = NULL; + } + + if (net_instance->echo_request_timer != NULL) { + timer_list_delete(&net_instance->main_timer_list, net_instance->echo_request_timer); + net_instance->echo_request_timer = NULL; + } + + try_connect = qdevice_net_disconnect_reason_try_reconnect(net_instance->disconnect_reason); + + /* + * Unpause cast vote timer, because if it is paused we cannot remove tracking + */ + qdevice_net_cast_vote_timer_set_paused(net_instance, 0); + + vote = TLV_VOTE_NO_CHANGE; + + if (qdevice_net_algorithm_disconnected(net_instance, + net_instance->disconnect_reason, &try_connect, &vote) != 0) { + qdevice_log(LOG_ERR, "Algorithm returned error, force exit"); + return (-1); + } else { + qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", + tlv_vote_to_str(vote)); + } + + if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { + qdevice_log(LOG_ERR, "qdevice_model_net_run fatal error. " + " Can't update cast vote timer vote"); + } + + if (qdevice_net_disconnect_reason_force_disconnect(net_instance->disconnect_reason)) { + try_connect = 0; + } + + if (net_instance->socket != NULL) { + if (PR_Close(net_instance->socket) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to close connection"); + } + net_instance->socket = NULL; + } + + if (!net_instance->non_blocking_client.destroyed) { + nss_sock_non_blocking_client_destroy(&net_instance->non_blocking_client); + } + + if (net_instance->non_blocking_client.socket != NULL) { + if (PR_Close(net_instance->non_blocking_client.socket) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to close non-blocking client connection"); + } + net_instance->non_blocking_client.socket = NULL; + } + + if (try_connect && + net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) { + /* + * Give qnetd server a little time before reconnect + */ + delay_before_reconnect = random() % + (int)(net_instance->cast_vote_timer_interval * 0.9); + + qdevice_log(LOG_DEBUG, "Sleeping for %u ms before reconnect", + delay_before_reconnect); + (void)poll(NULL, 0, delay_before_reconnect); + } + + qdevice_net_instance_clean(net_instance); + } + + return (0); +} + +/* + * Called when cmap reload (or nodelist) was requested. + * + * nlist is node list + * config_version is valid only if config_version_set != 0 + * + * Should return 0 if processing should continue or -1 to call exit + */ +int +qdevice_model_net_config_node_list_changed(struct qdevice_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version) +{ + struct qdevice_net_instance *net_instance; + int send_node_list; + enum tlv_vote vote; + + net_instance = instance->model_data; + + if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + /* + * Nodelist changed, but connection to qnetd not initiated yet. + */ + send_node_list = 0; + + if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { + vote = TLV_VOTE_NACK; + } else { + vote = TLV_VOTE_NO_CHANGE; + } + } else { + send_node_list = 1; + vote = TLV_VOTE_NO_CHANGE; + } + + if (qdevice_net_algorithm_config_node_list_changed(net_instance, nlist, config_version_set, + config_version, &send_node_list, &vote) != 0) { + qdevice_log(LOG_ERR, "Algorithm returned error, Disconnecting"); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_CONFIG_NODE_LIST_CHANGED_ERR; + net_instance->schedule_disconnect = 1; + + return (0); + } else { + qdevice_log(LOG_DEBUG, "Algorithm decided to %s node list and result vote is %s", + (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote)); + } + + if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { + qdevice_log(LOG_CRIT, "qdevice_model_net_config_node_list_changed fatal error. " + " Can't update cast vote timer vote"); + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + net_instance->schedule_disconnect = 1; + + return (0); + } + + if (send_node_list) { + if (qdevice_net_send_config_node_list(net_instance, nlist, config_version_set, + config_version, 0) != 0) { + net_instance->schedule_disconnect = 1; + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + + return (0); + } + } + + return (0); +} + +/* + * Called when cmap reload (or nodelist) was requested, but it was not possible to + * get node list. + * + * Should return 0 if processing should continue or -1 to call exit + */ +int +qdevice_model_net_get_config_node_list_failed(struct qdevice_instance *instance) +{ + struct qdevice_net_instance *net_instance; + + net_instance = instance->model_data; + + net_instance->schedule_disconnect = 1; + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + + return (0); +} + +int +qdevice_model_net_votequorum_quorum_notify(struct qdevice_instance *instance, + uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[]) +{ + struct qdevice_net_instance *net_instance; + int send_node_list; + enum tlv_vote vote; + + net_instance = instance->model_data; + + if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + /* + * Nodelist changed, but connection to qnetd not initiated yet. + */ + send_node_list = 0; + + if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { + vote = TLV_VOTE_NACK; + } else { + vote = TLV_VOTE_NO_CHANGE; + } + } else { + send_node_list = 1; + vote = TLV_VOTE_NO_CHANGE; + } + + if (qdevice_net_algorithm_votequorum_quorum_notify(net_instance, quorate, + node_list_entries, node_list, &send_node_list, &vote) != 0) { + qdevice_log(LOG_ERR, "Algorithm returned error. Disconnecting."); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_QUORUM_NOTIFY_ERR; + net_instance->schedule_disconnect = 1; + + return (0); + } else { + qdevice_log(LOG_DEBUG, "Algorithm decided to %s list and result vote is %s", + (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote)); + } + + if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { + qdevice_log(LOG_CRIT, "qdevice_model_net_votequorum_quorum_notify fatal error. " + " Can't update cast vote timer vote"); + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + net_instance->schedule_disconnect = 1; + + return (0); + } + + if (send_node_list) { + if (qdevice_net_send_quorum_node_list(net_instance, + (quorate ? TLV_QUORATE_QUORATE : TLV_QUORATE_INQUORATE), + node_list_entries, node_list) != 0) { + /* + * Fatal error -> schedule disconnect + */ + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + net_instance->schedule_disconnect = 1; + + return (0); + } + } + + return (0); +} + +int +qdevice_model_net_votequorum_node_list_heuristics_notify(struct qdevice_instance *instance, + votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[], + enum qdevice_heuristics_exec_result heuristics_exec_result) +{ + struct qdevice_net_instance *net_instance; + struct tlv_ring_id tlv_rid; + enum tlv_vote vote; + enum tlv_heuristics heuristics; + int send_node_list; + + net_instance = instance->model_data; + + qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, &votequorum_ring_id); + heuristics = qdevice_net_heuristics_exec_result_to_tlv(heuristics_exec_result); + + if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + /* + * Nodelist changed, but connection to qnetd not initiated yet. + */ + send_node_list = 0; + + if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { + vote = TLV_VOTE_NACK; + } else { + vote = TLV_VOTE_NO_CHANGE; + } + } else { + send_node_list = 1; + vote = TLV_VOTE_WAIT_FOR_REPLY; + } + + if (qdevice_net_algorithm_votequorum_node_list_heuristics_notify(net_instance, &tlv_rid, + node_list_entries, node_list, &send_node_list, &vote, &heuristics) != 0) { + qdevice_log(LOG_ERR, "Algorithm returned error. Disconnecting."); + + net_instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_HEURISTICS_NOTIFY_ERR; + net_instance->schedule_disconnect = 1; + + return (0); + } else { + qdevice_log(LOG_DEBUG, "Algorithm decided to %s list, result vote is %s and heuristics is %s", + (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote), + tlv_heuristics_to_str(heuristics)); + } + + if (send_node_list) { + if (qdevice_net_send_membership_node_list(net_instance, &tlv_rid, + node_list_entries, node_list, heuristics) != 0) { + /* + * Fatal error -> schedule disconnect + */ + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + net_instance->schedule_disconnect = 1; + + return (0); + } + } + + /* + * Unpause cast vote timer + */ + qdevice_net_cast_vote_timer_set_paused(net_instance, 0); + + if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { + qdevice_log(LOG_CRIT, "qdevice_model_net_votequorum_node_list_notify fatal error " + "Can't update cast vote timer"); + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + net_instance->schedule_disconnect = 1; + + return (0); + } + + net_instance->latest_vq_heuristics_result = heuristics; + net_instance->latest_heuristics_result = heuristics; + + if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) { + return (0); + } + + return (0); +} + +int +qdevice_model_net_votequorum_node_list_notify(struct qdevice_instance *instance, + votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[]) +{ + struct qdevice_net_instance *net_instance; + struct tlv_ring_id tlv_rid; + enum tlv_vote vote; + int pause_cast_vote_timer; + + net_instance = instance->model_data; + + /* + * Stop regular heuristics till qdevice_model_net_votequorum_node_list_heuristics_notify + * is called + */ + if (qdevice_net_heuristics_stop_timer(net_instance) != 0) { + return (0); + } + + pause_cast_vote_timer = 1; + vote = TLV_VOTE_NO_CHANGE; + + if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS && + net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { + /* + * Nodelist changed and vote timer still votes ACK. It's needed to start voting + * NACK. + */ + if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) { + vote = TLV_VOTE_NACK; + } + } + + qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, &votequorum_ring_id); + + if (qdevice_net_algorithm_votequorum_node_list_notify(net_instance, &tlv_rid, + node_list_entries, node_list, &pause_cast_vote_timer, &vote) != 0) { + qdevice_log(LOG_ERR, "Algorithm returned error. Disconnecting."); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_NOTIFY_ERR; + net_instance->schedule_disconnect = 1; + + return (0); + } else { + qdevice_log(LOG_DEBUG, "Algorithm decided to %s cast vote timer and result vote is %s ", + (pause_cast_vote_timer ? "pause" : "not pause"), tlv_vote_to_str(vote)); + } + + if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { + qdevice_log(LOG_CRIT, "qdevice_model_net_votequorum_node_list_notify fatal error " + "Can't update cast vote timer"); + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + net_instance->schedule_disconnect = 1; + + return (0); + } + + qdevice_net_cast_vote_timer_set_paused(net_instance, pause_cast_vote_timer); + + return (0); +} + +int +qdevice_model_net_votequorum_expected_votes_notify(struct qdevice_instance *instance, + uint32_t expected_votes) +{ + struct qdevice_net_instance *net_instance; + enum tlv_vote vote; + + net_instance = instance->model_data; + + qdevice_log(LOG_DEBUG, "qdevice_model_net_votequorum_expected_votes_notify" + " (expected votes old=%"PRIu32" / new=%"PRIu32")", + net_instance->qdevice_instance_ptr->vq_expected_votes, expected_votes); + + vote = TLV_VOTE_NO_CHANGE; + + if (qdevice_net_algorithm_votequorum_expected_votes_notify(net_instance, expected_votes, + &vote) != 0) { + qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting."); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_EXPECTED_VOTES_NOTIFY_ERR; + net_instance->schedule_disconnect = 1; + + return (0); + } else { + qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(vote)); + } + + if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { + qdevice_log(LOG_CRIT, "qdevice_model_net_votequorum_expected_votes_notify fatal error. " + " Can't update cast vote timer vote"); + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + net_instance->schedule_disconnect = 1; + + return (0); + } + + return (0); +} + +int +qdevice_model_net_cmap_changed(struct qdevice_instance *instance, + const struct qdevice_cmap_change_events *events) +{ + struct qdevice_net_instance *net_instance; + enum qdevice_heuristics_mode active_heuristics_mode; + int heuristics_enabled; + + net_instance = instance->model_data; + + if (events->heuristics) { + active_heuristics_mode = instance->heuristics_instance.mode; + heuristics_enabled = (active_heuristics_mode == QDEVICE_HEURISTICS_MODE_ENABLED || + active_heuristics_mode == QDEVICE_HEURISTICS_MODE_SYNC); + + if (net_instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS && + !net_instance->server_supports_heuristics && heuristics_enabled) { + qdevice_log(LOG_ERR, "Heuristics are enabled but not supported by the server"); + + net_instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT; + + net_instance->schedule_disconnect = 1; + + return (0); + } + + if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) { + return (0); + } + } + + return (0); +} + +int +qdevice_model_net_ipc_cmd_status(struct qdevice_instance *instance, + struct dynar *outbuf, int verbose) +{ + struct qdevice_net_instance *net_instance; + + net_instance = instance->model_data; + + if (!qdevice_net_ipc_cmd_status(net_instance, outbuf, verbose)) { + return (-1); + } + + return (0); +} + +static struct qdevice_model qdevice_model_net = { + .name = "net", + .init = qdevice_model_net_init, + .destroy = qdevice_model_net_destroy, + .run = qdevice_model_net_run, + .get_config_node_list_failed = qdevice_model_net_get_config_node_list_failed, + .config_node_list_changed = qdevice_model_net_config_node_list_changed, + .votequorum_quorum_notify = qdevice_model_net_votequorum_quorum_notify, + .votequorum_node_list_notify = qdevice_model_net_votequorum_node_list_notify, + .votequorum_node_list_heuristics_notify = qdevice_model_net_votequorum_node_list_heuristics_notify, + .votequorum_expected_votes_notify = qdevice_model_net_votequorum_expected_votes_notify, + .cmap_changed = qdevice_model_net_cmap_changed, + .ipc_cmd_status = qdevice_model_net_ipc_cmd_status, +}; + +int +qdevice_model_net_register(void) +{ + return (qdevice_model_register(QDEVICE_MODEL_TYPE_NET, &qdevice_model_net)); +} diff --git a/qdevices/qdevice-model-net.h b/qdevices/qdevice-model-net.h new file mode 100644 index 0000000..199205c --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-model-type.h b/qdevices/qdevice-model-type.h new file mode 100644 index 0000000..f133ebd --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-model.c b/qdevices/qdevice-model.c new file mode 100644 index 0000000..1af887f --- /dev/null +++ b/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"); +} diff --git a/qdevices/qdevice-model.h b/qdevices/qdevice-model.h new file mode 100644 index 0000000..6afa5f9 --- /dev/null +++ b/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_ */ diff --git a/qdevices/qdevice-net-algo-2nodelms.c b/qdevices/qdevice-net-algo-2nodelms.c new file mode 100644 index 0000000..e64a702 --- /dev/null +++ b/qdevices/qdevice-net-algo-2nodelms.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include "qdevice-net-algo-2nodelms.h" +#include "qdevice-log.h" +#include "qdevice-net-send.h" +#include "qdevice-net-cast-vote-timer.h" + +int +qdevice_net_algo_2nodelms_init(struct qdevice_net_instance *instance) +{ + + return (0); +} + +int +qdevice_net_algo_2nodelms_connected(struct qdevice_net_instance *instance, enum tlv_heuristics *heuristics, + int *send_config_node_list, int *send_membership_node_list, int *send_quorum_node_list, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_2nodelms_config_node_list_changed(struct qdevice_net_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version, + int *send_node_list, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_2nodelms_votequorum_node_list_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *pause_cast_vote_timer, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_2nodelms_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics) +{ + + return (0); +} + +int +qdevice_net_algo_2nodelms_votequorum_quorum_notify(struct qdevice_net_instance *instance, + uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list, + enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_2nodelms_votequorum_expected_votes_notify(struct qdevice_net_instance *instance, + uint32_t expected_votes, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_2nodelms_config_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_2nodelms_membership_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_2nodelms_quorum_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_2nodelms_ask_for_vote_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_2nodelms_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_2nodelms_echo_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int is_expected_seq_number) +{ + + return (is_expected_seq_number ? 0 : -1); +} + +int +qdevice_net_algo_2nodelms_echo_reply_not_received(struct qdevice_net_instance *instance) +{ + + return (-1); +} + +int +qdevice_net_algo_2nodelms_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_2nodelms_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_heuristics heuristics, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_2nodelms_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote) +{ + + return (0); +} + +void +qdevice_net_algo_2nodelms_destroy(struct qdevice_net_instance *instance) +{ + +} + +static struct qdevice_net_algorithm qdevice_net_algo_2nodelms = { + .init = qdevice_net_algo_2nodelms_init, + .connected = qdevice_net_algo_2nodelms_connected, + .config_node_list_changed = qdevice_net_algo_2nodelms_config_node_list_changed, + .votequorum_node_list_notify = qdevice_net_algo_2nodelms_votequorum_node_list_notify, + .votequorum_node_list_heuristics_notify = qdevice_net_algo_2nodelms_votequorum_node_list_heuristics_notify, + .votequorum_quorum_notify = qdevice_net_algo_2nodelms_votequorum_quorum_notify, + .votequorum_expected_votes_notify = qdevice_net_algo_2nodelms_votequorum_expected_votes_notify, + .config_node_list_reply_received = qdevice_net_algo_2nodelms_config_node_list_reply_received, + .membership_node_list_reply_received = qdevice_net_algo_2nodelms_membership_node_list_reply_received, + .quorum_node_list_reply_received = qdevice_net_algo_2nodelms_quorum_node_list_reply_received, + .ask_for_vote_reply_received = qdevice_net_algo_2nodelms_ask_for_vote_reply_received, + .vote_info_received = qdevice_net_algo_2nodelms_vote_info_received, + .echo_reply_received = qdevice_net_algo_2nodelms_echo_reply_received, + .echo_reply_not_received = qdevice_net_algo_2nodelms_echo_reply_not_received, + .heuristics_change = qdevice_net_algo_2nodelms_heuristics_change, + .heuristics_change_reply_received = qdevice_net_algo_2nodelms_heuristics_change_reply_received, + .disconnected = qdevice_net_algo_2nodelms_disconnected, + .destroy = qdevice_net_algo_2nodelms_destroy, +}; + +int +qdevice_net_algo_2nodelms_register(void) +{ + return (qdevice_net_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_2NODELMS, &qdevice_net_algo_2nodelms)); +} diff --git a/qdevices/qdevice-net-algo-2nodelms.h b/qdevices/qdevice-net-algo-2nodelms.h new file mode 100644 index 0000000..c76e606 --- /dev/null +++ b/qdevices/qdevice-net-algo-2nodelms.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_ALGO_2NODELMS_H_ +#define _QDEVICE_NET_ALGO_2NODELMS_H_ + +#include "qdevice-net-algorithm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_algo_2nodelms_init(struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_2nodelms_connected(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list, + int *send_quorum_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_config_node_list_changed( + struct qdevice_net_instance *instance, const struct node_list *nlist, + int config_version_set, uint64_t config_version, int *send_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_votequorum_node_list_notify( + struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], int *pause_cast_vote_timer, + enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_votequorum_node_list_heuristics_notify( + struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], int *send_node_list, enum tlv_vote *vote, + enum tlv_heuristics *heuristics); + +extern int qdevice_net_algo_2nodelms_votequorum_quorum_notify( + struct qdevice_net_instance *instance, uint32_t quorate, uint32_t node_list_entries, + votequorum_node_t node_list[], int *send_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_votequorum_expected_votes_notify( + struct qdevice_net_instance *instance, uint32_t expected_votes, enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_config_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, int initial, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_membership_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, const struct tlv_ring_id *ring_id, + int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_quorum_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_ask_for_vote_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_echo_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, int is_expected_seq_number); + +extern int qdevice_net_algo_2nodelms_echo_reply_not_received( + struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_2nodelms_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics, + enum tlv_vote *vote); + +extern int qdevice_net_algo_2nodelms_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote); + +extern void qdevice_net_algo_2nodelms_destroy(struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_2nodelms_register(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_ALGO_2NODELMS_H_ */ diff --git a/qdevices/qdevice-net-algo-ffsplit.c b/qdevices/qdevice-net-algo-ffsplit.c new file mode 100644 index 0000000..cc16194 --- /dev/null +++ b/qdevices/qdevice-net-algo-ffsplit.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include "qdevice-net-algo-ffsplit.h" +#include "qdevice-log.h" +#include "qdevice-net-send.h" +#include "qdevice-net-cast-vote-timer.h" +#include "qdevice-votequorum.h" +#include "utils.h" + +static int +check_vqinfo_validity(struct qdevice_net_instance *instance) +{ + struct qdevice_instance *qdev_instance; + struct votequorum_info vq_info; + cs_error_t cs_res; + struct node_list_entry *node; + uint32_t node_id; + + qdev_instance = instance->qdevice_instance_ptr; + + TAILQ_FOREACH(node, &qdev_instance->config_node_list, entries) { + node_id = node->node_id; + + cs_res = votequorum_getinfo(qdev_instance->votequorum_handle, node_id, &vq_info); + + if (cs_res == CS_ERR_NOT_EXIST) { + continue; + } else if (cs_res != CS_OK) { + qdevice_log(LOG_CRIT, "Can't get votequorum information for node " + UTILS_PRI_NODE_ID ". Error %s", node_id, cs_strerror(cs_res)); + + return (-1); + } + + if (vq_info.node_votes != 1) { + qdevice_log(LOG_CRIT, "50:50 split algorithm works only if all nodes have " + "exactly 1 vote. Node " UTILS_PRI_NODE_ID " has %u votes!", + node_id, vq_info.node_votes); + + return (-1); + } + + if (vq_info.qdevice_votes != 1) { + qdevice_log(LOG_CRIT, "50:50 split algorithm works only if qdevice has " + "exactly 1 vote. Node "UTILS_PRI_NODE_ID" has %u votes!", + node_id, vq_info.qdevice_votes); + + return (-1); + } + } + + return (0); +} + +static int +check_cmap_validity(struct qdevice_net_instance *instance) +{ + struct qdevice_instance *qdev_instance; + uint32_t qdevice_votes; + + qdev_instance = instance->qdevice_instance_ptr; + + if (cmap_get_uint32(qdev_instance->cmap_handle, "quorum.device.votes", &qdevice_votes) != CS_OK || + qdevice_votes != 1) { + qdevice_log(LOG_CRIT, "50:50 split algorithm works only if quorum.device.votes" + " configuration key is set to 1!"); + + return (-1); + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_init(struct qdevice_net_instance *instance) +{ + + if (check_cmap_validity(instance) != 0 || + check_vqinfo_validity(instance) != 0) { + return (-1); + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_connected(struct qdevice_net_instance *instance, enum tlv_heuristics *heuristics, + int *send_config_node_list, int *send_membership_node_list, int *send_quorum_node_list, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_ffsplit_config_node_list_changed(struct qdevice_net_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version, + int *send_node_list, enum tlv_vote *vote) +{ + + if (check_vqinfo_validity(instance) != 0) { + return (-1); + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_votequorum_node_list_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *pause_cast_vote_timer, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_ffsplit_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics) +{ + + return (0); +} + +int +qdevice_net_algo_ffsplit_votequorum_quorum_notify(struct qdevice_net_instance *instance, + uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list, + enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_ffsplit_votequorum_expected_votes_notify(struct qdevice_net_instance *instance, + uint32_t expected_votes, enum tlv_vote *vote) +{ + + if (check_vqinfo_validity(instance) != 0) { + return (-1); + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_config_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_membership_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_quorum_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_ask_for_vote_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_echo_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int is_expected_seq_number) +{ + + return (is_expected_seq_number ? 0 : -1); +} + +int +qdevice_net_algo_ffsplit_echo_reply_not_received(struct qdevice_net_instance *instance) +{ + + return (-1); +} + +int +qdevice_net_algo_ffsplit_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_ffsplit_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_heuristics heuristics, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_ffsplit_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote) +{ + + /* + * We cannot depend on default behavior (until there is no change -> use old vote). + * This could create two quorate clusters (2:2 -> first half get ACK -> first half + * disconnects from qnetd -> second half get ACK -> two quorate clusters) + */ + *vote = TLV_VOTE_NACK; + + return (0); +} + +void +qdevice_net_algo_ffsplit_destroy(struct qdevice_net_instance *instance) +{ + +} + +static struct qdevice_net_algorithm qdevice_net_algo_ffsplit = { + .init = qdevice_net_algo_ffsplit_init, + .connected = qdevice_net_algo_ffsplit_connected, + .config_node_list_changed = qdevice_net_algo_ffsplit_config_node_list_changed, + .votequorum_node_list_notify = qdevice_net_algo_ffsplit_votequorum_node_list_notify, + .votequorum_node_list_heuristics_notify = qdevice_net_algo_ffsplit_votequorum_node_list_heuristics_notify, + .votequorum_quorum_notify = qdevice_net_algo_ffsplit_votequorum_quorum_notify, + .votequorum_expected_votes_notify = qdevice_net_algo_ffsplit_votequorum_expected_votes_notify, + .config_node_list_reply_received = qdevice_net_algo_ffsplit_config_node_list_reply_received, + .membership_node_list_reply_received = qdevice_net_algo_ffsplit_membership_node_list_reply_received, + .quorum_node_list_reply_received = qdevice_net_algo_ffsplit_quorum_node_list_reply_received, + .ask_for_vote_reply_received = qdevice_net_algo_ffsplit_ask_for_vote_reply_received, + .vote_info_received = qdevice_net_algo_ffsplit_vote_info_received, + .echo_reply_received = qdevice_net_algo_ffsplit_echo_reply_received, + .echo_reply_not_received = qdevice_net_algo_ffsplit_echo_reply_not_received, + .heuristics_change = qdevice_net_algo_ffsplit_heuristics_change, + .heuristics_change_reply_received = qdevice_net_algo_ffsplit_heuristics_change_reply_received, + .disconnected = qdevice_net_algo_ffsplit_disconnected, + .destroy = qdevice_net_algo_ffsplit_destroy, +}; + +int +qdevice_net_algo_ffsplit_register(void) +{ + return (qdevice_net_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_FFSPLIT, &qdevice_net_algo_ffsplit)); +} diff --git a/qdevices/qdevice-net-algo-ffsplit.h b/qdevices/qdevice-net-algo-ffsplit.h new file mode 100644 index 0000000..e47a35c --- /dev/null +++ b/qdevices/qdevice-net-algo-ffsplit.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_ALGO_FFSPLIT_H_ +#define _QDEVICE_NET_ALGO_FFSPLIT_H_ + +#include "qdevice-net-algorithm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_algo_ffsplit_init(struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_ffsplit_connected(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list, + int *send_quorum_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_config_node_list_changed( + struct qdevice_net_instance *instance, const struct node_list *nlist, + int config_version_set, uint64_t config_version, int *send_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_votequorum_node_list_notify( + struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], int *pause_cast_vote_timer, + enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_votequorum_node_list_heuristics_notify( + struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], int *send_node_list, enum tlv_vote *vote, + enum tlv_heuristics *heuristics); + +extern int qdevice_net_algo_ffsplit_votequorum_quorum_notify( + struct qdevice_net_instance *instance, uint32_t quorate, uint32_t node_list_entries, + votequorum_node_t node_list[], int *send_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_votequorum_expected_votes_notify( + struct qdevice_net_instance *instance, uint32_t expected_votes, enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_config_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, int initial, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_membership_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, const struct tlv_ring_id *ring_id, + int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_quorum_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_ask_for_vote_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_echo_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, int is_expected_seq_number); + +extern int qdevice_net_algo_ffsplit_echo_reply_not_received( + struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_ffsplit_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics, + enum tlv_vote *vote); + +extern int qdevice_net_algo_ffsplit_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote); + +extern void qdevice_net_algo_ffsplit_destroy(struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_ffsplit_register(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_ALGO_FFSPLIT_H_ */ diff --git a/qdevices/qdevice-net-algo-lms.c b/qdevices/qdevice-net-algo-lms.c new file mode 100644 index 0000000..eab0c4c --- /dev/null +++ b/qdevices/qdevice-net-algo-lms.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include "qdevice-net-algo-lms.h" +#include "qdevice-log.h" +#include "qdevice-net-send.h" +#include "qdevice-net-cast-vote-timer.h" + +struct algo_lms_instance_data { + uint32_t quorate; + uint8_t have_wfa; + enum tlv_vote vote; +}; + +int +qdevice_net_algo_lms_init(struct qdevice_net_instance *instance) +{ + struct algo_lms_instance_data *data; + int res; + + data = malloc(sizeof(struct algo_lms_instance_data)); + if (!data) { + return (-1); + } + instance->algorithm_data = data; + + data->quorate = 0; + data->vote = TLV_VOTE_ASK_LATER; + res = cmap_get_uint8(instance->qdevice_instance_ptr->cmap_handle, "quorum.wait_for_all", &data->have_wfa); + if (res != CS_OK) { + qdevice_log(LOG_DEBUG, "algo-lms: Can't get WFA res = %d", res); + data->have_wfa = 0; + } + + qdevice_log(LOG_DEBUG, "algo-lms: initialised. WFA = %d", data->have_wfa); + return (0); +} + + +int +qdevice_net_algo_lms_connected(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list, + int *send_quorum_node_list, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_lms_config_node_list_changed(struct qdevice_net_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version, + int *send_node_list, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_lms_votequorum_node_list_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *pause_cast_vote_timer, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_lms_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics) +{ + + return (0); +} + +int +qdevice_net_algo_lms_votequorum_quorum_notify(struct qdevice_net_instance *instance, + uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list, + enum tlv_vote *vote) +{ + struct algo_lms_instance_data *data = instance->algorithm_data; + + data->quorate = quorate; + qdevice_log(LOG_DEBUG, "algo-lms: quorum_notify. quorate = %d", data->quorate); + + return (0); +} + +int +qdevice_net_algo_lms_config_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_lms_membership_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_lms_quorum_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_lms_votequorum_expected_votes_notify(struct qdevice_net_instance *instance, + uint32_t expected_votes, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_lms_ask_for_vote_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_lms_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_lms_echo_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int is_expected_seq_number) +{ + + return (is_expected_seq_number ? 0 : -1); +} + +int +qdevice_net_algo_lms_echo_reply_not_received(struct qdevice_net_instance *instance) +{ + struct algo_lms_instance_data *data = instance->algorithm_data; + + qdevice_log(LOG_DEBUG, "algo-lms: echo_not_recvd. quorate = %d, WFA = %d", data->quorate, data->have_wfa); + + /* qnetd server is disconnected, if we were already quorate AND WFA is enabled + then we can continue to provide our vote. + Otherwise ... no + */ + if (data->quorate && data->have_wfa) { + return (0); + } + + return (-1); +} + +int +qdevice_net_algo_lms_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote) +{ + + return (0); +} + +int +qdevice_net_algo_lms_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_heuristics heuristics, enum tlv_vote *vote) +{ + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +int +qdevice_net_algo_lms_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote) +{ + struct algo_lms_instance_data *data = instance->algorithm_data; + + qdevice_log(LOG_DEBUG, "algo-lms: disconnected. quorate = %d, WFA = %d", data->quorate, data->have_wfa); + qdevice_log(LOG_DEBUG, "algo-lms: disconnected. reason = %d, WFA = %d", disconnect_reason, data->have_wfa); + + if (!data->quorate || !data->have_wfa) { + *vote = TLV_VOTE_NACK; + } + *try_reconnect = 1; + return (0); +} + +void +qdevice_net_algo_lms_destroy(struct qdevice_net_instance *instance) +{ + free(instance->algorithm_data); +} + +static struct qdevice_net_algorithm qdevice_net_algo_lms = { + .init = qdevice_net_algo_lms_init, + .connected = qdevice_net_algo_lms_connected, + .config_node_list_changed = qdevice_net_algo_lms_config_node_list_changed, + .votequorum_node_list_notify = qdevice_net_algo_lms_votequorum_node_list_notify, + .votequorum_node_list_heuristics_notify = qdevice_net_algo_lms_votequorum_node_list_heuristics_notify, + .votequorum_quorum_notify = qdevice_net_algo_lms_votequorum_quorum_notify, + .votequorum_expected_votes_notify = qdevice_net_algo_lms_votequorum_expected_votes_notify, + .config_node_list_reply_received = qdevice_net_algo_lms_config_node_list_reply_received, + .membership_node_list_reply_received = qdevice_net_algo_lms_membership_node_list_reply_received, + .quorum_node_list_reply_received = qdevice_net_algo_lms_quorum_node_list_reply_received, + .ask_for_vote_reply_received = qdevice_net_algo_lms_ask_for_vote_reply_received, + .vote_info_received = qdevice_net_algo_lms_vote_info_received, + .echo_reply_received = qdevice_net_algo_lms_echo_reply_received, + .echo_reply_not_received = qdevice_net_algo_lms_echo_reply_not_received, + .heuristics_change = qdevice_net_algo_lms_heuristics_change, + .heuristics_change_reply_received = qdevice_net_algo_lms_heuristics_change_reply_received, + .disconnected = qdevice_net_algo_lms_disconnected, + .destroy = qdevice_net_algo_lms_destroy, +}; + +int +qdevice_net_algo_lms_register(void) +{ + return (qdevice_net_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_LMS, &qdevice_net_algo_lms)); +} diff --git a/qdevices/qdevice-net-algo-lms.h b/qdevices/qdevice-net-algo-lms.h new file mode 100644 index 0000000..f4bcd7e --- /dev/null +++ b/qdevices/qdevice-net-algo-lms.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_ALGO_LMS_H_ +#define _QDEVICE_NET_ALGO_LMS_H_ + +#include "qdevice-net-algorithm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_algo_lms_init(struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_lms_connected(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list, + int *send_quorum_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_config_node_list_changed( + struct qdevice_net_instance *instance, const struct node_list *nlist, + int config_version_set, uint64_t config_version, int *send_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_votequorum_node_list_notify( + struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], int *pause_cast_vote_timer, + enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_votequorum_node_list_heuristics_notify( + struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], int *send_node_list, enum tlv_vote *vote, + enum tlv_heuristics *heuristics); + +extern int qdevice_net_algo_lms_votequorum_quorum_notify( + struct qdevice_net_instance *instance, uint32_t quorate, uint32_t node_list_entries, + votequorum_node_t node_list[], int *send_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_votequorum_expected_votes_notify( + struct qdevice_net_instance *instance, uint32_t expected_votes, enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_config_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, int initial, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_membership_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, const struct tlv_ring_id *ring_id, + int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_quorum_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_ask_for_vote_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_echo_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, int is_expected_seq_number); + +extern int qdevice_net_algo_lms_echo_reply_not_received( + struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_lms_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics, + enum tlv_vote *vote); + +extern int qdevice_net_algo_lms_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote); + +extern void qdevice_net_algo_lms_destroy(struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_lms_register(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_ALGO_LMS_H_ */ diff --git a/qdevices/qdevice-net-algo-test.c b/qdevices/qdevice-net-algo-test.c new file mode 100644 index 0000000..9955bf5 --- /dev/null +++ b/qdevices/qdevice-net-algo-test.c @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include "qdevice-net-algo-test.h" +#include "qdevice-log.h" +#include "qdevice-net-send.h" +#include "qdevice-net-cast-vote-timer.h" + +/* + * Called after qdevice_net_instance is initialized. Connection to server is not yet + * established. Used mainly for allocating instance->algorithm_data. + * + * Callback should return 0 on success or -1 on failure. + */ +int +qdevice_net_algo_test_init(struct qdevice_net_instance *instance) +{ + + instance->algorithm_data = NULL; + qdevice_log(LOG_INFO, "algo-test: Initialized"); + + return (0); +} + +/* + * Called after qdevice connected to qnetd. + * send_config_node_list, send_membership_node_list and send_quorum_node_list can be set to + * nonzero (default) to make qdevice-net send given lists to qnetd + * vote (default TLV_VOTE_WAIT_FOR_REPLY) can be set to update voting timer + * heuristics is result of pre connect heuristics and can be updated + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + */ +int +qdevice_net_algo_test_connected(struct qdevice_net_instance *instance, enum tlv_heuristics *heuristics, + int *send_config_node_list, int *send_membership_node_list, int *send_quorum_node_list, enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Connected"); + + return (0); +} + +/* + * Called after config node list changed. + * + * Callback can override send_node_list and vote. + * Depending on net_instance->state, they are set acordingly: + * If net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS + * send_node_list = 0 + * if cast_vote_timer_vote != TLV_VOTE_ACK + * vote = TLV_VOTE_NO_CHANGE + * if cast_vote_timer_vote = TLV_VOTE_ACK + * vote = TLV_VOTE_NACK. + * Otherwise send_node_list = 1 and vote = TLV_VOTE_NO_CHANGE + * If send_node_list is set to non zero, node list is sent to qnetd + */ +int +qdevice_net_algo_test_config_node_list_changed(struct qdevice_net_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version, + int *send_node_list, enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Config node list changed"); + + return (0); +} + +/* + * Called after votequorum node list notify is dispatched but heuristics result is not + * yet known (heuristics were just executed). Full list together with result of heuristics + * are received in qdevice_net_algo_test_votequorum_node_list_heuristics_notify. + * This applies also if heuristics are disabled. + * + * pause_cast_vote_timer is preset to 1. If after callback this value is still != 0, + * cast vote timer is paused until heuristics finishes. + * + * vote is by default set to TLV_VOTE_NO_CHANGE with exception of situation when + * net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS and + * cast_vote_timer_vote = TLV_VOTE_ACK + * then vote is set to TLV_VOTE_NACK. This is to allow qnetd disconnection and still vote + * ACK as long as no change happens. + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + */ +int +qdevice_net_algo_test_votequorum_node_list_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *pause_cast_vote_timer, enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Votequorum list notify"); + + return (0); +} + +/* + * Called after votequorum node list notify is dispatched and heuristics result is known. + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + * + * If net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS + * send_node_list = 0 + * if cast_vote_timer_vote != TLV_VOTE_ACK + * vote = TLV_VOTE_NO_CHANGE + * if cast_vote_timer_vote = TLV_VOTE_ACK + * vote = TLV_VOTE_NACK. + * Otherwise send_node_list = 1 and vote = TLV_VOTE_WAIT_FOR_REPLY + * If send_node_list is set to non zero, node list is sent to qnetd + * heuristics results can be used (and overwrite) + */ +int +qdevice_net_algo_test_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics) +{ + + qdevice_log(LOG_INFO, "algo-test: Votequorum heuristics list notify"); + + return (0); +} + +/* + * Called after votequorum quorum notify is dispatched. + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + * + * Callback can override send_node_list and vote. + * Depending on net_instance->state, they are set acordingly: + * If net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS + * send_node_list = 0 + * if cast_vote_timer_vote != TLV_VOTE_ACK + * vote = TLV_VOTE_NO_CHANGE + * if cast_vote_timer_vote = TLV_VOTE_ACK + * vote = TLV_VOTE_NACK. + * Otherwise send_node_list = 1 and vote = TLV_VOTE_NO_CHANGE + * + * If send_node_list is set to non zero, node list is sent to qnetd + */ +int +qdevice_net_algo_test_votequorum_quorum_notify(struct qdevice_net_instance *instance, + uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list, + enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Votequorum quorum notify"); + + return (0); +} +/* + * Called after votequorum expected_votes notify is dispatched. + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + * + * Vote is set to TLV_VOTE_NO_CHANGE + */ +int +qdevice_net_algo_test_votequorum_expected_votes_notify(struct qdevice_net_instance *instance, + uint32_t expected_votes, enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Votequorum expected votes notify"); + + return (0); +} + +/* + * Called when config node list reply is received. Vote is set to value returned by server (and can + * be overwriten by algorithm). ring_id is returned by server. ring_id_is_valid is set to 1 only if + * received ring id matches last sent ring id. If it is 0, then it usually makes sense to not + * update timer. + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + */ +int +qdevice_net_algo_test_config_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Config node list reply"); + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +/* + * Called when membership node list reply (reply for votequorum votequorum_nodelist_notify_fn) + * is received. Vote is set to value returned by server (and can be overwriten by algorithm). + * + * Also if server returned TLV_VOTE_ASK_LATER, it's good idea to create timer (call timer_list_add + * with instance->main_timer_list parameter) and ask for reply (qdevice_net_send_ask_for_vote). + * Another option may be to wait for vote_info message (if server algorithm is configured so). + * + * ring_id and ring_id_is_valid have same meaning as for + * qdevice_net_algo_test_config_node_list_reply_received + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + */ +int +qdevice_net_algo_test_membership_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Membership node list reply"); + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +/* + * Called when quorum node list reply (reply for votequorum votequorum_quorum_notify_fn) + * is received. Vote is set to value returned by server (and can be overwriten by algorithm). + * + * ring_id and ring_id_is_valid have same meaning as for + * qdevice_net_algo_test_config_node_list_reply_received + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + */ +int +qdevice_net_algo_test_quorum_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Quorum node list reply"); + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +/* + * Called when reply for ask for vote message was received. + * Vote is set to value returned by server (and can be overwriten by algorithm). + * + * ring_id and ring_id_is_valid have same meaning as for + * qdevice_net_algo_test_config_node_list_reply_received + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + */ +int +qdevice_net_algo_test_ask_for_vote_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Ask for vote reply received"); + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +/* + * Called when vote info message from server was received. + * Vote is set to value sent by server (and can be overwriten by algorithm). + * + * ring_id and ring_id_is_valid have same meaning as for + * qdevice_net_algo_test_config_node_list_reply_received + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + */ +int +qdevice_net_algo_test_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Vote info received"); + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +/* + * Called when echo reply message was received. + * is_expected_seq_number is set to 1 if received seq_number was equal to last sent echo request. + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + */ +int +qdevice_net_algo_test_echo_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int is_expected_seq_number) +{ + + qdevice_log(LOG_INFO, "algo-test: Echo reply received"); + + return (is_expected_seq_number ? 0 : -1); +} + +/* + * Called when client is about to send echo request but echo reply to previous echo request + * was not yet received. + * + * Callback should return 0 if processing should continue (echo request is not sent but timer is + * scheduled again) otherwise -1 (-> disconnect client). + */ +int +qdevice_net_algo_test_echo_reply_not_received(struct qdevice_net_instance *instance) +{ + + qdevice_log(LOG_INFO, "algo-test: Echo reply not received"); + + return (-1); +} + +/* + * Called when regular heuristics finished and it's result differs from previous heuristics. + * + * heuristics contains result of heuristics + * send_msg distinquish if message should be sent to qnetd. Default value is 0 if qnetd is not + * connected and 1 otherwise + * vote can be set to change cast_vote_timer voting value (default is TLV_VOTE_NO_CHANGE) + * + * It's not possible to set send_msg to 1 and heuristics to TLV_HEURISTICS_UNDEFINED + * + * Callback should return 0 on success, -1 on failure (-> force exit) + */ +int +qdevice_net_algo_test_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Heuristics change"); + + return (0); +} + +/* + * Called when reply for heuristics change was received. + * Vote is set to value returned by server (and can be overwriten by algorithm). + * + * ring_id and ring_id_is_valid have same meaning as for + * qdevice_net_algo_test_config_node_list_reply_received + * + * heuristics is unmodified value sent to server (and set by qdevice_net_algo_test_heuristics_change) + * + * Callback should return 0 on success or -1 on failure (-> disconnect client). + */ +int +qdevice_net_algo_test_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_heuristics heuristics, enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: heuristics change reply received"); + + if (!ring_id_is_valid) { + *vote = TLV_VOTE_NO_CHANGE; + } + + return (0); +} + +/* + * Called when client disconnects from server. + * + * disconnect_reason contains one of QDEVICE_NET_DISCONNECT_REASON_ + * try_reconnect can be set to non zero value if reconnect to server should be tried + * vote (default TLV_VOTE_NO_CHANGE) can be set to update voting timer + * + * Callback should return 0 on success, -1 on failure (-> force exit) + */ +int +qdevice_net_algo_test_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote) +{ + + qdevice_log(LOG_INFO, "algo-test: Disconnected"); + + return (0); +} + +/* + * Called when qdevice-net is going down. + */ +void +qdevice_net_algo_test_destroy(struct qdevice_net_instance *instance) +{ + + qdevice_log(LOG_INFO, "algo-test: Destroy"); +} + +static struct qdevice_net_algorithm qdevice_net_algo_test = { + .init = qdevice_net_algo_test_init, + .connected = qdevice_net_algo_test_connected, + .config_node_list_changed = qdevice_net_algo_test_config_node_list_changed, + .votequorum_node_list_notify = qdevice_net_algo_test_votequorum_node_list_notify, + .votequorum_node_list_heuristics_notify = qdevice_net_algo_test_votequorum_node_list_heuristics_notify, + .votequorum_quorum_notify = qdevice_net_algo_test_votequorum_quorum_notify, + .votequorum_expected_votes_notify = qdevice_net_algo_test_votequorum_expected_votes_notify, + .config_node_list_reply_received = qdevice_net_algo_test_config_node_list_reply_received, + .membership_node_list_reply_received = qdevice_net_algo_test_membership_node_list_reply_received, + .quorum_node_list_reply_received = qdevice_net_algo_test_quorum_node_list_reply_received, + .ask_for_vote_reply_received = qdevice_net_algo_test_ask_for_vote_reply_received, + .vote_info_received = qdevice_net_algo_test_vote_info_received, + .echo_reply_received = qdevice_net_algo_test_echo_reply_received, + .echo_reply_not_received = qdevice_net_algo_test_echo_reply_not_received, + .heuristics_change = qdevice_net_algo_test_heuristics_change, + .heuristics_change_reply_received = qdevice_net_algo_test_heuristics_change_reply_received, + .disconnected = qdevice_net_algo_test_disconnected, + .destroy = qdevice_net_algo_test_destroy, +}; + +int +qdevice_net_algo_test_register(void) +{ + return (qdevice_net_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_TEST, &qdevice_net_algo_test)); +} diff --git a/qdevices/qdevice-net-algo-test.h b/qdevices/qdevice-net-algo-test.h new file mode 100644 index 0000000..ab8af85 --- /dev/null +++ b/qdevices/qdevice-net-algo-test.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_ALGO_TEST_H_ +#define _QDEVICE_NET_ALGO_TEST_H_ + +#include "qdevice-net-algorithm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_algo_test_init(struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_test_connected(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list, + int *send_quorum_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_test_config_node_list_changed( + struct qdevice_net_instance *instance, const struct node_list *nlist, + int config_version_set, uint64_t config_version, int *send_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_test_votequorum_node_list_notify( + struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], int *pause_cast_vote_timer, + enum tlv_vote *vote); + +extern int qdevice_net_algo_test_votequorum_node_list_heuristics_notify( + struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], int *send_node_list, enum tlv_vote *vote, + enum tlv_heuristics *heuristics); + +extern int qdevice_net_algo_test_votequorum_quorum_notify( + struct qdevice_net_instance *instance, uint32_t quorate, uint32_t node_list_entries, + votequorum_node_t node_list[], int *send_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algo_test_votequorum_expected_votes_notify( + struct qdevice_net_instance *instance, uint32_t expected_votes, enum tlv_vote *vote); + +extern int qdevice_net_algo_test_config_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, int initial, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_test_membership_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, const struct tlv_ring_id *ring_id, + int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_test_quorum_node_list_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_test_ask_for_vote_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, + const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algo_test_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + +extern int qdevice_net_algo_test_echo_reply_received( + struct qdevice_net_instance *instance, uint32_t seq_number, int is_expected_seq_number); + +extern int qdevice_net_algo_test_echo_reply_not_received( + struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_test_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote); + +extern int qdevice_net_algo_test_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics, + enum tlv_vote *vote); + +extern int qdevice_net_algo_test_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote); + +extern void qdevice_net_algo_test_destroy(struct qdevice_net_instance *instance); + +extern int qdevice_net_algo_test_register(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_ALGO_TEST_H_ */ diff --git a/qdevices/qdevice-net-algorithm.c b/qdevices/qdevice-net-algorithm.c new file mode 100644 index 0000000..9919383 --- /dev/null +++ b/qdevices/qdevice-net-algorithm.c @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "qnet-config.h" +#include "qdevice-net-algorithm.h" +#include "qdevice-log.h" + +#include "qdevice-net-algo-test.h" +#include "qdevice-net-algo-ffsplit.h" +#include "qdevice-net-algo-2nodelms.h" +#include "qdevice-net-algo-lms.h" + +static struct qdevice_net_algorithm *qdevice_net_algorithm_array[QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE]; + +int +qdevice_net_algorithm_init(struct qdevice_net_instance *instance) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_init unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]->init(instance)); +} + +int +qdevice_net_algorithm_connected(struct qdevice_net_instance *instance, enum tlv_heuristics *heuristics, + int *send_config_node_list, int *send_membership_node_list, int *send_quorum_node_list, enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_connected unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]->connected(instance, + heuristics, send_config_node_list, send_membership_node_list, send_quorum_node_list, vote)); +} + +int +qdevice_net_algorithm_config_node_list_changed(struct qdevice_net_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version, + int *send_node_list, enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_connected unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + config_node_list_changed(instance, nlist, config_version_set, config_version, + send_node_list, vote)); +} + +int +qdevice_net_algorithm_votequorum_node_list_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *pause_cast_vote_timer, enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_votequorum_node_list_notify " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]->votequorum_node_list_notify( + instance, ring_id, node_list_entries, node_list, pause_cast_vote_timer, vote)); +} + +int +qdevice_net_algorithm_votequorum_node_list_heuristics_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_votequorum_node_list_heuristics_notify " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + votequorum_node_list_heuristics_notify( + instance, ring_id, node_list_entries, node_list, send_node_list, vote, heuristics)); +} + +int +qdevice_net_algorithm_votequorum_quorum_notify(struct qdevice_net_instance *instance, + uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list, + enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_votequorum_quorum_notify " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + votequorum_quorum_notify(instance, quorate, node_list_entries, node_list, + send_node_list, vote)); +} + +int +qdevice_net_algorithm_votequorum_expected_votes_notify(struct qdevice_net_instance *instance, + uint32_t expected_votes, enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_votequorum_expected_votes_notify " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + votequorum_expected_votes_notify(instance, expected_votes, vote)); +} + +int +qdevice_net_algorithm_config_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_config_node_list_reply_received " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + config_node_list_reply_received(instance, seq_number, initial, ring_id, + ring_id_is_valid, vote)); +} + +int +qdevice_net_algorithm_membership_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_membership_node_list_reply_received " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + membership_node_list_reply_received(instance, seq_number, ring_id, ring_id_is_valid, + vote)); +} + +int +qdevice_net_algorithm_quorum_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_quorum_node_list_reply_received " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + quorum_node_list_reply_received(instance, seq_number, ring_id, ring_id_is_valid, + vote)); +} + +int +qdevice_net_algorithm_ask_for_vote_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_ask_for_vote_reply_received " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + ask_for_vote_reply_received(instance, seq_number, ring_id, ring_id_is_valid, vote)); +} + +int +qdevice_net_algorithm_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_vote_info_received " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + vote_info_received(instance, seq_number, ring_id, ring_id_is_valid, vote)); +} + +int +qdevice_net_algorithm_echo_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int is_expected_seq_number) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_echo_reply_received " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + echo_reply_received(instance, seq_number, is_expected_seq_number)); +} + +int +qdevice_net_algorithm_echo_reply_not_received(struct qdevice_net_instance *instance) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_echo_reply_not_received " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + echo_reply_not_received(instance)); +} + +int +qdevice_net_algorithm_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_heuristics_change " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + heuristics_change(instance, heuristics, send_msg, vote)); +} + +int +qdevice_net_algorithm_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_heuristics heuristics, enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_heuristics_change_reply_received " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + heuristics_change_reply_received(instance, seq_number, ring_id, ring_id_is_valid, + heuristics, vote)); +} + +int +qdevice_net_algorithm_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_disconnected " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + disconnected(instance, disconnect_reason, try_reconnect, vote)); +} + +void +qdevice_net_algorithm_destroy(struct qdevice_net_instance *instance) +{ + + if (instance->decision_algorithm >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qdevice_net_algorithm_array[instance->decision_algorithm] == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_algorithm_destroy " + "unhandled decision algorithm"); + exit(1); + } + + return (qdevice_net_algorithm_array[instance->decision_algorithm]-> + destroy(instance)); +} + +int +qdevice_net_algorithm_register(enum tlv_decision_algorithm_type algorithm_number, + struct qdevice_net_algorithm *algorithm) +{ + + if (algorithm_number >= QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE) { + return (-1); + } + + if (qdevice_net_algorithm_array[algorithm_number] != NULL) { + return (-1); + } + + qdevice_net_algorithm_array[algorithm_number] = algorithm; + + return (0); +} + +int +qdevice_net_algorithm_register_all(void) +{ + + if (qdevice_net_algo_test_register() != 0) { + qdevice_log(LOG_CRIT, "Failed to register decision algorithm 'test' "); + return (-1); + } + + if (qdevice_net_algo_ffsplit_register() != 0) { + qdevice_log(LOG_CRIT, "Failed to register decision algorithm 'ffsplit' "); + return (-1); + } + + if (qdevice_net_algo_2nodelms_register() != 0) { + qdevice_log(LOG_CRIT, "Failed to register decision algorithm '2nodelms' "); + return (-1); + } + + if (qdevice_net_algo_lms_register() != 0) { + qdevice_log(LOG_CRIT, "Failed to register decision algorithm 'lms' "); + return (-1); + } + + return (0); +} diff --git a/qdevices/qdevice-net-algorithm.h b/qdevices/qdevice-net-algorithm.h new file mode 100644 index 0000000..2ef7e95 --- /dev/null +++ b/qdevices/qdevice-net-algorithm.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_ALGORITHM_H_ +#define _QDEVICE_NET_ALGORITHM_H_ + +#include +#include + +#include "node-list.h" +#include "qdevice-net-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_algorithm_init(struct qdevice_net_instance *instance); + +extern int qdevice_net_algorithm_connected(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list, + int *send_quorum_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algorithm_config_node_list_changed(struct qdevice_net_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version, + int *send_node_list, enum tlv_vote *vote); + +extern int qdevice_net_algorithm_votequorum_node_list_notify(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *pause_cast_vote_timer, enum tlv_vote *vote); + +extern int qdevice_net_algorithm_votequorum_node_list_heuristics_notify( + struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics); + +extern int qdevice_net_algorithm_votequorum_quorum_notify(struct qdevice_net_instance *instance, + uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list, + enum tlv_vote *vote); + +extern int qdevice_net_algorithm_votequorum_expected_votes_notify(struct qdevice_net_instance *instance, + uint32_t expected_votes, enum tlv_vote *vote); + +extern int qdevice_net_algorithm_config_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + +extern int qdevice_net_algorithm_membership_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algorithm_quorum_node_list_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algorithm_ask_for_vote_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_vote *vote); + +extern int qdevice_net_algorithm_vote_info_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + +extern int qdevice_net_algorithm_echo_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, int is_expected_seq_number); + +extern int qdevice_net_algorithm_echo_reply_not_received(struct qdevice_net_instance *instance); + +extern int qdevice_net_algorithm_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote); + +extern int qdevice_net_algorithm_heuristics_change_reply_received(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, enum tlv_heuristics heuristics, + enum tlv_vote *vote); + +extern int qdevice_net_algorithm_disconnected(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, enum tlv_vote *vote); + +extern void qdevice_net_algorithm_destroy(struct qdevice_net_instance *instance); + +struct qdevice_net_algorithm { + int (*init)(struct qdevice_net_instance *instance); + int (*connected)(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_config_node_list, int *send_membership_node_list, + int *send_quorum_node_list, enum tlv_vote *vote); + int (*config_node_list_changed)(struct qdevice_net_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version, + int *send_node_list, enum tlv_vote *vote); + int (*votequorum_node_list_notify)(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *pause_cast_vote_timer, enum tlv_vote *vote); + int (*votequorum_node_list_heuristics_notify)(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, uint32_t node_list_entries, uint32_t node_list[], + int *send_node_list, enum tlv_vote *vote, enum tlv_heuristics *heuristics); + int (*votequorum_quorum_notify)(struct qdevice_net_instance *instance, uint32_t quorate, + uint32_t node_list_entries, votequorum_node_t node_list[], int *send_node_list, + enum tlv_vote *vote); + int (*votequorum_expected_votes_notify)(struct qdevice_net_instance *instance, + uint32_t expected_votes, enum tlv_vote *vote); + int (*config_node_list_reply_received)(struct qdevice_net_instance *instance, + uint32_t seq_number, int initial, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + int (*membership_node_list_reply_received)(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + int (*quorum_node_list_reply_received)(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + int (*ask_for_vote_reply_received)(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + int (*vote_info_received)(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_vote *vote); + int (*echo_reply_received)(struct qdevice_net_instance *instance, + uint32_t seq_number, int is_expected_seq_number); + int (*echo_reply_not_received)(struct qdevice_net_instance *instance); + int (*heuristics_change)(struct qdevice_net_instance *instance, + enum tlv_heuristics *heuristics, int *send_msg, enum tlv_vote *vote); + int (*heuristics_change_reply_received)(struct qdevice_net_instance *instance, + uint32_t seq_number, const struct tlv_ring_id *ring_id, int ring_id_is_valid, + enum tlv_heuristics heuristics, enum tlv_vote *vote); + int (*disconnected)(struct qdevice_net_instance *instance, + enum qdevice_net_disconnect_reason disconnect_reason, int *try_reconnect, + enum tlv_vote *vote); + void (*destroy)(struct qdevice_net_instance *instance); +}; + +extern int qdevice_net_algorithm_register( + enum tlv_decision_algorithm_type algorithm_number, struct qdevice_net_algorithm *algorithm); + +extern int qdevice_net_algorithm_register_all(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_ALGORITHM_H_ */ diff --git a/qdevices/qdevice-net-cast-vote-timer.c b/qdevices/qdevice-net-cast-vote-timer.c new file mode 100644 index 0000000..a95b905 --- /dev/null +++ b/qdevices/qdevice-net-cast-vote-timer.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qnet-config.h" +#include "qdevice-log.h" +#include "qdevice-net-cast-vote-timer.h" +#include "qdevice-votequorum.h" + +static int +qdevice_net_cast_vote_timer_callback(void *data1, void *data2) +{ + struct qdevice_net_instance *instance; + int cast_vote; + int case_processed; + + instance = (struct qdevice_net_instance *)data1; + + if (instance->cast_vote_timer_paused) { + return (-1); + } + + case_processed = 0; + + switch (instance->cast_vote_timer_vote) { + case TLV_VOTE_ACK: + case_processed = 1; + cast_vote = 1; + break; + case TLV_VOTE_NACK: + case_processed = 1; + cast_vote = 0; + break; + case TLV_VOTE_ASK_LATER: + case TLV_VOTE_WAIT_FOR_REPLY: + case TLV_VOTE_NO_CHANGE: + case TLV_VOTE_UNDEFINED: + /* + * Shouldn't happen + */ + break; + /* + * Default is not defined intentionally. Compiler shows warning when + * new tlv_vote is added. + */ + } + + if (!case_processed) { + qdevice_log(LOG_CRIT, "qdevice_net_timer_cast_vote: Unhandled cast_vote_timer_vote %u\n", + instance->cast_vote_timer_vote); + exit(1); + } + + if (qdevice_votequorum_poll(instance->qdevice_instance_ptr, cast_vote) != 0) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + instance->schedule_disconnect = 1; + instance->cast_vote_timer = NULL; + return (0); + } + + /* + * Schedule this function callback again + */ + return (-1); +} + +int +qdevice_net_cast_vote_timer_update(struct qdevice_net_instance *instance, enum tlv_vote vote) +{ + int timer_needs_running; + int case_processed; + + case_processed = 0; + + switch (vote) { + case TLV_VOTE_UNDEFINED: + break; + case TLV_VOTE_ACK: + case TLV_VOTE_NACK: + case_processed = 1; + timer_needs_running = 1; + break; + case TLV_VOTE_WAIT_FOR_REPLY: + case TLV_VOTE_ASK_LATER: + case_processed = 1; + timer_needs_running = 0; + break; + case TLV_VOTE_NO_CHANGE: + case_processed = 1; + return (0); + + break; + /* + * Default is not defined intentionally. Compiler shows warning when + * new tlv_vote is added. + */ + } + + if (!case_processed) { + qdevice_log(LOG_CRIT, "qdevice_net_cast_vote_timer_update_vote: Unhandled vote parameter %u\n", + vote); + exit(1); + } + + instance->cast_vote_timer_vote = vote; + + if (timer_needs_running) { + if (instance->cast_vote_timer == NULL) { + instance->cast_vote_timer = timer_list_add(&instance->main_timer_list, + instance->cast_vote_timer_interval, + qdevice_net_cast_vote_timer_callback, (void *)instance, NULL); + + if (instance->cast_vote_timer == NULL) { + qdevice_log(LOG_ERR, "Can't schedule sending of " + "votequorum poll"); + + return (-1); + } else { + qdevice_log(LOG_DEBUG, "Cast vote timer is now scheduled every " + "%"PRIu32"ms voting %s.", instance->cast_vote_timer_interval, + tlv_vote_to_str(instance->cast_vote_timer_vote)); + } + } else { + qdevice_log(LOG_DEBUG, "Cast vote timer remains scheduled every " + "%"PRIu32"ms voting %s.", instance->cast_vote_timer_interval, + tlv_vote_to_str(instance->cast_vote_timer_vote)); + } + + if (qdevice_net_cast_vote_timer_callback((void *)instance, NULL) != -1) { + return (-1); + } + } else { + if (instance->cast_vote_timer != NULL) { + timer_list_delete(&instance->main_timer_list, instance->cast_vote_timer); + instance->cast_vote_timer = NULL; + qdevice_log(LOG_DEBUG, "Cast vote timer is now stopped."); + } else { + qdevice_log(LOG_DEBUG, "Cast vote timer remains stopped."); + } + } + + return (0); +} + +void +qdevice_net_cast_vote_timer_set_paused(struct qdevice_net_instance *instance, int paused) +{ + + if (paused != instance->cast_vote_timer_paused) { + instance->cast_vote_timer_paused = paused; + + if (instance->cast_vote_timer_paused) { + qdevice_log(LOG_DEBUG, "Cast vote timer is now paused."); + } else { + qdevice_log(LOG_DEBUG, "Cast vote timer is no longer paused."); + } + } +} diff --git a/qdevices/qdevice-net-cast-vote-timer.h b/qdevices/qdevice-net-cast-vote-timer.h new file mode 100644 index 0000000..f7014d1 --- /dev/null +++ b/qdevices/qdevice-net-cast-vote-timer.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_NET_CAST_VOTE_TIMER_H_ +#define _QDEVICE_NET_CAST_VOTE_TIMER_H_ + +#include "qdevice-net-instance.h" +#include "tlv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_cast_vote_timer_update( + struct qdevice_net_instance *instance, enum tlv_vote vote); + +extern void qdevice_net_cast_vote_timer_set_paused( + struct qdevice_net_instance *instance, int paused); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_CAST_VOTE_TIMER_H_ */ diff --git a/qdevices/qdevice-net-disconnect-reason.h b/qdevices/qdevice-net-disconnect-reason.h new file mode 100644 index 0000000..8c86b64 --- /dev/null +++ b/qdevices/qdevice-net-disconnect-reason.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_DISCONNECT_REASON_H_ +#define _QDEVICE_NET_DISCONNECT_REASON_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +enum qdevice_net_disconnect_reason { + /* Undefined reason. If this error appears, it's error in source code */ + QDEVICE_NET_DISCONNECT_REASON_UNDEFINED, + + /* Received known message, but it was not expected */ + QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG, + /* Received unknown message */ + QDEVICE_NET_DISCONNECT_REASON_UNSUPPORTED_MSG, + /* TLS setting of server and client are incompatible */ + QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_TLS, + /* MSG setting of server and client are incompatible */ + QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_MSG_SIZE, + /* Message doesn't contain required option */ + QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING, + + /* Can't allocate send list item or message is too long to fit into send buffer */ + QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER, + /* Impossible to create or update heartbeat sending timer */ + QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_HB_TIMER, + /* Impossible to create or update votequorum poll timer */ + QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER, + /* Impossible to create or update regular heuristics timer */ + QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_HEURISTICS_TIMER, + /* Impossible to exec heuristics */ + QDEVICE_NET_DISCONNECT_REASON_CANT_START_HEURISTICS, + /* Impossible to activate/deactive heuristics result notifier */ + QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER, + /* Impossible to register votequorum callback */ + QDEVICE_NET_DISCONNECT_REASON_CANT_REGISTER_VOTEQUORUM_CALLBACK, + /* Impossible to register cmap callback */ + QDEVICE_NET_DISCONNECT_REASON_CANT_REGISTER_CMAP_CALLBACK, + /* Impossible to start TLS session */ + QDEVICE_NET_DISCONNECT_REASON_CANT_START_TLS, + + /* Received message with error field set to non TLV_REPLY_ERROR_CODE_NO_ERROR value */ + QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ERROR, + /* Received message with error field set to TLV_REPLY_ERROR_CODE_DUPLICATE_NODE_ID value */ + QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_DUPLICATE_NODE_ID_ERROR, + /* Received message with error field set to TLV_REPLY_ERROR_CODE_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES value */ + QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES_ERROR, + /* Received message with error field set to TLV_REPLY_ERROR_CODE_ALGORITHM_DIFFERS_FROM_OTHER_NODES value */ + QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ALGORITHM_DIFFERS_FROM_OTHER_NODES_ERROR, + + /* Server doesn't support client selected decision algorithm */ + QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_ALGORITHM, + /* Server doesn't support client required option */ + QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT, + + /* Can't decode message sent by server */ + QDEVICE_NET_DISCONNECT_REASON_MSG_DECODE_ERROR, + /* Server closed connection */ + QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION, + /* Can't read or store message received from server */ + QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE, + /* Can't send message to server */ + QDEVICE_NET_DISCONNECT_REASON_CANT_SEND_MESSAGE, + + /* Can't dispatch cmap or votequroum. This cannot be overwritten and always means end of qdevice-net */ + QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED, + + /* Local socket closed is reasult of sigint */ + QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED, + + /* It was not possible to establish connection with qnetd */ + QDEVICE_NET_DISCONNECT_REASON_CANT_CONNECT_TO_THE_SERVER, + + QDEVICE_NET_DISCONNECT_REASON_ALGO_CONNECTED_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_CONFIG_NODE_LIST_CHANGED_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_QUORUM_NOTIFY_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_NOTIFY_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_HEURISTICS_NOTIFY_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_EXPECTED_VOTES_NOTIFY_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_NODE_LIST_REPLY_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_ASK_FOR_VOTE_REPLY_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTE_INFO_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_RECEIVED_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_NOT_RECEIVED_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_ERR, + QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_REPLY_ERR, + + QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED, + QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_CANT_SEND_RECEIVE_MSG, +}; + +#define qdevice_net_disconnect_reason_try_reconnect(reason) ( \ + reason == QDEVICE_NET_DISCONNECT_REASON_MSG_DECODE_ERROR || \ + reason == QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION || \ + reason == QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE || \ + reason == QDEVICE_NET_DISCONNECT_REASON_CANT_SEND_MESSAGE || \ + reason == QDEVICE_NET_DISCONNECT_REASON_CANT_CONNECT_TO_THE_SERVER || \ + reason == QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_NOT_RECEIVED_ERR || \ + reason == QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_DUPLICATE_NODE_ID_ERROR || \ + reason == QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES_ERROR || \ + reason == QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ALGORITHM_DIFFERS_FROM_OTHER_NODES_ERROR) + + +#define qdevice_net_disconnect_reason_force_disconnect(reason) ( \ + reason == QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED || \ + reason == QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED || \ + reason == QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER || \ + reason == QDEVICE_NET_DISCONNECT_REASON_CANT_REGISTER_VOTEQUORUM_CALLBACK || \ + reason == QDEVICE_NET_DISCONNECT_REASON_CANT_REGISTER_CMAP_CALLBACK || \ + reason == QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED || \ + reason == QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_CANT_SEND_RECEIVE_MSG) + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_DISCONNECT_REASON_H_ */ diff --git a/qdevices/qdevice-net-echo-request-timer.c b/qdevices/qdevice-net-echo-request-timer.c new file mode 100644 index 0000000..280793e --- /dev/null +++ b/qdevices/qdevice-net-echo-request-timer.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qdevice-net-algorithm.h" +#include "qdevice-net-echo-request-timer.h" +#include "qdevice-net-send.h" +#include "qdevice-log.h" + +static int +qdevice_net_echo_request_timer_callback(void *data1, void *data2) +{ + struct qdevice_net_instance *instance; + + instance = (struct qdevice_net_instance *)data1; + + if (instance->echo_reply_received_msg_seq_num != + instance->echo_request_expected_msg_seq_num) { + qdevice_log(LOG_ERR, "Server didn't send echo reply message on time"); + + if (qdevice_net_algorithm_echo_reply_not_received(instance) != 0) { + qdevice_log(LOG_DEBUG, "Algorithm decided to disconnect"); + + instance->schedule_disconnect = 1; + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_NOT_RECEIVED_ERR; + + instance->echo_request_timer = NULL; + return (0); + } else { + qdevice_log(LOG_DEBUG, "Algorithm decided to continue send heartbeat"); + + return (-1); + } + } + + if (qdevice_net_send_echo_request(instance) == -1) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + + instance->schedule_disconnect = 1; + instance->echo_request_timer = NULL; + return (0); + } + + /* + * Schedule this function callback again + */ + return (-1); +} + +int +qdevice_net_echo_request_timer_schedule(struct qdevice_net_instance *instance) +{ + instance->echo_request_expected_msg_seq_num = 0; + instance->echo_reply_received_msg_seq_num = 0; + + if (instance->echo_request_timer != NULL) { + timer_list_delete(&instance->main_timer_list, instance->echo_request_timer); + instance->echo_request_timer = NULL; + } + + qdevice_log(LOG_DEBUG, "Scheduling send of heartbeat every %"PRIu32"ms", instance->heartbeat_interval); + instance->echo_request_timer = timer_list_add(&instance->main_timer_list, + instance->heartbeat_interval, qdevice_net_echo_request_timer_callback, + (void *)instance, NULL); + + if (instance->echo_request_timer == NULL) { + qdevice_log(LOG_ERR, "Can't schedule regular sending of heartbeat."); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_HB_TIMER; + + return (-1); + } + + return (0); +} diff --git a/qdevices/qdevice-net-echo-request-timer.h b/qdevices/qdevice-net-echo-request-timer.h new file mode 100644 index 0000000..2191c55 --- /dev/null +++ b/qdevices/qdevice-net-echo-request-timer.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_ECHO_REQUEST_TIMER_H_ +#define _QDEVICE_NET_ECHO_REQUEST_TIMER_H_ + +#include "qdevice-net-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_echo_request_timer_schedule(struct qdevice_net_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_ECHO_REQUEST_TIMER_H_ */ diff --git a/qdevices/qdevice-net-heuristics.c b/qdevices/qdevice-net-heuristics.c new file mode 100644 index 0000000..2bf23cf --- /dev/null +++ b/qdevices/qdevice-net-heuristics.c @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qdevice-log.h" +#include "qdevice-net-algorithm.h" +#include "qdevice-net-cast-vote-timer.h" +#include "qdevice-net-heuristics.h" +#include "qdevice-net-send.h" +#include "qdevice-net-votequorum.h" + +enum tlv_heuristics +qdevice_net_heuristics_exec_result_to_tlv(enum qdevice_heuristics_exec_result exec_result) +{ + enum tlv_heuristics res; + + switch (exec_result) { + case QDEVICE_HEURISTICS_EXEC_RESULT_DISABLED: res = TLV_HEURISTICS_UNDEFINED; break; + case QDEVICE_HEURISTICS_EXEC_RESULT_PASS: res = TLV_HEURISTICS_PASS; break; + case QDEVICE_HEURISTICS_EXEC_RESULT_FAIL: res = TLV_HEURISTICS_FAIL; break; + default: + qdevice_log(LOG_ERR, "qdevice_net_heuristics_exec_result_to_tlv: Unhandled " + "heuristics exec result %s", + qdevice_heuristics_exec_result_to_str(exec_result)); + exit(1); + break; + } + + return (res); +} + +static int +qdevice_net_regular_heuristics_exec_result_callback(void *heuristics_instance_ptr, + uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result) +{ + struct qdevice_heuristics_instance *heuristics_instance; + struct qdevice_instance *instance; + struct qdevice_net_instance *net_instance; + int send_msg; + enum tlv_vote vote; + enum tlv_heuristics heuristics; + + heuristics_instance = (struct qdevice_heuristics_instance *)heuristics_instance_ptr; + instance = heuristics_instance->qdevice_instance_ptr; + net_instance = instance->model_data; + + if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list, + qdevice_net_regular_heuristics_exec_result_callback, 0) != 0) { + qdevice_log(LOG_ERR, "Can't deactivate net regular heuristics exec callback notifier"); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER; + net_instance->schedule_disconnect = 1; + + return (0); + } + + heuristics = qdevice_net_heuristics_exec_result_to_tlv(exec_result); + + if (exec_result == QDEVICE_HEURISTICS_EXEC_RESULT_DISABLED) { + /* + * Can happen when user disables heuristics during runtime + */ + return (0); + } + + if (net_instance->latest_heuristics_result != heuristics) { + qdevice_log(LOG_ERR, "Heuristics result changed from %s to %s", + tlv_heuristics_to_str(net_instance->latest_heuristics_result), + tlv_heuristics_to_str(heuristics)); + + if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + /* + * Not connected to qnetd + */ + send_msg = 0; + } else { + send_msg = 1; + } + + vote = TLV_VOTE_NO_CHANGE; + + if (qdevice_net_algorithm_heuristics_change(net_instance, &heuristics, &send_msg, + &vote) == -1) { + qdevice_log(LOG_ERR, "Algorithm returned error. Disconnecting."); + + net_instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_ERR; + net_instance->schedule_disconnect = 1; + + return (0); + } else { + qdevice_log(LOG_DEBUG, "Algorithm decided to %s message with heuristics result " + "%s and result vote is %s", (send_msg ? "send" : "not send"), + tlv_heuristics_to_str(heuristics), tlv_vote_to_str(vote)); + } + + if (send_msg) { + if (heuristics == TLV_HEURISTICS_UNDEFINED) { + qdevice_log(LOG_ERR, "Inconsistent algorithm result. " + "It's not possible to send message with undefined heuristics. " + "Disconnecting."); + + net_instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_ERR; + net_instance->schedule_disconnect = 1; + + return (0); + } + + if (!net_instance->server_supports_heuristics) { + qdevice_log(LOG_ERR, "Server doesn't support heuristics. " + "Disconnecting."); + + net_instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT; + net_instance->schedule_disconnect = 1; + + return (0); + } + + if (qdevice_net_send_heuristics_change(net_instance, heuristics) != 0) { + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + net_instance->schedule_disconnect = 1; + + return (0); + } + } + + if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { + qdevice_log(LOG_CRIT, "qdevice_net_heuristics_exec_result_callback " + "Can't update cast vote timer"); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + net_instance->schedule_disconnect = 1; + + return (0); + } + } + + net_instance->latest_regular_heuristics_result = heuristics; + net_instance->latest_heuristics_result = heuristics; + + if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) { + return (0); + } + + return (0); +} + +static int +qdevice_net_connect_heuristics_exec_result_callback(void *heuristics_instance_ptr, + uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result) +{ + struct qdevice_heuristics_instance *heuristics_instance; + struct qdevice_instance *instance; + struct qdevice_net_instance *net_instance; + enum tlv_vote vote; + enum tlv_heuristics heuristics; + int send_config_node_list; + int send_membership_node_list; + int send_quorum_node_list; + struct tlv_ring_id tlv_rid; + enum tlv_quorate quorate; + + heuristics_instance = (struct qdevice_heuristics_instance *)heuristics_instance_ptr; + instance = heuristics_instance->qdevice_instance_ptr; + net_instance = instance->model_data; + + + if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list, + qdevice_net_connect_heuristics_exec_result_callback, 0) != 0) { + qdevice_log(LOG_ERR, "Can't deactivate net connect heuristics exec callback notifier"); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER; + net_instance->schedule_disconnect = 1; + + return (0); + } + + heuristics = qdevice_net_heuristics_exec_result_to_tlv(exec_result); + + send_config_node_list = 1; + send_membership_node_list = 1; + send_quorum_node_list = 1; + vote = TLV_VOTE_WAIT_FOR_REPLY; + + if (qdevice_net_algorithm_connected(net_instance, &heuristics, &send_config_node_list, + &send_membership_node_list, &send_quorum_node_list, &vote) != 0) { + qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting."); + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_CONNECTED_ERR; + return (0); + } else { + qdevice_log(LOG_DEBUG, "Algorithm decided to %s config node list, %s membership " + "node list, %s quorum node list, heuristics is %s and result vote is %s", + (send_config_node_list ? "send" : "not send"), + (send_membership_node_list ? "send" : "not send"), + (send_quorum_node_list ? "send" : "not send"), + tlv_heuristics_to_str(heuristics), + tlv_vote_to_str(vote)); + } + + /* + * Now we can finally really send node list, votequorum node list and update timer + */ + if (send_config_node_list) { + if (qdevice_net_send_config_node_list(net_instance, + &instance->config_node_list, + instance->config_node_list_version_set, + instance->config_node_list_version, 1) != 0) { + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + return (0); + } + } + + if (send_membership_node_list) { + qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, + &instance->vq_node_list_ring_id); + + if (qdevice_net_send_membership_node_list(net_instance, &tlv_rid, + instance->vq_node_list_entries, + instance->vq_node_list, + heuristics) != 0) { + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + return (0); + } + } + + if (send_quorum_node_list) { + quorate = (instance->vq_quorum_quorate ? + TLV_QUORATE_QUORATE : TLV_QUORATE_INQUORATE); + + if (qdevice_net_send_quorum_node_list(net_instance, + quorate, + instance->vq_quorum_node_list_entries, + instance->vq_quorum_node_list) != 0) { + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + return (0); + } + } + + if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) { + qdevice_log(LOG_CRIT, "qdevice_net_msg_received_set_option_reply fatal error. " + " Can't update cast vote timer vote"); + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + } + + net_instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS; + net_instance->connected_since_time = time(NULL); + + net_instance->latest_connect_heuristics_result = heuristics; + net_instance->latest_heuristics_result = heuristics; + + return (0); +} + +static int +qdevice_net_heuristics_timer_callback(void *data1, void *data2) +{ + struct qdevice_net_instance *net_instance; + struct qdevice_heuristics_instance *heuristics_instance; + + net_instance = (struct qdevice_net_instance *)data1; + heuristics_instance = &net_instance->qdevice_instance_ptr->heuristics_instance; + + if (qdevice_heuristics_waiting_for_result(heuristics_instance)) { + qdevice_log(LOG_DEBUG, "Not executing regular heuristics because other heuristics is already running."); + + return (1); + } + + net_instance->regular_heuristics_timer = NULL; + + qdevice_log(LOG_DEBUG, "Executing regular heuristics."); + + if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list, + qdevice_net_regular_heuristics_exec_result_callback, 1) != 0) { + qdevice_log(LOG_ERR, "Can't activate net regular heuristics exec callback notifier"); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER; + net_instance->schedule_disconnect = 1; + + return (0); + } + + if (qdevice_heuristics_exec(heuristics_instance, + net_instance->qdevice_instance_ptr->sync_in_progress) != 0) { + qdevice_log(LOG_ERR, "Can't execute regular heuristics."); + + net_instance->schedule_disconnect = 1; + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_START_HEURISTICS; + + return (0); + } + + /* + * Do not schedule this callback again. It's going to be scheduled in the + * qdevice_net_heuristics_exec_result_callback + */ + return (0); +} + +int +qdevice_net_heuristics_stop_timer(struct qdevice_net_instance *net_instance) +{ + struct qdevice_instance *instance; + struct qdevice_heuristics_instance *heuristics_instance; + + instance = net_instance->qdevice_instance_ptr; + heuristics_instance = &instance->heuristics_instance; + + if (net_instance->regular_heuristics_timer != NULL) { + qdevice_log(LOG_DEBUG, "Regular heuristics timer stopped"); + + timer_list_delete(&net_instance->main_timer_list, net_instance->regular_heuristics_timer); + net_instance->regular_heuristics_timer = NULL; + + if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list, + qdevice_net_regular_heuristics_exec_result_callback, 0) != 0) { + qdevice_log(LOG_ERR, "Can't deactivate net regular heuristics exec callback notifier"); + + net_instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER; + net_instance->schedule_disconnect = 1; + return (-1); + } + } + + return (0); +} + +int +qdevice_net_heuristics_schedule_timer(struct qdevice_net_instance *net_instance) +{ + uint32_t interval; + struct qdevice_instance *instance; + struct qdevice_heuristics_instance *heuristics_instance; + + instance = net_instance->qdevice_instance_ptr; + heuristics_instance = &instance->heuristics_instance; + + if (heuristics_instance->mode != QDEVICE_HEURISTICS_MODE_ENABLED) { + qdevice_log(LOG_DEBUG, "Not scheduling heuristics timer because mode is not enabled"); + + if (qdevice_net_heuristics_stop_timer(net_instance) != 0) { + return (-1); + } + + return (0); + } + + if (net_instance->regular_heuristics_timer != NULL) { + qdevice_log(LOG_DEBUG, "Not scheduling heuristics timer because it is already scheduled"); + + return (0); + } + + interval = heuristics_instance->interval; + + qdevice_log(LOG_DEBUG, "Scheduling next regular heuristics in %"PRIu32"ms", interval); + + net_instance->regular_heuristics_timer = timer_list_add(&net_instance->main_timer_list, + interval, + qdevice_net_heuristics_timer_callback, + (void *)net_instance, NULL); + + if (net_instance->regular_heuristics_timer == NULL) { + qdevice_log(LOG_ERR, "Can't schedule regular heuristics."); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_HEURISTICS_TIMER; + net_instance->schedule_disconnect = 1; + return (-1); + } + + return (0); +} + +int +qdevice_net_heuristics_init(struct qdevice_net_instance *net_instance) +{ + + if (qdevice_heuristics_result_notifier_list_add( + &net_instance->qdevice_instance_ptr->heuristics_instance.exec_result_notifier_list, + qdevice_net_regular_heuristics_exec_result_callback) == NULL) { + qdevice_log(LOG_ERR, "Can't add net regular heuristics exec callback into notifier"); + + return (-1); + } + + if (qdevice_heuristics_result_notifier_list_add( + &net_instance->qdevice_instance_ptr->heuristics_instance.exec_result_notifier_list, + qdevice_net_connect_heuristics_exec_result_callback) == NULL) { + qdevice_log(LOG_ERR, "Can't add net connect heuristics exec callback into notifier"); + + return (-1); + } + + return (0); +} + +int +qdevice_net_heuristics_exec_after_connect(struct qdevice_net_instance *net_instance) +{ + struct qdevice_instance *instance; + struct qdevice_heuristics_instance *heuristics_instance; + + instance = net_instance->qdevice_instance_ptr; + heuristics_instance = &instance->heuristics_instance; + + qdevice_log(LOG_DEBUG, "Executing after-connect heuristics."); + + if (qdevice_heuristics_result_notifier_list_set_active(&heuristics_instance->exec_result_notifier_list, + qdevice_net_connect_heuristics_exec_result_callback, 1) != 0) { + qdevice_log(LOG_ERR, "Can't activate net connect heuristics exec callback notifier"); + + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ACTIVATE_HEURISTICS_RESULT_NOTIFIER; + net_instance->schedule_disconnect = 1; + + return (-1); + } + + if (qdevice_heuristics_exec(heuristics_instance, + instance->sync_in_progress) != 0) { + qdevice_log(LOG_ERR, "Can't execute connect heuristics."); + + net_instance->schedule_disconnect = 1; + net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_START_HEURISTICS; + + return (-1); + } + + return (0); +} diff --git a/qdevices/qdevice-net-heuristics.h b/qdevices/qdevice-net-heuristics.h new file mode 100644 index 0000000..6e0a1ff --- /dev/null +++ b/qdevices/qdevice-net-heuristics.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_HEURISTICS_H_ +#define _QDEVICE_NET_HEURISTICS_H_ + +#include "qdevice-heuristics-exec-result.h" +#include "qdevice-net-instance.h" +#include "tlv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern enum tlv_heuristics qdevice_net_heuristics_exec_result_to_tlv( + enum qdevice_heuristics_exec_result exec_result); + +extern int qdevice_net_heuristics_stop_timer(struct qdevice_net_instance *net_instance); + +extern int qdevice_net_heuristics_schedule_timer(struct qdevice_net_instance *net_instance); + +extern int qdevice_net_heuristics_init(struct qdevice_net_instance *net_instance); + +extern int qdevice_net_heuristics_exec_after_connect(struct qdevice_net_instance *net_instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_HEURISTICS_H_ */ diff --git a/qdevices/qdevice-net-instance.c b/qdevices/qdevice-net-instance.c new file mode 100644 index 0000000..e4b7b04 --- /dev/null +++ b/qdevices/qdevice-net-instance.c @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qdevice-config.h" +#include "qdevice-log.h" +#include "qdevice-net-instance.h" +#include "qnet-config.h" +#include "utils.h" +#include "qdevice-net-poll-array-user-data.h" +#include "qdevice-ipc.h" + +/* + * Needed for creating nspr handle from unix fd + */ +#include + +int +qdevice_net_instance_init(struct qdevice_net_instance *instance, + enum tlv_tls_supported tls_supported, + enum tlv_decision_algorithm_type decision_algorithm, uint32_t heartbeat_interval, + uint32_t sync_heartbeat_interval, uint32_t cast_vote_timer_interval, + const char *host_addr, uint16_t host_port, const char *cluster_name, + const struct tlv_tie_breaker *tie_breaker, uint32_t connect_timeout, + int force_ip_version, int cmap_fd, int votequorum_fd, int local_socket_fd, + const struct qdevice_advanced_settings *advanced_settings, + int heuristics_pipe_cmd_send_fd, int heuristics_pipe_cmd_recv_fd, + int heuristics_pipe_log_recv_fd) +{ + + memset(instance, 0, sizeof(*instance)); + + instance->advanced_settings = advanced_settings; + instance->decision_algorithm = decision_algorithm; + instance->heartbeat_interval = heartbeat_interval; + instance->sync_heartbeat_interval = sync_heartbeat_interval; + instance->cast_vote_timer_interval = cast_vote_timer_interval; + instance->cast_vote_timer = NULL; + instance->host_addr = host_addr; + instance->host_port = host_port; + instance->cluster_name = cluster_name; + instance->connect_timeout = connect_timeout; + instance->last_msg_seq_num = 1; + instance->echo_request_expected_msg_seq_num = 1; + instance->echo_reply_received_msg_seq_num = 1; + instance->force_ip_version = force_ip_version; + instance->last_echo_reply_received_time = ((time_t) -1); + instance->connected_since_time = ((time_t) -1); + + memcpy(&instance->tie_breaker, tie_breaker, sizeof(*tie_breaker)); + + dynar_init(&instance->receive_buffer, advanced_settings->net_initial_msg_receive_size); + + send_buffer_list_init(&instance->send_buffer_list, advanced_settings->net_max_send_buffers, + advanced_settings->net_initial_msg_send_size); + + timer_list_init(&instance->main_timer_list); + + pr_poll_array_init(&instance->poll_array, sizeof(struct qdevice_net_poll_array_user_data)); + + instance->tls_supported = tls_supported; + + if ((instance->cmap_poll_fd = PR_CreateSocketPollFd(cmap_fd)) == NULL) { + qdevice_log_nss(LOG_CRIT, "Can't create NSPR cmap poll fd"); + return (-1); + } + + if ((instance->votequorum_poll_fd = PR_CreateSocketPollFd(votequorum_fd)) == NULL) { + qdevice_log_nss(LOG_CRIT, "Can't create NSPR votequorum poll fd"); + return (-1); + } + + if ((instance->ipc_socket_poll_fd = PR_CreateSocketPollFd(local_socket_fd)) == NULL) { + qdevice_log_nss(LOG_CRIT, "Can't create NSPR IPC socket poll fd"); + return (-1); + } + + if ((instance->heuristics_pipe_cmd_send_poll_fd = + PR_CreateSocketPollFd(heuristics_pipe_cmd_send_fd)) == NULL) { + qdevice_log_nss(LOG_CRIT, "Can't create NSPR heuristics pipe command send poll fd"); + return (-1); + } + + if ((instance->heuristics_pipe_cmd_recv_poll_fd = + PR_CreateSocketPollFd(heuristics_pipe_cmd_recv_fd)) == NULL) { + qdevice_log_nss(LOG_CRIT, "Can't create NSPR heuristics pipe command recv poll fd"); + return (-1); + } + + if ((instance->heuristics_pipe_log_recv_poll_fd = + PR_CreateSocketPollFd(heuristics_pipe_log_recv_fd)) == NULL) { + qdevice_log_nss(LOG_CRIT, "Can't create NSPR heuristics pipe log recv poll fd"); + return (-1); + } + + return (0); +} + +void +qdevice_net_instance_clean(struct qdevice_net_instance *instance) +{ + + dynar_clean(&instance->receive_buffer); + + send_buffer_list_free(&instance->send_buffer_list); + + instance->skipping_msg = 0; + instance->msg_already_received_bytes = 0; + instance->echo_request_expected_msg_seq_num = instance->echo_reply_received_msg_seq_num; + instance->using_tls = 0; + instance->tls_client_cert_sent = 0; + instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT; + + instance->schedule_disconnect = 0; + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNDEFINED; + instance->last_echo_reply_received_time = ((time_t) -1); + instance->connected_since_time = ((time_t) -1); +} + +int +qdevice_net_instance_destroy(struct qdevice_net_instance *instance) +{ + struct unix_socket_client *ipc_client; + const struct unix_socket_client_list *ipc_client_list; + struct qdevice_ipc_user_data *qdevice_ipc_user_data; + PRFileDesc *prfd; + + ipc_client_list = &instance->qdevice_instance_ptr->local_ipc.clients; + + TAILQ_FOREACH(ipc_client, ipc_client_list, entries) { + qdevice_ipc_user_data = (struct qdevice_ipc_user_data *)ipc_client->user_data; + prfd = (PRFileDesc *)qdevice_ipc_user_data->model_data; + + if (PR_DestroySocketPollFd(prfd) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to destroy client IPC poll socket fd"); + } + } + + dynar_destroy(&instance->receive_buffer); + + send_buffer_list_free(&instance->send_buffer_list); + + pr_poll_array_destroy(&instance->poll_array); + + timer_list_free(&instance->main_timer_list); + + free((void *)instance->cluster_name); + free((void *)instance->host_addr); + + if (PR_DestroySocketPollFd(instance->votequorum_poll_fd) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to close votequorum connection fd"); + } + + if (PR_DestroySocketPollFd(instance->cmap_poll_fd) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to close votequorum connection fd"); + } + + if (PR_DestroySocketPollFd(instance->ipc_socket_poll_fd) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to close local socket poll fd"); + } + + if (PR_DestroySocketPollFd(instance->heuristics_pipe_cmd_send_poll_fd) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to close heuristics pipe command send poll fd"); + return (-1); + } + + if (PR_DestroySocketPollFd(instance->heuristics_pipe_cmd_recv_poll_fd) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to close heuristics pipe command recv poll fd"); + return (-1); + } + + if (PR_DestroySocketPollFd(instance->heuristics_pipe_log_recv_poll_fd) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to close heuristics pipe log recv poll fd"); + return (-1); + } + + return (0); +} + +int +qdevice_net_instance_init_from_cmap(struct qdevice_instance *instance) +{ + char *str; + cmap_handle_t cmap_handle; + enum tlv_tls_supported tls_supported; + int i; + long int li; + enum tlv_decision_algorithm_type decision_algorithm; + struct tlv_tie_breaker tie_breaker; + uint32_t heartbeat_interval; + uint32_t sync_heartbeat_interval; + uint32_t cast_vote_timer_interval; + char *host_addr; + int host_port; + char *ep; + char *cluster_name; + uint32_t connect_timeout; + struct qdevice_net_instance *net_instance; + int force_ip_version; + + cmap_handle = instance->cmap_handle; + + net_instance = malloc(sizeof(*net_instance)); + if (net_instance == NULL) { + qdevice_log(LOG_ERR, "Can't alloc qdevice_net_instance"); + return (-1); + } + + /* + * Check tls + */ + tls_supported = QDEVICE_NET_DEFAULT_TLS_SUPPORTED; + + if (cmap_get_string(cmap_handle, "quorum.device.net.tls", &str) == CS_OK) { + if ((i = utils_parse_bool_str(str)) == -1) { + if (strcasecmp(str, "required") != 0) { + free(str); + qdevice_log(LOG_ERR, "quorum.device.net.tls value is not valid."); + + goto error_free_instance; + } else { + tls_supported = TLV_TLS_REQUIRED; + } + } else { + if (i == 1) { + tls_supported = TLV_TLS_SUPPORTED; + } else { + tls_supported = TLV_TLS_UNSUPPORTED; + } + } + + free(str); + } + + /* + * Host + */ + if (cmap_get_string(cmap_handle, "quorum.device.net.host", &str) != CS_OK) { + qdevice_log(LOG_ERR, "Qdevice net daemon address is not defined (quorum.device.net.host)"); + goto error_free_instance; + } + host_addr = str; + + if (cmap_get_string(cmap_handle, "quorum.device.net.port", &str) == CS_OK) { + host_port = strtol(str, &ep, 10); + + if (host_port <= 0 || host_port > ((uint16_t)~0) || *ep != '\0') { + qdevice_log(LOG_ERR, "quorum.device.net.port must be in range 0-65535"); + free(str); + goto error_free_host_addr; + } + free(str); + } else { + host_port = QNETD_DEFAULT_HOST_PORT; + } + + /* + * Cluster name + */ + if (cmap_get_string(cmap_handle, "totem.cluster_name", &str) != CS_OK) { + qdevice_log(LOG_ERR, "Cluster name (totem.cluster_name) has to be defined."); + goto error_free_host_addr; + } + cluster_name = str; + + /* + * Adjust qdevice timeouts to better suit qnetd + */ + cast_vote_timer_interval = instance->heartbeat_interval * 0.5; + heartbeat_interval = instance->heartbeat_interval * 0.8; + if (heartbeat_interval < instance->advanced_settings->net_heartbeat_interval_min) { + qdevice_log(LOG_WARNING, "Heartbeat interval too small %"PRIu32". Adjusting to %"PRIu32".", + heartbeat_interval, instance->advanced_settings->net_heartbeat_interval_min); + heartbeat_interval = instance->advanced_settings->net_heartbeat_interval_min; + } + if (heartbeat_interval > instance->advanced_settings->net_heartbeat_interval_max) { + qdevice_log(LOG_WARNING, "Heartbeat interval too big %"PRIu32". Adjusting to %"PRIu32".", + heartbeat_interval, instance->advanced_settings->net_heartbeat_interval_max); + heartbeat_interval = instance->advanced_settings->net_heartbeat_interval_max; + } + sync_heartbeat_interval = instance->sync_heartbeat_interval * 0.8; + + /* + * Choose decision algorithm + */ + if (cmap_get_string(cmap_handle, "quorum.device.net.algorithm", &str) != CS_OK) { + decision_algorithm = QDEVICE_NET_DEFAULT_ALGORITHM; + } else { + if (strcmp(str, "test") == 0) { + decision_algorithm = TLV_DECISION_ALGORITHM_TYPE_TEST; + } else if (strcmp(str, "ffsplit") == 0) { + decision_algorithm = TLV_DECISION_ALGORITHM_TYPE_FFSPLIT; + } else if (strcmp(str, "2nodelms") == 0) { + decision_algorithm = TLV_DECISION_ALGORITHM_TYPE_2NODELMS; + } else if (strcmp(str, "lms") == 0) { + decision_algorithm = TLV_DECISION_ALGORITHM_TYPE_LMS; + } else { + qdevice_log(LOG_ERR, "Unknown decision algorithm %s", str); + free(str); + goto error_free_cluster_name; + } + + free(str); + } + + if (decision_algorithm == TLV_DECISION_ALGORITHM_TYPE_TEST && + !instance->advanced_settings->net_test_algorithm_enabled) { + qdevice_log(LOG_ERR, "Test algorithm is not enabled. You can force enable it by " + "passing -S net_test_algorithm_enabled=on to %s command", QDEVICE_PROGRAM_NAME); + + goto error_free_cluster_name; + } + /* + * Load tie_breaker mode + */ + memset(&tie_breaker, 0, sizeof(tie_breaker)); + + if (cmap_get_string(cmap_handle, "quorum.device.net.tie_breaker", &str) != CS_OK) { + tie_breaker.mode = QDEVICE_NET_DEFAULT_TIE_BREAKER_MODE; + } else { + if (strcmp(str, "lowest") == 0) { + tie_breaker.mode = TLV_TIE_BREAKER_MODE_LOWEST; + } else if (strcmp(str, "highest") == 0) { + tie_breaker.mode = TLV_TIE_BREAKER_MODE_HIGHEST; + } else { + li = strtol(str, &ep, 10); + if (li <= 0 || li > ((uint32_t)~0) || *ep != '\0') { + qdevice_log(LOG_ERR, "tie_breaker must be lowest|highest|valid_node_id"); + free(str); + goto error_free_cluster_name; + } + + tie_breaker.mode = TLV_TIE_BREAKER_MODE_NODE_ID; + tie_breaker.node_id = li; + } + + free(str); + } + + /* + * Get connect timeout + */ + if (cmap_get_string(cmap_handle, "quorum.device.net.connect_timeout", &str) != CS_OK) { + connect_timeout = heartbeat_interval; + } else { + li = strtol(str, &ep, 10); + if (li < instance->advanced_settings->net_min_connect_timeout || + li > instance->advanced_settings->net_max_connect_timeout || *ep != '\0') { + qdevice_log(LOG_ERR, "connect_timeout must be valid number in " + "range <%"PRIu32",%"PRIu32">", + instance->advanced_settings->net_min_connect_timeout, + instance->advanced_settings->net_max_connect_timeout); + free(str); + goto error_free_cluster_name; + } + + connect_timeout = li; + + free(str); + } + + if (cmap_get_string(cmap_handle, "quorum.device.net.force_ip_version", &str) != CS_OK) { + force_ip_version = 0; + } else { + li = strtol(str, &ep, 10); + if ((li != 0 && li != 4 && li != 6) || *ep != '\0') { + qdevice_log(LOG_ERR, "force_ip_version must be one of 0|4|6"); + free(str); + goto error_free_cluster_name; + } + + force_ip_version = li; + + free(str); + } + + /* + * Really initialize instance + */ + if (qdevice_net_instance_init(net_instance, + tls_supported, decision_algorithm, + heartbeat_interval, sync_heartbeat_interval, cast_vote_timer_interval, + host_addr, host_port, cluster_name, &tie_breaker, connect_timeout, + force_ip_version, + instance->cmap_poll_fd, instance->votequorum_poll_fd, + instance->local_ipc.socket, instance->advanced_settings, + instance->heuristics_instance.pipe_cmd_send, + instance->heuristics_instance.pipe_cmd_recv, + instance->heuristics_instance.pipe_log_recv) == -1) { + qdevice_log(LOG_ERR, "Can't initialize qdevice-net instance"); + goto error_free_instance; + } + + net_instance->qdevice_instance_ptr = instance; + instance->model_data = net_instance; + + return (0); + +error_free_cluster_name: + free(cluster_name); +error_free_host_addr: + free(host_addr); +error_free_instance: + free(net_instance); + return (-1); +} diff --git a/qdevices/qdevice-net-instance.h b/qdevices/qdevice-net-instance.h new file mode 100644 index 0000000..0b2c40c --- /dev/null +++ b/qdevices/qdevice-net-instance.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_INSTANCE_H_ +#define _QDEVICE_NET_INSTANCE_H_ + +#include + +#include +#include + +#include "nss-sock.h" + +#include "qdevice-instance.h" + +#include "dynar.h" +#include "node-list.h" +#include "pr-poll-array.h" +#include "qdevice-net-disconnect-reason.h" +#include "send-buffer-list.h" +#include "tlv.h" +#include "timer-list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum qdevice_net_instance_state { + QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT, + QDEVICE_NET_INSTANCE_STATE_SENDING_PREINIT_REPLY, + QDEVICE_NET_INSTANCE_STATE_WAITING_PREINIT_REPLY, + QDEVICE_NET_INSTANCE_STATE_WAITING_STARTTLS_BEING_SENT, + QDEVICE_NET_INSTANCE_STATE_WAITING_INIT_REPLY, + QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS, +}; + +struct qdevice_net_instance { + PRFileDesc *socket; + struct dynar receive_buffer; + struct send_buffer_list send_buffer_list; + int skipping_msg; + size_t msg_already_received_bytes; + enum qdevice_net_instance_state state; + uint32_t last_msg_seq_num; + uint32_t echo_request_expected_msg_seq_num; + uint32_t echo_reply_received_msg_seq_num; + enum tlv_tls_supported tls_supported; + int using_tls; + int tls_client_cert_sent; + uint32_t heartbeat_interval; /* Adjusted heartbeat interval during normal operation */ + uint32_t sync_heartbeat_interval; /* Adjusted heartbeat interval during corosync sync */ + uint32_t cast_vote_timer_interval; /* Timer for cast vote */ + uint32_t connect_timeout; + struct timer_list_entry *cast_vote_timer; + enum tlv_vote cast_vote_timer_vote; + int cast_vote_timer_paused; + const char *host_addr; + uint16_t host_port; + const char *cluster_name; + enum tlv_decision_algorithm_type decision_algorithm; + struct timer_list main_timer_list; + struct timer_list_entry *echo_request_timer; + int schedule_disconnect; + PRFileDesc *votequorum_poll_fd; + PRFileDesc *cmap_poll_fd; + PRFileDesc *ipc_socket_poll_fd; + struct tlv_ring_id last_sent_ring_id; + struct tlv_tie_breaker tie_breaker; + void *algorithm_data; + enum qdevice_net_disconnect_reason disconnect_reason; + struct qdevice_instance *qdevice_instance_ptr; + struct nss_sock_non_blocking_client non_blocking_client; + struct timer_list_entry *connect_timer; + int force_ip_version; + struct pr_poll_array poll_array; + time_t last_echo_reply_received_time; + time_t connected_since_time; + const struct qdevice_advanced_settings *advanced_settings; + PRFileDesc *heuristics_pipe_cmd_send_poll_fd; + PRFileDesc *heuristics_pipe_cmd_recv_poll_fd; + PRFileDesc *heuristics_pipe_log_recv_poll_fd; + struct timer_list_entry *regular_heuristics_timer; + int server_supports_heuristics; + enum tlv_heuristics latest_regular_heuristics_result; + enum tlv_heuristics latest_connect_heuristics_result; + enum tlv_heuristics latest_vq_heuristics_result; + enum tlv_heuristics latest_heuristics_result; +}; + +extern int qdevice_net_instance_init(struct qdevice_net_instance *instance, + enum tlv_tls_supported tls_supported, + enum tlv_decision_algorithm_type decision_algorithm, uint32_t heartbeat_interval, + uint32_t sync_heartbeat_interval, uint32_t cast_vote_timer_interval, + const char *host_addr, uint16_t host_port, const char *cluster_name, + const struct tlv_tie_breaker *tie_breaker, uint32_t connect_timeout, int force_ip_version, + int cmap_fd, int votequorum_fd, int local_socket_fd, + const struct qdevice_advanced_settings *advanced_settings, + int heuristics_pipe_cmd_send_fd, int heuristics_pipe_cmd_recv_fd, + int heuristics_pipe_log_recv_fd); + +extern void qdevice_net_instance_clean(struct qdevice_net_instance *instance); + +extern int qdevice_net_instance_destroy(struct qdevice_net_instance *instance); + +extern int qdevice_net_instance_init_from_cmap(struct qdevice_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_INSTANCE_H_ */ diff --git a/qdevices/qdevice-net-ipc-cmd.c b/qdevices/qdevice-net-ipc-cmd.c new file mode 100644 index 0000000..0c7a3d9 --- /dev/null +++ b/qdevices/qdevice-net-ipc-cmd.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qdevice-net-ipc-cmd.h" +#include "qdevice-log.h" +#include "dynar-str.h" +#include "qdevice-net-algorithm.h" +#include "utils.h" + +static int +qdevice_net_ipc_cmd_status_add_header(struct dynar *outbuf, int verbose) +{ + + return ((dynar_str_catf(outbuf, "Qdevice-net information\n") != -1) && + (dynar_str_catf(outbuf, "----------------------\n") != -1)); +} + +static int +qdevice_net_ipc_cmd_status_add_tie_breaker(struct qdevice_net_instance *instance, + struct dynar *outbuf, int verbose) +{ + + if (dynar_str_catf(outbuf, "Tie-breaker:\t\t") == -1) { + return (0); + } + + switch (instance->tie_breaker.mode) { + case TLV_TIE_BREAKER_MODE_LOWEST: + if (dynar_str_catf(outbuf, "Node with lowest node ID") == -1) { + return (0); + } + break; + case TLV_TIE_BREAKER_MODE_HIGHEST: + if (dynar_str_catf(outbuf, "Node with highest node ID") == -1) { + return (0); + } + break; + case TLV_TIE_BREAKER_MODE_NODE_ID: + if (dynar_str_catf(outbuf, "Node with node ID "UTILS_PRI_NODE_ID, + instance->tie_breaker.node_id) == -1) { + return (0); + } + break; + } + + return (dynar_str_catf(outbuf, "\n") != -1); +} + +static int +qdevice_net_ipc_cmd_status_add_basic_info(struct qdevice_net_instance *instance, + struct dynar *outbuf, int verbose) +{ + + if (dynar_str_catf(outbuf, "Cluster name:\t\t%s\n", instance->cluster_name) == -1) { + return (0); + } + + if (dynar_str_catf(outbuf, "QNetd host:\t\t%s:%"PRIu16"\n", + instance->host_addr, instance->host_port) == -1) { + return (0); + } + + if (verbose && instance->force_ip_version != 0) { + if (dynar_str_catf(outbuf, "Force IP version:\t%u\n", + instance->force_ip_version) == -1) { + return (0); + } + } + + if (verbose) { + if ((dynar_str_catf(outbuf, "Connect timeout:\t%"PRIu32"ms\n", + instance->connect_timeout) == -1) || + (dynar_str_catf(outbuf, "HB interval:\t\t%"PRIu32"ms\n", + instance->heartbeat_interval) == -1) || + (dynar_str_catf(outbuf, "VQ vote timer interval:\t%"PRIu32"ms\n", + instance->cast_vote_timer_interval) == -1)) { + return (0); + } + + if (dynar_str_catf(outbuf, "TLS:\t\t\t%s\n", + tlv_tls_supported_to_str(instance->tls_supported)) == -1) { + return (0); + } + } + + if (dynar_str_catf(outbuf, "Algorithm:\t\t%s\n", + tlv_decision_algorithm_type_to_str(instance->decision_algorithm)) == -1) { + return (0); + } + + return (1); +} + +static int +qdevice_net_ipc_cmd_status_add_poll_timer_status(struct qdevice_net_instance *instance, + struct dynar *outbuf, int verbose) +{ + + if (!verbose) { + return (1); + } + + if (dynar_str_catf(outbuf, "Poll timer running:\t%s", + (instance->cast_vote_timer != NULL ? "Yes" : "No")) == -1) { + return (0); + } + + if (instance->cast_vote_timer != NULL && instance->cast_vote_timer_vote == TLV_VOTE_ACK) { + if (dynar_str_catf(outbuf, " (cast vote)") == -1) { + return (0); + } + } + + return (dynar_str_catf(outbuf, "\n") != -1); +} + +static int +qdevice_net_ipc_cmd_status_add_state(struct qdevice_net_instance *instance, + struct dynar *outbuf, int verbose) +{ + const char *state; + + if (instance->schedule_disconnect) { + state = "Disconnected"; + } else { + if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + state = "Connected"; + } else { + if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT || + !instance->non_blocking_client.destroyed) { + state = "Connecting"; + } else { + state = "Connect failed"; + } + } + } + + return (dynar_str_catf(outbuf, "State:\t\t\t%s\n", state) != -1); +} + +static int +qdevice_net_ipc_cmd_status_add_heuristics(struct qdevice_net_instance *instance, + struct dynar *outbuf, int verbose) +{ + enum qdevice_heuristics_mode active_heuristics_mode; + int heuristics_enabled; + + active_heuristics_mode = instance->qdevice_instance_ptr->heuristics_instance.mode; + heuristics_enabled = (active_heuristics_mode == QDEVICE_HEURISTICS_MODE_ENABLED || + active_heuristics_mode == QDEVICE_HEURISTICS_MODE_SYNC); + + if (!heuristics_enabled) { + return (1); + } + + if (dynar_str_catf(outbuf, "Heuristics result:\t%s", + tlv_heuristics_to_str(instance->latest_heuristics_result)) == -1) { + return (0); + } + + if (verbose) { + if (dynar_str_catf(outbuf, " (regular: %s, membership: %s, connect: %s)", + tlv_heuristics_to_str(instance->latest_regular_heuristics_result), + tlv_heuristics_to_str(instance->latest_vq_heuristics_result), + tlv_heuristics_to_str(instance->latest_connect_heuristics_result)) == -1) { + return (0); + } + } + + return (dynar_str_catf(outbuf, "\n") != -1); +} + +static int +qdevice_net_ipc_cmd_status_add_tls_state(struct qdevice_net_instance *instance, + struct dynar *outbuf, int verbose) +{ + + if (!verbose || instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + return (1); + } + + if (dynar_str_catf(outbuf, "TLS active:\t\t%s", (instance->using_tls ? "Yes" : "No")) == -1) { + return (0); + } + + if (instance->using_tls && instance->tls_client_cert_sent) { + if (dynar_str_catf(outbuf, " (client certificate sent)") == -1) { + return (0); + } + } + + return (dynar_str_catf(outbuf, "\n") != -1); +} + +static int +qdevice_net_ipc_cmd_status_add_times(struct qdevice_net_instance *instance, + struct dynar *outbuf, int verbose) +{ + struct tm tm_res; + + if (!verbose || instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + return (1); + } + + if (instance->connected_since_time != ((time_t) -1)) { + localtime_r(&instance->connected_since_time, &tm_res); + if (dynar_str_catf(outbuf, "Connected since:\t%04d-%02d-%02dT%02d:%02d:%02d\n", + tm_res.tm_year + 1900, tm_res.tm_mon + 1, tm_res.tm_mday, + tm_res.tm_hour, tm_res.tm_min, tm_res.tm_sec) == -1) { + return (0); + } + } + + if (instance->last_echo_reply_received_time != ((time_t) -1)) { + localtime_r(&instance->last_echo_reply_received_time, &tm_res); + if (dynar_str_catf(outbuf, "Echo reply received:\t%04d-%02d-%02dT%02d:%02d:%02d\n", + tm_res.tm_year + 1900, tm_res.tm_mon + 1, tm_res.tm_mday, + tm_res.tm_hour, tm_res.tm_min, tm_res.tm_sec) == -1) { + return (0); + } + } + + return (1); +} + +int +qdevice_net_ipc_cmd_status(struct qdevice_net_instance *instance, struct dynar *outbuf, int verbose) +{ + + if (qdevice_net_ipc_cmd_status_add_header(outbuf, verbose) && + qdevice_net_ipc_cmd_status_add_basic_info(instance, outbuf, verbose) && + qdevice_net_ipc_cmd_status_add_tie_breaker(instance, outbuf, verbose) && + qdevice_net_ipc_cmd_status_add_poll_timer_status(instance, outbuf, verbose) && + qdevice_net_ipc_cmd_status_add_state(instance, outbuf, verbose) && + qdevice_net_ipc_cmd_status_add_heuristics(instance, outbuf, verbose) && + qdevice_net_ipc_cmd_status_add_tls_state(instance, outbuf, verbose) && + qdevice_net_ipc_cmd_status_add_times(instance, outbuf, verbose)) { + return (1); + } + + return (0); +} diff --git a/qdevices/qdevice-net-ipc-cmd.h b/qdevices/qdevice-net-ipc-cmd.h new file mode 100644 index 0000000..594f2ef --- /dev/null +++ b/qdevices/qdevice-net-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_NET_IPC_CMD_H_ +#define _QDEVICE_NET_IPC_CMD_H_ + +#include "dynar.h" +#include "qdevice-net-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_ipc_cmd_status(struct qdevice_net_instance *instance, + struct dynar *outbuf, int verbose); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_IPC_CMD_H_ */ diff --git a/qdevices/qdevice-net-msg-received.c b/qdevices/qdevice-net-msg-received.c new file mode 100644 index 0000000..38a1906 --- /dev/null +++ b/qdevices/qdevice-net-msg-received.c @@ -0,0 +1,983 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qdevice-log.h" +#include "qdevice-net-algorithm.h" +#include "qdevice-net-cast-vote-timer.h" +#include "qdevice-net-heuristics.h" +#include "qdevice-net-msg-received.h" +#include "qdevice-net-send.h" +#include "qdevice-net-votequorum.h" +#include "qdevice-net-echo-request-timer.h" +#include "msg.h" +#include "utils.h" + +/* + * -1 - Incompatible tls combination + * 0 - Don't use TLS + * 1 - Use TLS + */ +static int +qdevice_net_msg_received_check_tls_compatibility(enum tlv_tls_supported server_tls, + enum tlv_tls_supported client_tls) +{ + int res; + + res = -1; + + switch (server_tls) { + case TLV_TLS_UNSUPPORTED: + switch (client_tls) { + case TLV_TLS_UNSUPPORTED: res = 0; break; + case TLV_TLS_SUPPORTED: res = 0; break; + case TLV_TLS_REQUIRED: res = -1; break; + } + break; + case TLV_TLS_SUPPORTED: + switch (client_tls) { + case TLV_TLS_UNSUPPORTED: res = 0; break; + case TLV_TLS_SUPPORTED: res = 1; break; + case TLV_TLS_REQUIRED: res = 1; break; + } + break; + case TLV_TLS_REQUIRED: + switch (client_tls) { + case TLV_TLS_UNSUPPORTED: res = -1; break; + case TLV_TLS_SUPPORTED: res = 1; break; + case TLV_TLS_REQUIRED: res = 1; break; + } + break; + } + + return (res); +} + +static void +qdevice_net_msg_received_log_msg_decode_error(int ret) +{ + + switch (ret) { + case -1: + qdevice_log(LOG_WARNING, "Received message with option with invalid length"); + break; + case -2: + qdevice_log(LOG_CRIT, "Can't allocate memory"); + break; + case -3: + qdevice_log(LOG_WARNING, "Received inconsistent msg (tlv len > msg size)"); + break; + case -4: + qdevice_log(LOG_ERR, "Received message with option with invalid value"); + break; + default: + qdevice_log(LOG_ERR, "Unknown error occurred when decoding message"); + break; + } +} + +static int +qdevice_net_msg_received_unexpected_msg(struct qdevice_net_instance *instance, + const struct msg_decoded *msg, const char *msg_str) +{ + + qdevice_log(LOG_ERR, "Received unexpected %s message. Disconnecting from server", + msg_str); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG; + + return (-1); +} + +static int +qdevice_net_msg_received_init(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + return (qdevice_net_msg_received_unexpected_msg(instance, msg, "init")); +} + +static int +qdevice_net_msg_received_preinit(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + return (qdevice_net_msg_received_unexpected_msg(instance, msg, "preinit")); +} + +static int +qdevice_net_msg_check_seq_number(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + if (!msg->seq_number_set || msg->seq_number != instance->last_msg_seq_num) { + qdevice_log(LOG_ERR, "Received message doesn't contain seq_number or " + "it's not expected one."); + + return (-1); + } + + return (0); +} + +static int +qdevice_net_msg_received_preinit_reply(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + int res; + struct send_buffer_list_entry *send_buffer; + + qdevice_log(LOG_DEBUG, "Received preinit reply msg"); + + if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_PREINIT_REPLY) { + qdevice_log(LOG_ERR, "Received unexpected preinit reply message. " + "Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG; + + return (-1); + } + + if (qdevice_net_msg_check_seq_number(instance, msg) != 0) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + + return (-1); + } + + /* + * Check TLS support + */ + if (!msg->tls_supported_set || !msg->tls_client_cert_required_set) { + qdevice_log(LOG_ERR, "Required tls_supported or tls_client_cert_required " + "option is unset"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + + return (-1); + } + + res = qdevice_net_msg_received_check_tls_compatibility(msg->tls_supported, instance->tls_supported); + if (res == -1) { + qdevice_log(LOG_ERR, "Incompatible tls configuration (server %u client %u)", + msg->tls_supported, instance->tls_supported); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_TLS; + + return (-1); + } else if (res == 1) { + /* + * Start TLS + */ + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't allocate send list buffer for " + "starttls msg"); + + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + + return (-1); + } + + instance->last_msg_seq_num++; + if (msg_create_starttls(&send_buffer->buffer, 1, + instance->last_msg_seq_num) == 0) { + qdevice_log(LOG_ERR, "Can't allocate send buffer for starttls msg"); + + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + return (-1); + } + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_STARTTLS_BEING_SENT; + } else if (res == 0) { + if (qdevice_net_send_init(instance) != 0) { + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + + return (-1); + } + } + + return (0); +} + +static int +qdevice_net_msg_received_init_reply(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + size_t zi; + int res; + enum qdevice_heuristics_mode active_heuristics_mode; + + qdevice_log(LOG_DEBUG, "Received init reply msg"); + + if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_INIT_REPLY) { + qdevice_log(LOG_ERR, "Received unexpected init reply message. " + "Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG; + + return (-1); + } + + if (qdevice_net_msg_check_seq_number(instance, msg) != 0) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + + return (-1); + } + + if (!msg->reply_error_code_set) { + qdevice_log(LOG_ERR, "Received init reply message without error code." + "Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + + return (-1); + } + + if (msg->reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) { + qdevice_log(LOG_ERR, "Received init reply message with error code %"PRIu16". " + "Disconnecting from server", msg->reply_error_code); + + if (msg->reply_error_code == TLV_REPLY_ERROR_CODE_DUPLICATE_NODE_ID) { + qdevice_log(LOG_ERR, "Duplicate node id may be result of server not yet " + "accepted this node disconnect. Retry again."); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_DUPLICATE_NODE_ID_ERROR; + } else if (msg->reply_error_code == TLV_REPLY_ERROR_CODE_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES) { + qdevice_log(LOG_ERR, "Configured tie-breaker differs in cluster. This may be " + "result of server not yet accepted this node disconnect. Retry again."); + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES_ERROR; + } else if (msg->reply_error_code == TLV_REPLY_ERROR_CODE_ALGORITHM_DIFFERS_FROM_OTHER_NODES) { + qdevice_log(LOG_ERR, "Configured algorithm differs in cluster. This may be " + "result of server not yet accepted this node disconnect. Retry again."); + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ALGORITHM_DIFFERS_FROM_OTHER_NODES_ERROR; + } else { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ERROR; + } + + return (-1); + } + + if (!msg->server_maximum_request_size_set || !msg->server_maximum_reply_size_set) { + qdevice_log(LOG_ERR, "Required maximum_request_size or maximum_reply_size " + "option is unset"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + + return (-1); + } + + if (msg->supported_messages == NULL || msg->supported_options == NULL) { + qdevice_log(LOG_ERR, "Required supported messages or supported options " + "option is unset"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + + return (-1); + } + + if (msg->supported_decision_algorithms == NULL) { + qdevice_log(LOG_ERR, "Required supported decision algorithms option is unset"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + + return (-1); + } + + if (msg->server_maximum_request_size < instance->advanced_settings->net_min_msg_send_size) { + qdevice_log(LOG_ERR, + "Server accepts maximum %zu bytes message but this client minimum " + "is %zu bytes.", msg->server_maximum_request_size, + instance->advanced_settings->net_min_msg_send_size); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_MSG_SIZE; + return (-1); + } + + if (msg->server_maximum_reply_size > instance->advanced_settings->net_max_msg_receive_size) { + qdevice_log(LOG_ERR, + "Server may send message up to %zu bytes message but this client maximum " + "is %zu bytes.", msg->server_maximum_reply_size, + instance->advanced_settings->net_max_msg_receive_size); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_INCOMPATIBLE_MSG_SIZE; + return (-1); + } + + /* + * Change buffer sizes + */ + dynar_set_max_size(&instance->receive_buffer, msg->server_maximum_reply_size); + send_buffer_list_set_max_buffer_size(&instance->send_buffer_list, + msg->server_maximum_request_size); + + + /* + * Check if server supports decision algorithm we need + */ + res = 0; + + for (zi = 0; zi < msg->no_supported_decision_algorithms && !res; zi++) { + if (msg->supported_decision_algorithms[zi] == instance->decision_algorithm) { + res = 1; + } + } + + if (!res) { + qdevice_log(LOG_ERR, "Server doesn't support required decision algorithm"); + + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_ALGORITHM; + + return (-1); + } + + /* + * Check if server supports heuristics + */ + res = 0; + for (zi = 0; zi < msg->no_supported_options; zi++) { + if (msg->supported_options[zi] == TLV_OPT_HEURISTICS) { + res = 1; + } + } + + instance->server_supports_heuristics = res; + + if (!res) { + active_heuristics_mode = instance->qdevice_instance_ptr->heuristics_instance.mode; + + if (active_heuristics_mode == QDEVICE_HEURISTICS_MODE_ENABLED || + active_heuristics_mode == QDEVICE_HEURISTICS_MODE_SYNC) { + qdevice_log(LOG_ERR, "Heuristics are enabled but not supported by server"); + + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT; + + return (-1); + } + } + + /* + * Finally fully connected so it's possible to remove connection timer + */ + if (instance->connect_timer != NULL) { + timer_list_delete(&instance->main_timer_list, instance->connect_timer); + instance->connect_timer = NULL; + } + + /* + * Server accepted heartbeat interval -> schedule regular sending of echo request + */ + if (qdevice_net_echo_request_timer_schedule(instance) != 0) { + return (-1); + } + + /* + * Run heuristics (even when it is disabled, undefined result is ok, rest of sending + * is handled by qdevice_net_connect_heuristics_exec_result_callback + */ + if (qdevice_net_heuristics_exec_after_connect(instance) != 0) { + return (-1); + } + + return (0); +} + +static int +qdevice_net_msg_received_starttls(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + return (qdevice_net_msg_received_unexpected_msg(instance, msg, "starttls")); +} + +static int +qdevice_net_msg_received_server_error(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + if (!msg->reply_error_code_set) { + qdevice_log(LOG_ERR, "Received server error without error code set. " + "Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + } else { + qdevice_log(LOG_ERR, "Received server error %"PRIu16". " + "Disconnecting from server", msg->reply_error_code); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_SENT_ERROR; + } + + return (-1); +} + +static int +qdevice_net_msg_received_set_option(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + return (qdevice_net_msg_received_unexpected_msg(instance, msg, "set option")); +} + +static int +qdevice_net_msg_received_set_option_reply(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + qdevice_log(LOG_ERR, "Received unexpected set option reply message. " + "Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG; + + return (-1); + } + + if (qdevice_net_msg_check_seq_number(instance, msg) != 0) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + + return (-1); + } + + if (qdevice_net_echo_request_timer_schedule(instance) != 0) { + return (-1); + } + + return (0); +} + +static int +qdevice_net_msg_received_echo_request(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + return (qdevice_net_msg_received_unexpected_msg(instance, msg, "echo request")); +} + +static int +qdevice_net_msg_received_echo_reply(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + if (!msg->seq_number_set) { + qdevice_log(LOG_ERR, "Received echo reply message doesn't contain seq_number."); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + return (-1); + } + + if (msg->seq_number != instance->echo_request_expected_msg_seq_num) { + qdevice_log(LOG_WARNING, "Received echo reply message seq_number is not expected one."); + } + + if (qdevice_net_algorithm_echo_reply_received(instance, msg->seq_number, + msg->seq_number == instance->echo_request_expected_msg_seq_num) != 0) { + qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_ECHO_REPLY_RECEIVED_ERR; + return (-1); + } + + instance->echo_reply_received_msg_seq_num = msg->seq_number; + instance->last_echo_reply_received_time = time(NULL); + + return (0); +} + +static int +qdevice_net_msg_received_node_list(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + return (qdevice_net_msg_received_unexpected_msg(instance, msg, "node list")); +} + +static int +qdevice_net_msg_received_node_list_reply(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + const char *str; + enum tlv_vote result_vote; + int res; + int case_processed; + int ring_id_is_valid; + + if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + qdevice_log(LOG_ERR, "Received unexpected node list reply message. " + "Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG; + return (-1); + } + + if (!msg->vote_set || !msg->seq_number_set || !msg->node_list_type_set) { + qdevice_log(LOG_ERR, "Received node list reply message without " + "required options. Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + return (-1); + } + + if (!msg->ring_id_set) { + qdevice_log(LOG_ERR, "Received node list reply message " + "without ring id set. Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + return (-1); + } + + str = NULL; + + switch (msg->node_list_type) { + case TLV_NODE_LIST_TYPE_INITIAL_CONFIG: str = "initial config"; break; + case TLV_NODE_LIST_TYPE_CHANGED_CONFIG: str = "changed config"; break; + case TLV_NODE_LIST_TYPE_MEMBERSHIP: str ="membership"; break; + case TLV_NODE_LIST_TYPE_QUORUM: str ="quorum"; break; + /* + * Default is not defined intentionally. Compiler shows warning when new node list type + * is added + */ + } + + if (str == NULL) { + qdevice_log(LOG_CRIT, "qdevice_net_msg_received_node_list_reply fatal error. " + "Unhandled node_list_type (debug output)"); + exit(1); + } + + qdevice_log(LOG_DEBUG, "Received %s node list reply", str); + qdevice_log(LOG_DEBUG, " seq = "UTILS_PRI_MSG_SEQ, msg->seq_number); + qdevice_log(LOG_DEBUG, " vote = %s", tlv_vote_to_str(msg->vote)); + qdevice_log(LOG_DEBUG, " ring id = ("UTILS_PRI_RING_ID")", + msg->ring_id.node_id, msg->ring_id.seq); + + /* + * Call algorithm + */ + result_vote = msg->vote; + + if (!tlv_ring_id_eq(&msg->ring_id, &instance->last_sent_ring_id)) { + ring_id_is_valid = 0; + qdevice_log(LOG_DEBUG, "Received node list reply with old ring id."); + } else { + ring_id_is_valid = 1; + } + + case_processed = 0; + switch (msg->node_list_type) { + case TLV_NODE_LIST_TYPE_INITIAL_CONFIG: + case TLV_NODE_LIST_TYPE_CHANGED_CONFIG: + case_processed = 1; + res = qdevice_net_algorithm_config_node_list_reply_received(instance, + msg->seq_number, (msg->node_list_type == TLV_NODE_LIST_TYPE_INITIAL_CONFIG), + &msg->ring_id, ring_id_is_valid, &result_vote); + break; + case TLV_NODE_LIST_TYPE_MEMBERSHIP: + case_processed = 1; + res = qdevice_net_algorithm_membership_node_list_reply_received(instance, + msg->seq_number, &msg->ring_id, ring_id_is_valid, &result_vote); + break; + case TLV_NODE_LIST_TYPE_QUORUM: + case_processed = 1; + res = qdevice_net_algorithm_quorum_node_list_reply_received(instance, + msg->seq_number, &msg->ring_id, ring_id_is_valid, &result_vote); + break; + /* + * Default is not defined intentionally. Compiler shows warning when new node list type + * is added + */ + } + + if (!case_processed) { + qdevice_log(LOG_CRIT, "qdevice_net_msg_received_node_list_reply fatal error. " + "Unhandled node_list_type (algorithm call)"); + exit(1); + } + + if (res != 0) { + qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting."); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_NODE_LIST_REPLY_ERR; + return (-1); + } else { + qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote)); + } + + if (qdevice_net_cast_vote_timer_update(instance, result_vote) != 0) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + return (-1); + } + + return (0); +} + +static int +qdevice_net_msg_received_ask_for_vote(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + return (qdevice_net_msg_received_unexpected_msg(instance, msg, "ask for vote")); +} + +static int +qdevice_net_msg_received_ask_for_vote_reply(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + enum tlv_vote result_vote; + int ring_id_is_valid; + + if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + qdevice_log(LOG_ERR, "Received unexpected ask for vote reply message. " + "Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG; + return (-1); + } + + if (!msg->vote_set || !msg->seq_number_set || !msg->ring_id_set) { + qdevice_log(LOG_ERR, "Received ask for vote reply message without " + "required options. Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + return (-1); + } + + qdevice_log(LOG_DEBUG, "Received ask for vote reply"); + qdevice_log(LOG_DEBUG, " seq = "UTILS_PRI_MSG_SEQ, msg->seq_number); + qdevice_log(LOG_DEBUG, " vote = %s", tlv_vote_to_str(msg->vote)); + qdevice_log(LOG_DEBUG, " ring id = ("UTILS_PRI_RING_ID")", + msg->ring_id.node_id, msg->ring_id.seq); + + result_vote = msg->vote; + + if (!tlv_ring_id_eq(&msg->ring_id, &instance->last_sent_ring_id)) { + ring_id_is_valid = 0; + qdevice_log(LOG_DEBUG, "Received ask for vote reply with old ring id."); + } else { + ring_id_is_valid = 1; + } + + if (qdevice_net_algorithm_ask_for_vote_reply_received(instance, msg->seq_number, + &msg->ring_id, ring_id_is_valid, &result_vote) != 0) { + qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting."); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_ASK_FOR_VOTE_REPLY_ERR; + return (-1); + } else { + qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote)); + } + + if (qdevice_net_cast_vote_timer_update(instance, result_vote) != 0) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + return (-1); + } + + return (0); +} + +static int +qdevice_net_msg_received_vote_info(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + struct send_buffer_list_entry *send_buffer; + enum tlv_vote result_vote; + int ring_id_is_valid; + + if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + qdevice_log(LOG_ERR, "Received unexpected vote info message. " + "Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG; + return (-1); + } + + if (!msg->vote_set || !msg->seq_number_set || !msg->ring_id_set) { + qdevice_log(LOG_ERR, "Received node list reply message without " + "required options. Disconnecting from server"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + return (-1); + } + + qdevice_log(LOG_DEBUG, "Received vote info"); + qdevice_log(LOG_DEBUG, " seq = "UTILS_PRI_MSG_SEQ, msg->seq_number); + qdevice_log(LOG_DEBUG, " vote = %s", tlv_vote_to_str(msg->vote)); + qdevice_log(LOG_DEBUG, " ring id = ("UTILS_PRI_RING_ID")", + msg->ring_id.node_id, msg->ring_id.seq); + + result_vote = msg->vote; + + if (!tlv_ring_id_eq(&msg->ring_id, &instance->last_sent_ring_id)) { + ring_id_is_valid = 0; + qdevice_log(LOG_DEBUG, "Received vote info with old ring id."); + } else { + ring_id_is_valid = 1; + } + + if (qdevice_net_algorithm_vote_info_received(instance, msg->seq_number, + &msg->ring_id, ring_id_is_valid, &result_vote) != 0) { + qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting."); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTE_INFO_ERR; + return (-1); + } else { + qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote)); + } + + if (qdevice_net_cast_vote_timer_update(instance, result_vote) != 0) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + return (-1); + } + + /* + * Create reply message + */ + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't allocate send list buffer for " + "vote info reply msg"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + return (-1); + } + + if (msg_create_vote_info_reply(&send_buffer->buffer, msg->seq_number) == 0) { + qdevice_log(LOG_ERR, "Can't allocate send buffer for " + "vote info reply list msg"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + return (-1); + } + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + return (0); +} + +static int +qdevice_net_msg_received_vote_info_reply(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + return (qdevice_net_msg_received_unexpected_msg(instance, msg, "vote info reply")); +} + +static int +qdevice_net_msg_received_heuristics_change(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + + return (qdevice_net_msg_received_unexpected_msg(instance, msg, "heuristics change")); +} + +static int +qdevice_net_msg_received_heuristics_change_reply(struct qdevice_net_instance *instance, + const struct msg_decoded *msg) +{ + enum tlv_vote result_vote; + int ring_id_is_valid; + + if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) { + qdevice_log(LOG_ERR, "Received unexpected heuristics change reply message. " + "Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG; + return (-1); + } + + if (!msg->vote_set || !msg->seq_number_set || !msg->ring_id_set || + msg->heuristics == TLV_HEURISTICS_UNDEFINED) { + qdevice_log(LOG_ERR, "Received heuristics change reply message without " + "required options. Disconnecting from server"); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_REQUIRED_OPTION_MISSING; + return (-1); + } + + qdevice_log(LOG_DEBUG, "Received heuristics change reply"); + qdevice_log(LOG_DEBUG, " seq = "UTILS_PRI_MSG_SEQ, msg->seq_number); + qdevice_log(LOG_DEBUG, " vote = %s", tlv_vote_to_str(msg->vote)); + qdevice_log(LOG_DEBUG, " ring id = ("UTILS_PRI_RING_ID")", + msg->ring_id.node_id, msg->ring_id.seq); + qdevice_log(LOG_DEBUG, " heuristics = %s", tlv_heuristics_to_str(msg->heuristics)); + + result_vote = msg->vote; + + if (!tlv_ring_id_eq(&msg->ring_id, &instance->last_sent_ring_id)) { + ring_id_is_valid = 0; + qdevice_log(LOG_DEBUG, "Received heuristics change reply with old ring id."); + } else { + ring_id_is_valid = 1; + } + + if (qdevice_net_algorithm_heuristics_change_reply_received(instance, msg->seq_number, + &msg->ring_id, ring_id_is_valid, msg->heuristics, &result_vote) != 0) { + qdevice_log(LOG_DEBUG, "Algorithm returned error. Disconnecting."); + + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_HEURISTICS_CHANGE_REPLY_ERR; + return (-1); + } else { + qdevice_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote)); + } + + if (qdevice_net_cast_vote_timer_update(instance, result_vote) != 0) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER; + return (-1); + } + + return (0); +} + +int +qdevice_net_msg_received(struct qdevice_net_instance *instance) +{ + struct msg_decoded msg; + int res; + int ret_val; + int msg_processed; + + msg_decoded_init(&msg); + + res = msg_decode(&instance->receive_buffer, &msg); + if (res != 0) { + /* + * Error occurred. Disconnect. + */ + qdevice_net_msg_received_log_msg_decode_error(res); + qdevice_log(LOG_ERR, "Disconnecting from server"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_MSG_DECODE_ERROR; + + return (-1); + } + + ret_val = 0; + + msg_processed = 0; + + switch (msg.type) { + case MSG_TYPE_INIT: + msg_processed = 1; + ret_val = qdevice_net_msg_received_init(instance, &msg); + break; + case MSG_TYPE_PREINIT: + msg_processed = 1; + ret_val = qdevice_net_msg_received_preinit(instance, &msg); + break; + case MSG_TYPE_PREINIT_REPLY: + msg_processed = 1; + ret_val = qdevice_net_msg_received_preinit_reply(instance, &msg); + break; + case MSG_TYPE_STARTTLS: + msg_processed = 1; + ret_val = qdevice_net_msg_received_starttls(instance, &msg); + break; + case MSG_TYPE_SERVER_ERROR: + msg_processed = 1; + ret_val = qdevice_net_msg_received_server_error(instance, &msg); + break; + case MSG_TYPE_INIT_REPLY: + msg_processed = 1; + ret_val = qdevice_net_msg_received_init_reply(instance, &msg); + break; + case MSG_TYPE_SET_OPTION: + msg_processed = 1; + ret_val = qdevice_net_msg_received_set_option(instance, &msg); + break; + case MSG_TYPE_SET_OPTION_REPLY: + msg_processed = 1; + ret_val = qdevice_net_msg_received_set_option_reply(instance, &msg); + break; + case MSG_TYPE_ECHO_REQUEST: + msg_processed = 1; + ret_val = qdevice_net_msg_received_echo_request(instance, &msg); + break; + case MSG_TYPE_ECHO_REPLY: + msg_processed = 1; + ret_val = qdevice_net_msg_received_echo_reply(instance, &msg); + break; + case MSG_TYPE_NODE_LIST: + msg_processed = 1; + ret_val = qdevice_net_msg_received_node_list(instance, &msg); + break; + case MSG_TYPE_NODE_LIST_REPLY: + msg_processed = 1; + ret_val = qdevice_net_msg_received_node_list_reply(instance, &msg); + break; + case MSG_TYPE_ASK_FOR_VOTE: + msg_processed = 1; + ret_val = qdevice_net_msg_received_ask_for_vote(instance, &msg); + break; + case MSG_TYPE_ASK_FOR_VOTE_REPLY: + msg_processed = 1; + ret_val = qdevice_net_msg_received_ask_for_vote_reply(instance, &msg); + break; + case MSG_TYPE_VOTE_INFO: + msg_processed = 1; + ret_val = qdevice_net_msg_received_vote_info(instance, &msg); + break; + case MSG_TYPE_VOTE_INFO_REPLY: + msg_processed = 1; + ret_val = qdevice_net_msg_received_vote_info_reply(instance, &msg); + break; + case MSG_TYPE_HEURISTICS_CHANGE: + msg_processed = 1; + ret_val = qdevice_net_msg_received_heuristics_change(instance, &msg); + break; + case MSG_TYPE_HEURISTICS_CHANGE_REPLY: + msg_processed = 1; + ret_val = qdevice_net_msg_received_heuristics_change_reply(instance, &msg); + /* + * Default is not defined intentionally. Compiler shows warning when msg type is added + */ + } + + if (!msg_processed) { + qdevice_log(LOG_ERR, "Received unsupported message %u. " + "Disconnecting from server", msg.type); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNEXPECTED_MSG; + + ret_val = -1; + } + + msg_decoded_destroy(&msg); + + return (ret_val); +} diff --git a/qdevices/qdevice-net-msg-received.h b/qdevices/qdevice-net-msg-received.h new file mode 100644 index 0000000..12be9f7 --- /dev/null +++ b/qdevices/qdevice-net-msg-received.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_MSG_RECEIVED_H_ +#define _QDEVICE_NET_MSG_RECEIVED_H_ + +#include "qdevice-net-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_msg_received(struct qdevice_net_instance *instance); + + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_MSG_RECEIVED_H_ */ diff --git a/qdevices/qdevice-net-nss.c b/qdevices/qdevice-net-nss.c new file mode 100644 index 0000000..7907c2d --- /dev/null +++ b/qdevices/qdevice-net-nss.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "qdevice-log.h" +#include "qdevice-net-nss.h" +#include "qdevice-net-instance.h" +#include "qnet-config.h" + +SECStatus +qdevice_net_nss_bad_cert_hook(void *arg, PRFileDesc *fd) { + if (PR_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE || + PR_GetError() == SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE || + PR_GetError() == SEC_ERROR_CRL_EXPIRED || + PR_GetError() == SEC_ERROR_KRL_EXPIRED || + PR_GetError() == SSL_ERROR_EXPIRED_CERT_ALERT) { + qdevice_log(LOG_WARNING, "Server certificate is expired."); + + return (SECSuccess); + } + + qdevice_log_nss(LOG_ERR, "Server certificate verification failure."); + + return (SECFailure); +} + +SECStatus +qdevice_net_nss_get_client_auth_data(void *arg, PRFileDesc *sock, struct CERTDistNamesStr *caNames, + struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey) +{ + struct qdevice_net_instance *instance; + + qdevice_log(LOG_DEBUG, "Sending client auth data."); + + instance = (struct qdevice_net_instance *)arg; + + instance->tls_client_cert_sent = 1; + + return (NSS_GetClientAuthData((void *)instance->advanced_settings->net_nss_client_cert_nickname, + sock, caNames, pRetCert, pRetKey)); +} diff --git a/qdevices/qdevice-net-nss.h b/qdevices/qdevice-net-nss.h new file mode 100644 index 0000000..686f615 --- /dev/null +++ b/qdevices/qdevice-net-nss.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_NSS_H_ +#define _QDEVICE_NET_NSS_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern SECStatus qdevice_net_nss_bad_cert_hook(void *arg, PRFileDesc *fd); + +extern SECStatus qdevice_net_nss_get_client_auth_data(void *arg, + PRFileDesc *sock, struct CERTDistNamesStr *caNames, + struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey); + + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_NSS_H_ */ diff --git a/qdevices/qdevice-net-poll-array-user-data.h b/qdevices/qdevice-net-poll-array-user-data.h new file mode 100644 index 0000000..b696690 --- /dev/null +++ b/qdevices/qdevice-net-poll-array-user-data.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_POLL_ARRAY_USER_DATA_H_ +#define _QDEVICE_NET_POLL_ARRAY_USER_DATA_H_ + +#include "unix-socket-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum qdevice_net_poll_array_user_data_type { + QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM, + QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP, + QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET, + QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET, + QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT, + QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND, + QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV, + QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV, +}; + +struct qdevice_net_poll_array_user_data { + enum qdevice_net_poll_array_user_data_type type; + struct unix_socket_client *ipc_client; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_POLL_ARRAY_USER_DATA_H_ */ diff --git a/qdevices/qdevice-net-poll.c b/qdevices/qdevice-net-poll.c new file mode 100644 index 0000000..e90f2b9 --- /dev/null +++ b/qdevices/qdevice-net-poll.c @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qdevice-cmap.h" +#include "qdevice-net-poll.h" +#include "qdevice-log.h" +#include "qdevice-net-send.h" +#include "qdevice-net-socket.h" +#include "qdevice-votequorum.h" +#include "qdevice-ipc.h" +#include "qdevice-net-poll-array-user-data.h" +#include "qdevice-heuristics.h" +#include "qdevice-heuristics-cmd.h" + +/* + * Needed for creating nspr handle from unix fd + */ +#include + +static void +qdevice_net_poll_read_socket(struct qdevice_net_instance *instance) +{ + + if (qdevice_net_socket_read(instance) == -1) { + instance->schedule_disconnect = 1; + } +} + +static void +qdevice_net_poll_read_votequorum(struct qdevice_net_instance *instance) +{ + + if (qdevice_votequorum_dispatch(instance->qdevice_instance_ptr) == -1) { + instance->schedule_disconnect = 1; + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED; + } +} + +static void +qdevice_net_poll_read_cmap(struct qdevice_net_instance *instance) +{ + + if (qdevice_cmap_dispatch(instance->qdevice_instance_ptr) == -1) { + instance->schedule_disconnect = 1; + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED; + } +} + +static void +qdevice_net_poll_write_socket(struct qdevice_net_instance *instance, const PRPollDesc *pfd) +{ + int res; + + if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) { + res = nss_sock_non_blocking_client_succeeded(pfd); + if (res == -1) { + /* + * Connect failed -> try next + */ + res = nss_sock_non_blocking_client_try_next(&instance->non_blocking_client); + if (res == -1) { + qdevice_log_nss(LOG_ERR, "Can't connect to qnetd host."); + nss_sock_non_blocking_client_destroy(&instance->non_blocking_client); + } + } else if (res == 0) { + /* + * Poll again + */ + } else if (res == 1) { + /* + * Connect success + */ + instance->socket = instance->non_blocking_client.socket; + nss_sock_non_blocking_client_destroy(&instance->non_blocking_client); + instance->non_blocking_client.socket = NULL; + + instance->state = QDEVICE_NET_INSTANCE_STATE_SENDING_PREINIT_REPLY; + + qdevice_log(LOG_DEBUG, "Sending preinit msg to qnetd"); + if (qdevice_net_send_preinit(instance) != 0) { + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + instance->schedule_disconnect = 1; + } + } else { + qdevice_log(LOG_CRIT, "Unhandled nss_sock_non_blocking_client_succeeded"); + exit(1); + } + } else { + if (qdevice_net_socket_write(instance) == -1) { + instance->schedule_disconnect = 1; + } + } +} + +static void +qdevice_net_poll_err_socket(struct qdevice_net_instance *instance, const PRPollDesc *pfd) +{ + + if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) { + /* + * Workaround for RHEL<7. Pollout is never set for nonblocking connect (doesn't work + * only with poll, select works as expected!???). + * So test if client is still valid and if pollout was not already called (ensured + * by default because of order in PR_Poll). + * If both applies it's possible to emulate pollout set by calling poll_write. + */ + if (!instance->non_blocking_client.destroyed) { + qdevice_net_poll_write_socket(instance, pfd); + } + } else { + qdevice_log(LOG_ERR, "POLL_ERR (%u) on main socket", pfd->out_flags); + + instance->schedule_disconnect = 1; + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION; + } +} + +static void +qdevice_net_poll_read_heuristics_log(struct qdevice_net_instance *instance) +{ + int res; + + res = qdevice_heuristics_log_read_from_pipe(&instance->qdevice_instance_ptr->heuristics_instance); + if (res == -1) { + instance->schedule_disconnect = 1; + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED; + } +} + +static void +qdevice_net_poll_read_heuristics_cmd(struct qdevice_net_instance *instance) +{ + int res; + + res = qdevice_heuristics_cmd_read_from_pipe(&instance->qdevice_instance_ptr->heuristics_instance); + if (res == -1) { + instance->schedule_disconnect = 1; + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED; + } +} + +static void +qdevice_net_poll_write_heuristics_cmd(struct qdevice_net_instance *instance) +{ + int res; + + res = qdevice_heuristics_cmd_write(&instance->qdevice_instance_ptr->heuristics_instance); + if (res == -1) { + instance->schedule_disconnect = 1; + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_CANT_SEND_RECEIVE_MSG; + } +} + +static void +qdevice_net_poll_read_ipc_socket(struct qdevice_net_instance *instance) +{ + struct unix_socket_client *client; + PRFileDesc *prfd; + struct qdevice_ipc_user_data *user_data; + + if (qdevice_ipc_accept(instance->qdevice_instance_ptr, &client) != 0) { + return ; + } + + prfd = PR_CreateSocketPollFd(client->socket); + if (prfd == NULL) { + qdevice_log_nss(LOG_CRIT, "Can't create NSPR poll fd for IPC client. " + "Disconnecting client"); + qdevice_ipc_client_disconnect(instance->qdevice_instance_ptr, client); + + return ; + } + + user_data = (struct qdevice_ipc_user_data *)client->user_data; + user_data->model_data = (void *)prfd; +} + +static PRPollDesc * +qdevice_net_pr_poll_array_create(struct qdevice_net_instance *instance) +{ + struct pr_poll_array *poll_array; + PRPollDesc *poll_desc; + struct qdevice_net_poll_array_user_data *user_data; + struct unix_socket_client *ipc_client; + const struct unix_socket_client_list *ipc_client_list; + struct qdevice_ipc_user_data *qdevice_ipc_user_data; + + poll_array = &instance->poll_array; + ipc_client_list = &instance->qdevice_instance_ptr->local_ipc.clients; + + if (qdevice_ipc_is_closed(instance->qdevice_instance_ptr)) { + qdevice_log(LOG_DEBUG, "Local socket is closed"); + instance->schedule_disconnect = 1; + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED; + + return (NULL); + } + + pr_poll_array_clean(poll_array); + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + poll_desc->fd = instance->votequorum_poll_fd; + poll_desc->in_flags = PR_POLL_READ; + user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM; + + if (!instance->qdevice_instance_ptr->sync_in_progress) { + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + poll_desc->fd = instance->cmap_poll_fd; + poll_desc->in_flags = PR_POLL_READ; + user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP; + } + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + poll_desc->fd = instance->ipc_socket_poll_fd; + poll_desc->in_flags = PR_POLL_READ; + user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET; + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + + poll_desc->fd = instance->heuristics_pipe_log_recv_poll_fd; + poll_desc->in_flags = PR_POLL_READ; + user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV; + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + + poll_desc->fd = instance->heuristics_pipe_cmd_recv_poll_fd; + poll_desc->in_flags = PR_POLL_READ; + user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV; + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + + if (!send_buffer_list_empty( + &instance->qdevice_instance_ptr->heuristics_instance.cmd_out_buffer_list)) { + poll_desc->fd = instance->heuristics_pipe_cmd_send_poll_fd; + poll_desc->in_flags = PR_POLL_WRITE; + user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND; + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + } + + if (instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT || + !instance->non_blocking_client.destroyed) { + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + + user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET; + + if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) { + poll_desc->fd = instance->non_blocking_client.socket; + poll_desc->in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT; + } else { + poll_desc->fd = instance->socket; + poll_desc->in_flags = PR_POLL_READ; + + if (!send_buffer_list_empty(&instance->send_buffer_list)) { + poll_desc->in_flags |= PR_POLL_WRITE; + } + } + } + + TAILQ_FOREACH(ipc_client, ipc_client_list, entries) { + if (!ipc_client->reading_line && !ipc_client->writing_buffer) { + continue; + } + + if (pr_poll_array_add(poll_array, &poll_desc, (void **)&user_data) < 0) { + return (NULL); + } + + qdevice_ipc_user_data = (struct qdevice_ipc_user_data *)ipc_client->user_data; + poll_desc->fd = (PRFileDesc *)qdevice_ipc_user_data->model_data; + if (ipc_client->reading_line) { + poll_desc->in_flags |= PR_POLL_READ; + } + + if (ipc_client->writing_buffer) { + poll_desc->in_flags |= PR_POLL_WRITE; + } + + user_data->type = QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT; + user_data->ipc_client = ipc_client; + } + + pr_poll_array_gc(poll_array); + + return (poll_array->array); +} + +int +qdevice_net_poll(struct qdevice_net_instance *instance) +{ + PRPollDesc *pfds; + PRFileDesc *prfd; + PRInt32 poll_res; + ssize_t i; + struct qdevice_net_poll_array_user_data *user_data; + struct unix_socket_client *ipc_client; + struct qdevice_ipc_user_data *qdevice_ipc_user_data; + int case_processed; + + pfds = qdevice_net_pr_poll_array_create(instance); + if (pfds == NULL) { + return (-1); + } + + instance->schedule_disconnect = 0; + + if ((poll_res = PR_Poll(pfds, pr_poll_array_size(&instance->poll_array), + timer_list_time_to_expire(&instance->main_timer_list))) > 0) { + for (i = 0; i < pr_poll_array_size(&instance->poll_array); i++) { + user_data = pr_poll_array_get_user_data(&instance->poll_array, i); + + ipc_client = user_data->ipc_client; + + if (pfds[i].out_flags & PR_POLL_READ) { + case_processed = 0; + + switch (user_data->type) { + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET: + case_processed = 1; + qdevice_net_poll_read_socket(instance); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM: + case_processed = 1; + qdevice_net_poll_read_votequorum(instance); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP: + case_processed = 1; + qdevice_net_poll_read_cmap(instance); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET: + case_processed = 1; + qdevice_net_poll_read_ipc_socket(instance); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT: + case_processed = 1; + qdevice_ipc_io_read(instance->qdevice_instance_ptr, ipc_client); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND: + /* + * Read on heuristics cmd send fd shouldn't happen + */ + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV: + case_processed = 1; + qdevice_net_poll_read_heuristics_cmd(instance); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV: + case_processed = 1; + qdevice_net_poll_read_heuristics_log(instance); + break; + /* + * Default is not defined intentionally. Compiler shows warning when + * new poll_array_user_data_type is added + */ + } + + if (!case_processed) { + qdevice_log(LOG_CRIT, "Unhandled read on poll descriptor %u", i); + exit(1); + } + } + + if (!instance->schedule_disconnect && pfds[i].out_flags & PR_POLL_WRITE) { + case_processed = 0; + + switch (user_data->type) { + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET: + case_processed = 1; + qdevice_net_poll_write_socket(instance, &pfds[i]); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT: + case_processed = 1; + qdevice_ipc_io_write(instance->qdevice_instance_ptr, ipc_client); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND: + case_processed = 1; + qdevice_net_poll_write_heuristics_cmd(instance); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM: + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP: + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET: + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV: + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV: + /* + * Write on votequorum, cmap, ipc socket and + * heuristics log shouldn't happen. + */ + break; + /* + * Default is not defined intentionally. Compiler shows warning when + * new poll_array_user_data_type is added + */ + } + + if (!case_processed) { + qdevice_log(LOG_CRIT, "Unhandled write on poll descriptor %u", i); + exit(1); + } + } + + if (!instance->schedule_disconnect && + (pfds[i].out_flags & (PR_POLL_ERR|PR_POLL_NVAL|PR_POLL_HUP|PR_POLL_EXCEPT)) && + !(pfds[i].out_flags & (PR_POLL_READ|PR_POLL_WRITE))) { + case_processed = 0; + + switch (user_data->type) { + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_SOCKET: + case_processed = 1; + qdevice_net_poll_err_socket(instance, &pfds[i]); + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET: + case_processed = 1; + if (pfds[i].out_flags != PR_POLL_NVAL) { + qdevice_log(LOG_CRIT, "POLLERR (%u) on local socket", + pfds[i].out_flags); + exit(1); + } else { + qdevice_log(LOG_DEBUG, "Local socket is closed"); + instance->schedule_disconnect = 1; + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED; + } + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT: + case_processed = 1; + qdevice_log(LOG_DEBUG, "POLL_ERR (%u) on ipc client socket. " + "Disconnecting.", pfds[i].out_flags); + ipc_client->schedule_disconnect = 1; + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_VOTEQUORUM: + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_CMAP: + case_processed = 1; + qdevice_log(LOG_DEBUG, "POLL_ERR (%u) on corosync socket. " + "Disconnecting.", pfds[i].out_flags); + + instance->schedule_disconnect = 1; + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED; + break; + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_LOG_RECV: + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_RECV: + case QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_HEURISTICS_CMD_SEND: + case_processed = 1; + + /* + * Closed pipe doesn't mean return of PR_POLL_READ. To display + * better log message, we call read log as if POLL_READ would + * be set. + */ + qdevice_net_poll_read_heuristics_log(instance); + + qdevice_log(LOG_DEBUG, "POLL_ERR (%u) on heuristics pipe. " + "Disconnecting.", pfds[i].out_flags); + + instance->schedule_disconnect = 1; + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED; + break; + /* + * Default is not defined intentionally. Compiler shows warning when + * new poll_array_user_data_type is added + */ + } + + if (!case_processed) { + qdevice_log(LOG_CRIT, "Unhandled error on poll descriptor %u", i); + exit(1); + } + } + + if (user_data->type == QDEVICE_NET_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT && + ipc_client->schedule_disconnect) { + qdevice_ipc_user_data = (struct qdevice_ipc_user_data *)ipc_client->user_data; + prfd = (PRFileDesc *)qdevice_ipc_user_data->model_data; + + if (PR_DestroySocketPollFd(prfd) != PR_SUCCESS) { + qdevice_log_nss(LOG_WARNING, "Unable to destroy client IPC poll socket fd"); + } + + qdevice_ipc_client_disconnect(instance->qdevice_instance_ptr, ipc_client); + } + } + } + + if (!instance->schedule_disconnect) { + timer_list_expire(&instance->main_timer_list); + } + + if (instance->schedule_disconnect) { + /* + * Schedule disconnect can be set by this function, by some timer_list callback + * or cmap/votequorum callbacks + */ + return (-1); + } + + return (0); +} diff --git a/qdevices/qdevice-net-poll.h b/qdevices/qdevice-net-poll.h new file mode 100644 index 0000000..fe7732f --- /dev/null +++ b/qdevices/qdevice-net-poll.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_POLL_H_ +#define _QDEVICE_NET_POLL_H_ + + +#include "qdevice-net-instance.h" + +extern int qdevice_net_poll(struct qdevice_net_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_POLL_H_ */ diff --git a/qdevices/qdevice-net-send.c b/qdevices/qdevice-net-send.c new file mode 100644 index 0000000..3709590 --- /dev/null +++ b/qdevices/qdevice-net-send.c @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qdevice-log.h" +#include "qdevice-log-debug.h" +#include "qdevice-net-send.h" +#include "qdevice-net-votequorum.h" +#include "msg.h" +#include "utils.h" + +int +qdevice_net_send_echo_request(struct qdevice_net_instance *instance) +{ + struct send_buffer_list_entry *send_buffer; + + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_CRIT, "Can't allocate send list buffer for reply msg."); + + return (-1); + } + + instance->echo_request_expected_msg_seq_num++; + + if (msg_create_echo_request(&send_buffer->buffer, 1, + instance->echo_request_expected_msg_seq_num) == 0) { + qdevice_log(LOG_ERR, "Can't allocate send buffer for echo request msg"); + + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + return (0); +} + +int +qdevice_net_send_preinit(struct qdevice_net_instance *instance) +{ + struct send_buffer_list_entry *send_buffer; + + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't allocate send list buffer for preinit msg"); + + return (-1); + } + + if (msg_create_preinit(&send_buffer->buffer, instance->cluster_name, 1, + instance->last_msg_seq_num) == 0) { + qdevice_log(LOG_ERR, "Can't allocate buffer"); + + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + return (-1); + } + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_PREINIT_REPLY; + + return (0); +} + +int +qdevice_net_send_init(struct qdevice_net_instance *instance) +{ + enum msg_type *supported_msgs; + size_t no_supported_msgs; + enum tlv_opt_type *supported_opts; + size_t no_supported_opts; + struct send_buffer_list_entry *send_buffer; + struct tlv_ring_id tlv_rid; + + tlv_get_supported_options(&supported_opts, &no_supported_opts); + msg_get_supported_messages(&supported_msgs, &no_supported_msgs); + instance->last_msg_seq_num++; + + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't allocate send list buffer for init msg"); + + return (-1); + } + + qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, + &instance->qdevice_instance_ptr->vq_node_list_ring_id); + + if (msg_create_init(&send_buffer->buffer, 1, instance->last_msg_seq_num, + instance->decision_algorithm, + supported_msgs, no_supported_msgs, supported_opts, no_supported_opts, + instance->qdevice_instance_ptr->node_id, instance->heartbeat_interval, + &instance->tie_breaker, &tlv_rid) == 0) { + qdevice_log(LOG_ERR, "Can't allocate send buffer for init msg"); + + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + return (-1); + } + + memcpy(&instance->last_sent_ring_id, &tlv_rid, sizeof(instance->last_sent_ring_id)); + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_INIT_REPLY; + + return (0); +} + +int +qdevice_net_send_ask_for_vote(struct qdevice_net_instance *instance) +{ + struct send_buffer_list_entry *send_buffer; + + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't allocate send list buffer for ask for vote msg"); + + return (-1); + } + + instance->last_msg_seq_num++; + + qdevice_log(LOG_DEBUG, "Sending ask for vote seq = "UTILS_PRI_MSG_SEQ, + instance->last_msg_seq_num); + + if (msg_create_ask_for_vote(&send_buffer->buffer, instance->last_msg_seq_num) == 0) { + qdevice_log(LOG_ERR, "Can't allocate send buffer for ask for vote msg"); + + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + return (-1); + } + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + return (0); +} + +int +qdevice_net_send_config_node_list(struct qdevice_net_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version, + int initial) +{ + struct send_buffer_list_entry *send_buffer; + + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't allocate send list buffer for config " + "node list msg"); + + return (-1); + } + + instance->last_msg_seq_num++; + + qdevice_log(LOG_DEBUG, "Sending config node list seq = "UTILS_PRI_MSG_SEQ, + instance->last_msg_seq_num); + qdevice_log_debug_dump_node_list(nlist); + + if (msg_create_node_list(&send_buffer->buffer, instance->last_msg_seq_num, + (initial ? TLV_NODE_LIST_TYPE_INITIAL_CONFIG : TLV_NODE_LIST_TYPE_CHANGED_CONFIG), + 0, NULL, config_version_set, config_version, 0, TLV_QUORATE_INQUORATE, + 0, TLV_HEURISTICS_UNDEFINED, nlist) == 0) { + qdevice_log(LOG_ERR, "Can't allocate send buffer for config list msg"); + + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + return (-1); + } + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + return (0); +} + +int +qdevice_net_send_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics heuristics) +{ + struct send_buffer_list_entry *send_buffer; + + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't allocate send list buffer for heuristics change msg"); + + return (-1); + } + + instance->last_msg_seq_num++; + + qdevice_log(LOG_DEBUG, "Sending heuristics change seq = "UTILS_PRI_MSG_SEQ + ", heuristics = %s", + instance->last_msg_seq_num, tlv_heuristics_to_str(heuristics)); + + if (msg_create_heuristics_change(&send_buffer->buffer, instance->last_msg_seq_num, + heuristics) == 0) { + qdevice_log(LOG_ERR, "Can't allocate send buffer for heuristics change msg"); + + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + return (-1); + } + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + return (0); +} + +int +qdevice_net_send_membership_node_list(struct qdevice_net_instance *instance, + const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], enum tlv_heuristics heuristics) +{ + struct node_list nlist; + struct send_buffer_list_entry *send_buffer; + uint32_t i; + + node_list_init(&nlist); + + for (i = 0; i < node_list_entries; i++) { + if (node_list_add(&nlist, node_list[i], 0, TLV_NODE_STATE_NOT_SET) == NULL) { + qdevice_log(LOG_ERR, "Can't allocate membership node list."); + + node_list_free(&nlist); + + return (-1); + } + } + + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't allocate send list buffer for membership " + "node list msg"); + + node_list_free(&nlist); + + return (-1); + } + + instance->last_msg_seq_num++; + + qdevice_log(LOG_DEBUG, "Sending membership node list seq = "UTILS_PRI_MSG_SEQ", " + "ringid = ("UTILS_PRI_RING_ID"), heuristics = %s.", instance->last_msg_seq_num, + ring_id->node_id, ring_id->seq, tlv_heuristics_to_str(heuristics)); + qdevice_log_debug_dump_node_list(&nlist); + + if (msg_create_node_list(&send_buffer->buffer, instance->last_msg_seq_num, + TLV_NODE_LIST_TYPE_MEMBERSHIP, + 1, ring_id, 0, 0, 0, 0, 1, heuristics, &nlist) == 0) { + qdevice_log(LOG_ERR, "Can't allocate send buffer for membership list msg"); + + node_list_free(&nlist); + + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + return (-1); + } + + memcpy(&instance->last_sent_ring_id, ring_id, sizeof(instance->last_sent_ring_id)); + + node_list_free(&nlist); + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + return (0); +} + +int +qdevice_net_send_quorum_node_list(struct qdevice_net_instance *instance, + enum tlv_quorate quorate, + uint32_t node_list_entries, votequorum_node_t node_list[]) +{ + struct node_list nlist; + struct send_buffer_list_entry *send_buffer; + uint32_t i; + + node_list_init(&nlist); + + for (i = 0; i < node_list_entries; i++) { + if (node_list[i].nodeid == 0) { + continue; + } + + if (node_list_add(&nlist, node_list[i].nodeid, 0, + qdevice_net_votequorum_node_state_to_tlv(node_list[i].state)) == NULL) { + qdevice_log(LOG_ERR, "Can't allocate quorum node list."); + + node_list_free(&nlist); + + return (-1); + } + } + + send_buffer = send_buffer_list_get_new(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_ERR, "Can't allocate send list buffer for quorum " + "node list msg"); + + node_list_free(&nlist); + + return (-1); + } + + instance->last_msg_seq_num++; + + qdevice_log(LOG_DEBUG, "Sending quorum node list seq = "UTILS_PRI_MSG_SEQ", quorate = %u", + instance->last_msg_seq_num, quorate); + qdevice_log_debug_dump_node_list(&nlist); + + if (msg_create_node_list(&send_buffer->buffer, instance->last_msg_seq_num, + TLV_NODE_LIST_TYPE_QUORUM, + 0, NULL, 0, 0, 1, quorate, 0, TLV_HEURISTICS_UNDEFINED, &nlist) == 0) { + qdevice_log(LOG_ERR, "Can't allocate send buffer for quorum list msg"); + + node_list_free(&nlist); + + send_buffer_list_discard_new(&instance->send_buffer_list, send_buffer); + return (-1); + } + + node_list_free(&nlist); + + send_buffer_list_put(&instance->send_buffer_list, send_buffer); + + return (0); +} diff --git a/qdevices/qdevice-net-send.h b/qdevices/qdevice-net-send.h new file mode 100644 index 0000000..08763da --- /dev/null +++ b/qdevices/qdevice-net-send.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_SEND_H_ +#define _QDEVICE_NET_SEND_H_ + +#include + +#include "qdevice-net-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_send_echo_request(struct qdevice_net_instance *instance); + +extern int qdevice_net_send_preinit(struct qdevice_net_instance *instance); + +extern int qdevice_net_send_init(struct qdevice_net_instance *instance); + +extern int qdevice_net_send_ask_for_vote(struct qdevice_net_instance *instance); + +extern int qdevice_net_send_config_node_list(struct qdevice_net_instance *instance, + const struct node_list *nlist, int config_version_set, uint64_t config_version, + int initial); + +extern int qdevice_net_send_heuristics_change(struct qdevice_net_instance *instance, + enum tlv_heuristics heuristics); + +extern int qdevice_net_send_membership_node_list( + struct qdevice_net_instance *instance, const struct tlv_ring_id *ring_id, + uint32_t node_list_entries, uint32_t node_list[], enum tlv_heuristics heuristics); + +extern int qdevice_net_send_quorum_node_list( + struct qdevice_net_instance *instance, enum tlv_quorate quorate, + uint32_t node_list_entries, votequorum_node_t node_list[]); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_SEND_H_ */ diff --git a/qdevices/qdevice-net-socket.c b/qdevices/qdevice-net-socket.c new file mode 100644 index 0000000..6dc0f3f --- /dev/null +++ b/qdevices/qdevice-net-socket.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "msg.h" +#include "msgio.h" +#include "qnet-config.h" +#include "qdevice-log.h" +#include "qdevice-net-msg-received.h" +#include "qdevice-net-nss.h" +#include "qdevice-net-send.h" +#include "qdevice-net-socket.h" + +/* + * -1 means end of connection (EOF) or some other unhandled error. 0 = success + */ +int +qdevice_net_socket_read(struct qdevice_net_instance *instance) +{ + int res; + int ret_val; + int orig_skipping_msg; + + orig_skipping_msg = instance->skipping_msg; + + res = msgio_read(instance->socket, &instance->receive_buffer, + &instance->msg_already_received_bytes, &instance->skipping_msg); + + if (!orig_skipping_msg && instance->skipping_msg) { + qdevice_log(LOG_DEBUG, "msgio_read set skipping_msg"); + } + + ret_val = 0; + + switch (res) { + case 0: + /* + * Partial read + */ + break; + case -1: + qdevice_log(LOG_DEBUG, "Server closed connection"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION; + ret_val = -1; + break; + case -2: + qdevice_log(LOG_ERR, "Unhandled error when reading from server. " + "Disconnecting from server"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE; + ret_val = -1; + break; + case -3: + qdevice_log(LOG_ERR, "Can't store message header from server. " + "Disconnecting from server"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE; + ret_val = -1; + break; + case -4: + qdevice_log(LOG_ERR, "Can't store message from server. " + "Disconnecting from server"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE; + ret_val = -1; + break; + case -5: + qdevice_log(LOG_WARNING, "Server sent unsupported msg type %u. " + "Disconnecting from server", msg_get_type(&instance->receive_buffer)); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_UNSUPPORTED_MSG; + ret_val = -1; + break; + case -6: + qdevice_log(LOG_WARNING, + "Server wants to send too long message %u bytes. Disconnecting from server", + msg_get_len(&instance->receive_buffer)); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_READ_MESSAGE; + ret_val = -1; + break; + case 1: + /* + * Full message received / skipped + */ + if (!instance->skipping_msg) { + if (qdevice_net_msg_received(instance) == -1) { + ret_val = -1; + } + } else { + qdevice_log(LOG_CRIT, "net_socket_read in skipping msg state"); + exit(1); + } + + instance->skipping_msg = 0; + instance->msg_already_received_bytes = 0; + dynar_clean(&instance->receive_buffer); + break; + default: + qdevice_log(LOG_CRIT, "qdevice_net_socket_read unhandled error %d", res); + exit(1); + break; + } + + return (ret_val); +} + +static int +qdevice_net_socket_write_finished(struct qdevice_net_instance *instance) +{ + PRFileDesc *new_pr_fd; + + if (instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_STARTTLS_BEING_SENT) { + /* + * StartTLS sent to server. Begin with TLS handshake + */ + if ((new_pr_fd = nss_sock_start_ssl_as_client(instance->socket, + instance->advanced_settings->net_nss_qnetd_cn, + qdevice_net_nss_bad_cert_hook, + qdevice_net_nss_get_client_auth_data, + instance, 0, NULL)) == NULL) { + qdevice_log_nss(LOG_ERR, "Can't start TLS"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_START_TLS; + return (-1); + } + + /* + * And send init msg + */ + if (qdevice_net_send_init(instance) != 0) { + instance->disconnect_reason = + QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER; + + return (-1); + } + + instance->socket = new_pr_fd; + instance->using_tls = 1; + } + + return (0); +} + +int +qdevice_net_socket_write(struct qdevice_net_instance *instance) +{ + int res; + struct send_buffer_list_entry *send_buffer; + enum msg_type sent_msg_type; + + send_buffer = send_buffer_list_get_active(&instance->send_buffer_list); + if (send_buffer == NULL) { + qdevice_log(LOG_CRIT, "send_buffer_list_get_active returned NULL"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SEND_MESSAGE; + + return (-1); + } + + res = msgio_write(instance->socket, &send_buffer->buffer, + &send_buffer->msg_already_sent_bytes); + + if (res == 1) { + sent_msg_type = msg_get_type(&send_buffer->buffer); + + send_buffer_list_delete(&instance->send_buffer_list, send_buffer); + + if (sent_msg_type != MSG_TYPE_ECHO_REQUEST) { + if (qdevice_net_socket_write_finished(instance) == -1) { + return (-1); + } + } + } + + if (res == -1) { + qdevice_log_nss(LOG_CRIT, "PR_Send returned 0"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_SERVER_CLOSED_CONNECTION; + return (-1); + } + + if (res == -2) { + qdevice_log_nss(LOG_ERR, "Unhandled error when sending message to server"); + instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SEND_MESSAGE; + + return (-1); + } + + return (0); +} diff --git a/qdevices/qdevice-net-socket.h b/qdevices/qdevice-net-socket.h new file mode 100644 index 0000000..045e4a9 --- /dev/null +++ b/qdevices/qdevice-net-socket.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_NET_SOCKET_H_ +#define _QDEVICE_NET_SOCKET_H_ + +#include "qdevice-net-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qdevice_net_socket_read(struct qdevice_net_instance *instance); + +extern int qdevice_net_socket_write(struct qdevice_net_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_SOCKET_H_ */ diff --git a/qdevices/qdevice-net-votequorum.c b/qdevices/qdevice-net-votequorum.c new file mode 100644 index 0000000..4ede694 --- /dev/null +++ b/qdevices/qdevice-net-votequorum.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qdevice-log.h" +#include "qdevice-net-votequorum.h" + +enum tlv_node_state +qdevice_net_votequorum_node_state_to_tlv(uint32_t votequorum_node_state) +{ + enum tlv_node_state res; + + switch (votequorum_node_state) { + case VOTEQUORUM_NODESTATE_MEMBER: res = TLV_NODE_STATE_MEMBER; break; + case VOTEQUORUM_NODESTATE_DEAD: res = TLV_NODE_STATE_DEAD; break; + case VOTEQUORUM_NODESTATE_LEAVING: res = TLV_NODE_STATE_LEAVING; break; + default: + qdevice_log(LOG_ERR, "qdevice_net_votequorum_node_state_to_tlv: Unhandled votequorum " + "node state %"PRIu32, votequorum_node_state); + exit(1); + break; + } + + return (res); +} + +void +qdevice_net_votequorum_ring_id_to_tlv(struct tlv_ring_id *tlv_rid, + const votequorum_ring_id_t *votequorum_rid) +{ + + tlv_rid->node_id = votequorum_rid->nodeid; + tlv_rid->seq = votequorum_rid->seq; +} diff --git a/qdevices/qdevice-net-votequorum.h b/qdevices/qdevice-net-votequorum.h new file mode 100644 index 0000000..5d62df7 --- /dev/null +++ b/qdevices/qdevice-net-votequorum.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QDEVICE_NET_VOTEQUORUM_H_ +#define _QDEVICE_NET_VOTEQUORUM_H_ + +#include + +#include "qdevice-net-instance.h" +#include "tlv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern enum tlv_node_state qdevice_net_votequorum_node_state_to_tlv( + uint32_t votequorum_node_state); + +extern void qdevice_net_votequorum_ring_id_to_tlv(struct tlv_ring_id *tlv_rid, + const votequorum_ring_id_t *votequorum_rid); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_NET_VOTEQUORUM_H_ */ diff --git a/qdevices/qdevice-votequorum.c b/qdevices/qdevice-votequorum.c new file mode 100644 index 0000000..b0a2e17 --- /dev/null +++ b/qdevices/qdevice-votequorum.c @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "qdevice-config.h" +#include "qdevice-log.h" +#include "qdevice-votequorum.h" +#include "qdevice-model.h" +#include "utils.h" + +static void +qdevice_votequorum_quorum_notify_callback(votequorum_handle_t votequorum_handle, + uint64_t context, uint32_t quorate, + uint32_t node_list_entries, votequorum_node_t node_list[]) +{ + struct qdevice_instance *instance; + uint32_t u32; + + if (votequorum_context_get(votequorum_handle, (void **)&instance) != CS_OK) { + qdevice_log(LOG_CRIT, "Fatal error. Can't get votequorum context"); + exit(1); + } + + instance->sync_in_progress = 0; + + qdevice_log(LOG_DEBUG, "Votequorum quorum notify callback:"); + qdevice_log(LOG_DEBUG, " Quorate = %u", quorate); + + qdevice_log(LOG_DEBUG, " Node list (size = %"PRIu32"):", node_list_entries); + for (u32 = 0; u32 < node_list_entries; u32++) { + qdevice_log(LOG_DEBUG, " %"PRIu32" nodeid = "UTILS_PRI_NODE_ID", state = %"PRIu32, + u32, node_list[u32].nodeid, node_list[u32].state); + } + + if (qdevice_model_votequorum_quorum_notify(instance, quorate, node_list_entries, + node_list) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_model_votequorum_quorum_notify returned error -> exit"); + exit(2); + } + + instance->vq_quorum_quorate = quorate; + instance->vq_quorum_node_list_entries = node_list_entries; + + free(instance->vq_quorum_node_list); + instance->vq_quorum_node_list = malloc(sizeof(*node_list) * node_list_entries); + if (instance->vq_quorum_node_list == NULL) { + qdevice_log(LOG_CRIT, "Can't alloc votequorum node list memory"); + exit(1); + } + memcpy(instance->vq_quorum_node_list, node_list, sizeof(*node_list) * node_list_entries); +} + +static int +qdevice_votequorum_heuristics_exec_result_callback( + void *heuristics_instance_ptr, + uint32_t seq_number, enum qdevice_heuristics_exec_result exec_result) +{ + struct qdevice_heuristics_instance *heuristics_instance; + struct qdevice_instance *instance; + + heuristics_instance = (struct qdevice_heuristics_instance *)heuristics_instance_ptr; + instance = heuristics_instance->qdevice_instance_ptr; + + if (qdevice_heuristics_result_notifier_list_set_active( + &instance->heuristics_instance.exec_result_notifier_list, + qdevice_votequorum_heuristics_exec_result_callback, 0) != 0) { + qdevice_log(LOG_CRIT, "Can't deactivate votequrorum heuristics exec callback notifier"); + exit(2); + } + + qdevice_log(LOG_DEBUG, "Votequorum heuristics exec result callback:"); + qdevice_log(LOG_DEBUG, " seq_number = %"PRIu32", exec_result = %s", + seq_number, qdevice_heuristics_exec_result_to_str(exec_result)); + + if (qdevice_model_votequorum_node_list_heuristics_notify(instance, instance->vq_node_list_ring_id, + instance->vq_node_list_entries, instance->vq_node_list, exec_result) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_votequorum_node_list_heuristics_notify_callback returned error -> exit"); + exit(2); + } + + instance->vq_node_list_initial_heuristics_finished = 1; + instance->vq_node_list_heuristics_result = exec_result; + + return (0); +} + +static void +qdevice_votequorum_node_list_notify_callback(votequorum_handle_t votequorum_handle, + uint64_t context, votequorum_ring_id_t votequorum_ring_id, + uint32_t node_list_entries, uint32_t node_list[]) +{ + struct qdevice_instance *instance; + uint32_t u32; + + if (votequorum_context_get(votequorum_handle, (void **)&instance) != CS_OK) { + qdevice_log(LOG_CRIT, "Fatal error. Can't get votequorum context"); + exit(1); + } + + instance->sync_in_progress = 1; + memcpy(&instance->vq_poll_ring_id, &votequorum_ring_id, sizeof(votequorum_ring_id)); + + qdevice_log(LOG_DEBUG, "Votequorum nodelist notify callback:"); + qdevice_log(LOG_DEBUG, " Ring_id = ("UTILS_PRI_RING_ID")", + votequorum_ring_id.nodeid, votequorum_ring_id.seq); + + qdevice_log(LOG_DEBUG, " Node list (size = %"PRIu32"):", node_list_entries); + for (u32 = 0; u32 < node_list_entries; u32++) { + qdevice_log(LOG_DEBUG, " %"PRIu32" nodeid = "UTILS_PRI_NODE_ID, + u32, node_list[u32]); + } + + if (qdevice_model_votequorum_node_list_notify(instance, votequorum_ring_id, node_list_entries, + node_list) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_votequorum_node_list_notify_callback returned error -> exit"); + exit(2); + } + + if (qdevice_heuristics_result_notifier_list_set_active( + &instance->heuristics_instance.exec_result_notifier_list, + qdevice_votequorum_heuristics_exec_result_callback, 1) != 0) { + qdevice_log(LOG_CRIT, "Can't activate votequrorum heuristics exec callback notifier"); + exit(2); + } + + if (qdevice_heuristics_exec(&instance->heuristics_instance, instance->sync_in_progress) != 0) { + qdevice_log(LOG_CRIT, "Can't start heuristics -> exit"); + exit(2); + } + + instance->vq_node_list_initial_ring_id_set = 1; + memcpy(&instance->vq_node_list_ring_id, &votequorum_ring_id, sizeof(votequorum_ring_id)); + instance->vq_node_list_entries = node_list_entries; + free(instance->vq_node_list); + instance->vq_node_list = malloc(sizeof(*node_list) * node_list_entries); + if (instance->vq_node_list == NULL) { + qdevice_log(LOG_CRIT, "Can't alloc votequorum node list memory"); + exit(1); + } + memcpy(instance->vq_node_list, node_list, sizeof(*node_list) * node_list_entries); +} + +static void +qdevice_votequorum_expected_votes_notify_callback(votequorum_handle_t votequorum_handle, + uint64_t context, uint32_t expected_votes) +{ + struct qdevice_instance *instance; + + if (votequorum_context_get(votequorum_handle, (void **)&instance) != CS_OK) { + qdevice_log(LOG_CRIT, "Fatal error. Can't get votequorum context"); + exit(1); + } + + qdevice_log(LOG_DEBUG, "Votequorum expected_votes notify callback:"); + qdevice_log(LOG_DEBUG, " Expected_votes: "UTILS_PRI_EXPECTED_VOTES, expected_votes); + + if (qdevice_model_votequorum_expected_votes_notify(instance, expected_votes) != 0) { + qdevice_log(LOG_DEBUG, "qdevice_votequorum_expected_votes_notify_callback returned error -> exit"); + exit(2); + } + + instance->vq_expected_votes = expected_votes; +} + +void +qdevice_votequorum_init(struct qdevice_instance *instance) +{ + votequorum_callbacks_t votequorum_callbacks; + votequorum_handle_t votequorum_handle; + cs_error_t res; + int no_retries; + struct votequorum_info vq_info; + + memset(&votequorum_callbacks, 0, sizeof(votequorum_callbacks)); + + votequorum_callbacks.votequorum_quorum_notify_fn = + qdevice_votequorum_quorum_notify_callback; + + votequorum_callbacks.votequorum_nodelist_notify_fn = + qdevice_votequorum_node_list_notify_callback; + + votequorum_callbacks.votequorum_expectedvotes_notify_fn = + qdevice_votequorum_expected_votes_notify_callback; + + no_retries = 0; + + while ((res = votequorum_initialize(&votequorum_handle, + &votequorum_callbacks)) == CS_ERR_TRY_AGAIN && + no_retries++ < instance->advanced_settings->max_cs_try_again) { + (void)poll(NULL, 0, 1000); + } + + if (res != CS_OK) { + qdevice_log(LOG_CRIT, "Failed to initialize the votequorum API. Error %s", cs_strerror(res)); + exit(1); + } + + if ((res = votequorum_qdevice_register(votequorum_handle, + instance->advanced_settings->votequorum_device_name)) != CS_OK) { + qdevice_log(LOG_CRIT, "Can't register votequorum device. Error %s", cs_strerror(res)); + exit(1); + } + + if ((res = votequorum_context_set(votequorum_handle, (void *)instance)) != CS_OK) { + qdevice_log(LOG_CRIT, "Can't set votequorum context. Error %s", cs_strerror(res)); + exit(1); + } + + if ((res = votequorum_getinfo(votequorum_handle, VOTEQUORUM_QDEVICE_NODEID, + &vq_info)) != CS_OK) { + qdevice_log(LOG_CRIT, "Can't get votequorum information. Error %s", cs_strerror(res)); + exit(1); + } + instance->vq_expected_votes = vq_info.node_expected_votes; + + instance->votequorum_handle = votequorum_handle; + + votequorum_fd_get(votequorum_handle, &instance->votequorum_poll_fd); + + if ((res = votequorum_trackstart(instance->votequorum_handle, 0, + CS_TRACK_CHANGES)) != CS_OK) { + qdevice_log(LOG_CRIT, "Can't start tracking votequorum changes. Error %s", + cs_strerror(res)); + exit(1); + } + + if (qdevice_heuristics_result_notifier_list_add(&instance->heuristics_instance.exec_result_notifier_list, + qdevice_votequorum_heuristics_exec_result_callback) == NULL) { + qdevice_log(LOG_CRIT, "Can't add votequrorum heuristics exec callback into notifier"); + exit(1); + } +} + +void +qdevice_votequorum_destroy(struct qdevice_instance *instance) +{ + cs_error_t res; + + free(instance->vq_quorum_node_list); instance->vq_quorum_node_list = NULL; + free(instance->vq_node_list); instance->vq_node_list = NULL; + + res = votequorum_trackstop(instance->votequorum_handle); + if (res != CS_OK) { + qdevice_log(LOG_WARNING, "Can't start tracking votequorum changes. Error %s", + cs_strerror(res)); + } + + res = votequorum_qdevice_unregister(instance->votequorum_handle, + instance->advanced_settings->votequorum_device_name); + + if (res != CS_OK) { + qdevice_log(LOG_WARNING, "Unable to unregister votequorum device. Error %s", cs_strerror(res)); + } + + res = votequorum_finalize(instance->votequorum_handle); + if (res != CS_OK) { + qdevice_log(LOG_WARNING, "Unable to finalize votequorum. Error %s", cs_strerror(res)); + } +} + +int +qdevice_votequorum_wait_for_ring_id(struct qdevice_instance *instance) +{ + int no_retries; + + no_retries = 0; + + while (qdevice_votequorum_dispatch(instance) != -1 && + no_retries++ < instance->advanced_settings->max_cs_try_again && + !instance->vq_node_list_initial_ring_id_set) { + (void)poll(NULL, 0, 1000); + } + + if (!instance->vq_node_list_initial_ring_id_set) { + qdevice_log(LOG_CRIT, "Can't get initial votequorum membership information."); + return (-1); + } + + return (0); +} + +int +qdevice_votequorum_dispatch(struct qdevice_instance *instance) +{ + cs_error_t res; + + res = votequorum_dispatch(instance->votequorum_handle, CS_DISPATCH_ALL); + + if (res != CS_OK && res != CS_ERR_TRY_AGAIN) { + qdevice_log(LOG_ERR, "Can't dispatch votequorum messages"); + + return (-1); + } + + return (0); +} + +int +qdevice_votequorum_poll(struct qdevice_instance *instance, int cast_vote) +{ + cs_error_t res; + + instance->vq_last_poll = time(NULL); + instance->vq_last_poll_cast_vote = cast_vote; + + res = votequorum_qdevice_poll(instance->votequorum_handle, + instance->advanced_settings->votequorum_device_name, cast_vote, + instance->vq_poll_ring_id); + + if (res != CS_OK && res != CS_ERR_TRY_AGAIN) { + if (res == CS_ERR_MESSAGE_ERROR) { + qdevice_log(LOG_INFO, "qdevice_votequorum_poll called with old ring id"); + } else { + qdevice_log(LOG_CRIT, "Can't call votequorum_qdevice_poll. Error %s", + cs_strerror(res)); + + return (-1); + } + } + + return (0); +} + +int +qdevice_votequorum_master_wins(struct qdevice_instance *instance, int allow) +{ + cs_error_t res; + int final_allow; + + final_allow = allow; + + if (instance->advanced_settings->master_wins == + QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_OFF && allow) { + qdevice_log(LOG_WARNING, "Allow of master wins is requested, but user forcibly " + "disallowed it. Keeping master wins disallowed."); + + final_allow = 0; + } + + if (instance->advanced_settings->master_wins == + QDEVICE_ADVANCED_SETTINGS_MASTER_WINS_FORCE_ON && !allow) { + qdevice_log(LOG_WARNING, "Disallow of master wins is requested, but user forcibly " + "allowed it. Keeping master wins allowed."); + + final_allow = 1; + } + + res = votequorum_qdevice_master_wins(instance->votequorum_handle, + instance->advanced_settings->votequorum_device_name, final_allow); + + if (res != CS_OK) { + qdevice_log(LOG_CRIT, "Can't set master wins. Error %s", cs_strerror(res)); + + return (-1); + } + + return (0); +} diff --git a/qdevices/qdevice-votequorum.h b/qdevices/qdevice-votequorum.h new file mode 100644 index 0000000..0d01d77 --- /dev/null +++ b/qdevices/qdevice-votequorum.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_VOTEQUORUM_H_ +#define _QDEVICE_VOTEQUORUM_H_ + +#include + +#include "qdevice-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern void qdevice_votequorum_init(struct qdevice_instance *instance); + +extern void qdevice_votequorum_destroy(struct qdevice_instance *instance); + +extern int qdevice_votequorum_dispatch(struct qdevice_instance *instance); + +extern int qdevice_votequorum_poll(struct qdevice_instance *instance, + int cast_vote); + +extern int qdevice_votequorum_wait_for_ring_id( + struct qdevice_instance *instance); + +extern int qdevice_votequorum_master_wins(struct qdevice_instance *instance, + int allow); + +#ifdef __cplusplus +} +#endif + +#endif /* _QDEVICE_VOTEQUORUM_H_ */ diff --git a/qdevices/qnet-config.h b/qdevices/qnet-config.h new file mode 100644 index 0000000..8435b53 --- /dev/null +++ b/qdevices/qnet-config.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNET_CONFIG_H_ +#define _QNET_CONFIG_H_ + +#include + +#include "tlv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * There are "hardcoded" defaults for both qnetd and qdevice-net. It's not so good + * idea to change them as long as you are not 100% sure what you are doing. Also + * most of them can be changed in CLI via advanced_settings (-S). + */ + +#define QNETD_PROGRAM_NAME "corosync-qnetd" +#define QNETD_DEFAULT_HOST_PORT 5403 +#define QNETD_DEFAULT_LISTEN_BACKLOG 10 +#define QNETD_MIN_LISTEN_BACKLOG 1 +#define QNETD_DEFAULT_MAX_CLIENT_SEND_BUFFERS 32 +#define QNETD_MIN_CLIENT_SEND_BUFFERS 2 +#define QNETD_DEFAULT_MAX_CLIENT_SEND_SIZE (1 << 15) +#define QNETD_DEFAULT_MAX_CLIENT_RECEIVE_SIZE (1 << 15) +#define QNETD_MIN_CLIENT_RECEIVE_SEND_SIZE 16 +#define QNETD_DEFAULT_MAX_CLIENTS 0 + +#define QNETD_DEFAULT_NSS_DB_DIR COROSYSCONFDIR "/qnetd/nssdb" +#define QNETD_DEFAULT_CERT_NICKNAME "QNetd Cert" + +#define QNETD_DEFAULT_TLS_SUPPORTED TLV_TLS_SUPPORTED +#define QNETD_DEFAULT_TLS_CLIENT_CERT_REQUIRED 1 + +#define QNETD_DEFAULT_HEARTBEAT_INTERVAL_MIN (1*1000) +#define QNETD_DEFAULT_HEARTBEAT_INTERVAL_MAX (2*60*1000) +#define QNETD_MIN_HEARTBEAT_INTERVAL 1 + +#define QNETD_DEFAULT_DPD_ENABLED 1 +#define QNETD_DEFAULT_DPD_INTERVAL (10*1000) +#define QNETD_MIN_DPD_INTERVAL 1 + +#define QNETD_DEFAULT_LOCK_FILE LOCALSTATEDIR"/run/corosync-qnetd/corosync-qnetd.pid" +#define QNETD_DEFAULT_LOCAL_SOCKET_FILE LOCALSTATEDIR"/run/corosync-qnetd/corosync-qnetd.sock" +#define QNETD_DEFAULT_LOCAL_SOCKET_BACKLOG 10 +#define QNETD_MIN_LOCAL_SOCKET_BACKLOG 1 + +#define QNETD_DEFAULT_IPC_MAX_CLIENTS 10 +#define QNETD_MIN_IPC_MAX_CLIENTS 0 +#define QNETD_DEFAULT_IPC_MAX_RECEIVE_SIZE (4*1024) +#define QNETD_DEFAULT_IPC_MAX_SEND_SIZE (10*1024*1024) +#define QNETD_MIN_IPC_RECEIVE_SEND_SIZE 1024 + +#define QNETD_TOOL_PROGRAM_NAME "corosync-qnetd-tool" + +#define QDEVICE_NET_DEFAULT_NSS_DB_DIR COROSYSCONFDIR "/qdevice/net/nssdb" + +#define QDEVICE_NET_DEFAULT_INITIAL_MSG_RECEIVE_SIZE (1 << 15) +#define QDEVICE_NET_DEFAULT_INITIAL_MSG_SEND_SIZE (1 << 15) +#define QDEVICE_NET_DEFAULT_MIN_MSG_SEND_SIZE QDEVICE_NET_DEFAULT_INITIAL_MSG_SEND_SIZE +#define QDEVICE_NET_DEFAULT_MAX_MSG_RECEIVE_SIZE (1 << 24) +#define QDEVICE_NET_DEFAULT_MAX_SEND_BUFFERS 10 +#define QDEVICE_NET_MIN_MAX_SEND_BUFFERS 2 +#define QDEVICE_NET_MIN_MSG_RECEIVE_SEND_SIZE 16 + +#define QDEVICE_NET_DEFAULT_NSS_QNETD_CN "Qnetd Server" + +#define QDEVICE_NET_DEFAULT_NSS_CLIENT_CERT_NICKNAME "Cluster Cert" + + +#define QDEVICE_NET_DEFAULT_ALGORITHM TLV_DECISION_ALGORITHM_TYPE_FFSPLIT + +#define QDEVICE_NET_DEFAULT_TLS_SUPPORTED TLV_TLS_SUPPORTED + +#define QDEVICE_NET_DEFAULT_TIE_BREAKER_MODE TLV_TIE_BREAKER_MODE_LOWEST + +#define QDEVICE_NET_DEFAULT_HEARTBEAT_INTERVAL_MIN QNETD_DEFAULT_HEARTBEAT_INTERVAL_MIN +#define QDEVICE_NET_DEFAULT_HEARTBEAT_INTERVAL_MAX QNETD_DEFAULT_HEARTBEAT_INTERVAL_MAX +#define QDEVICE_NET_MIN_HEARTBEAT_INTERVAL 1 + +#define QDEVICE_NET_DEFAULT_MIN_CONNECT_TIMEOUT (1*1000) +#define QDEVICE_NET_DEFAULT_MAX_CONNECT_TIMEOUT (2*60*1000) +#define QDEVICE_NET_MIN_CONNECT_TIMEOUT 1 + +#ifdef DEBUG +#define QDEVICE_NET_DEFAULT_TEST_ALGORITHM_ENABLED 1 +#else +#define QDEVICE_NET_DEFAULT_TEST_ALGORITHM_ENABLED 0 +#endif + +/* + * Decision algorithms supported by qnetd + */ +#define QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE 4 + +extern enum tlv_decision_algorithm_type + qnetd_static_supported_decision_algorithms[QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE]; + +#define QDEVICE_NET_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE + +#ifdef __cplusplus +} +#endif + +#endif /* _QNET_CONFIG_H_ */ diff --git a/qdevices/qnetd-advanced-settings.c b/qdevices/qnetd-advanced-settings.c new file mode 100644 index 0000000..f1eff37 --- /dev/null +++ b/qdevices/qnetd-advanced-settings.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "dynar.h" +#include "dynar-getopt-lex.h" +#include "dynar-str.h" +#include "qnet-config.h" +#include "qnetd-advanced-settings.h" +#include "utils.h" + +int +qnetd_advanced_settings_init(struct qnetd_advanced_settings *settings) +{ + + memset(settings, 0, sizeof(*settings)); + settings->listen_backlog = QNETD_DEFAULT_LISTEN_BACKLOG; + settings->max_client_send_buffers = QNETD_DEFAULT_MAX_CLIENT_SEND_BUFFERS; + settings->max_client_send_size = QNETD_DEFAULT_MAX_CLIENT_SEND_SIZE; + settings->max_client_receive_size = QNETD_DEFAULT_MAX_CLIENT_RECEIVE_SIZE; + if ((settings->nss_db_dir = strdup(QNETD_DEFAULT_NSS_DB_DIR)) == NULL) { + return (-1); + } + if ((settings->cert_nickname = strdup(QNETD_DEFAULT_CERT_NICKNAME)) == NULL) { + return (-1); + } + settings->heartbeat_interval_min = QNETD_DEFAULT_HEARTBEAT_INTERVAL_MIN; + settings->heartbeat_interval_max = QNETD_DEFAULT_HEARTBEAT_INTERVAL_MAX; + settings->dpd_enabled = QNETD_DEFAULT_DPD_ENABLED; + settings->dpd_interval = QNETD_DEFAULT_DPD_INTERVAL; + if ((settings->lock_file = strdup(QNETD_DEFAULT_LOCK_FILE)) == NULL) { + return (-1); + } + if ((settings->local_socket_file = strdup(QNETD_DEFAULT_LOCAL_SOCKET_FILE)) == NULL) { + return (-1); + } + settings->local_socket_backlog = QNETD_DEFAULT_LOCAL_SOCKET_BACKLOG; + settings->ipc_max_clients = QNETD_DEFAULT_IPC_MAX_CLIENTS; + settings->ipc_max_receive_size = QNETD_DEFAULT_IPC_MAX_RECEIVE_SIZE; + settings->ipc_max_send_size = QNETD_DEFAULT_IPC_MAX_SEND_SIZE; + + return (0); +} + +void +qnetd_advanced_settings_destroy(struct qnetd_advanced_settings *settings) +{ + + free(settings->nss_db_dir); + free(settings->cert_nickname); + free(settings->lock_file); + free(settings->local_socket_file); +} + +/* + * 0 - No error + * -1 - Unknown option + * -2 - Incorrect value + */ +int +qnetd_advanced_settings_set(struct qnetd_advanced_settings *settings, + const char *option, const char *value) +{ + long long int tmpll; + char *ep; + + if (strcasecmp(option, "listen_backlog") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_LISTEN_BACKLOG || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->listen_backlog = (int)tmpll; + } else if (strcasecmp(option, "max_client_send_buffers") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_CLIENT_SEND_BUFFERS || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->max_client_send_buffers = (size_t)tmpll; + } else if (strcasecmp(option, "max_client_send_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_CLIENT_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->max_client_send_size = (size_t)tmpll; + } else if (strcasecmp(option, "max_client_receive_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_CLIENT_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->max_client_receive_size = (size_t)tmpll; + } else if (strcasecmp(option, "nss_db_dir") == 0) { + free(settings->nss_db_dir); + + if ((settings->nss_db_dir = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "cert_nickname") == 0) { + free(settings->cert_nickname); + + if ((settings->cert_nickname = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "heartbeat_interval_min") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_HEARTBEAT_INTERVAL || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heartbeat_interval_min = (uint32_t)tmpll; + } else if (strcasecmp(option, "heartbeat_interval_max") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_HEARTBEAT_INTERVAL || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->heartbeat_interval_max = (uint32_t)tmpll; + } else if (strcasecmp(option, "dpd_enabled") == 0) { + if ((tmpll = utils_parse_bool_str(value)) == -1) { + return (-2); + } + + settings->dpd_enabled = (uint8_t)tmpll; + } else if (strcasecmp(option, "dpd_interval") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_DPD_INTERVAL || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->dpd_interval = (uint32_t)tmpll; + } else if (strcasecmp(option, "lock_file") == 0) { + free(settings->lock_file); + + if ((settings->lock_file = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "local_socket_file") == 0) { + free(settings->local_socket_file); + + if ((settings->local_socket_file = strdup(value)) == NULL) { + return (-1); + } + } else if (strcasecmp(option, "local_socket_backlog") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_LOCAL_SOCKET_BACKLOG || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->local_socket_backlog = (int)tmpll; + } else if (strcasecmp(option, "ipc_max_clients") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_IPC_MAX_CLIENTS || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->ipc_max_clients = (size_t)tmpll; + } else if (strcasecmp(option, "ipc_max_receive_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_IPC_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->ipc_max_receive_size = (size_t)tmpll; + } else if (strcasecmp(option, "ipc_max_send_size") == 0) { + tmpll = strtoll(value, &ep, 10); + if (tmpll < QNETD_MIN_IPC_RECEIVE_SEND_SIZE || errno != 0 || *ep != '\0') { + return (-2); + } + + settings->ipc_max_send_size = (size_t)tmpll; + } else { + return (-1); + } + + return (0); +} diff --git a/qdevices/qnetd-advanced-settings.h b/qdevices/qnetd-advanced-settings.h new file mode 100644 index 0000000..b4ad0ae --- /dev/null +++ b/qdevices/qnetd-advanced-settings.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_ADVANCED_SETTINGS_H_ +#define _QNETD_ADVANCED_SETTINGS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct qnetd_advanced_settings { + int listen_backlog; + size_t max_client_send_buffers; + size_t max_client_send_size; + size_t max_client_receive_size; + char *nss_db_dir; + char *cert_nickname; + uint32_t heartbeat_interval_min; + uint32_t heartbeat_interval_max; + uint8_t dpd_enabled; + uint32_t dpd_interval; + char *lock_file; + char *local_socket_file; + int local_socket_backlog; + size_t ipc_max_clients; + size_t ipc_max_send_size; + size_t ipc_max_receive_size; +}; + +extern int qnetd_advanced_settings_init(struct qnetd_advanced_settings *settings); + +extern int qnetd_advanced_settings_set(struct qnetd_advanced_settings *settings, + const char *option, const char *value); + +extern void qnetd_advanced_settings_destroy(struct qnetd_advanced_settings *settings); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_ADVANCED_SETTINGS_H_ */ diff --git a/qdevices/qnetd-algo-2nodelms.c b/qdevices/qnetd-algo-2nodelms.c new file mode 100644 index 0000000..079aca3 --- /dev/null +++ b/qdevices/qnetd-algo-2nodelms.c @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Christine Caulfield (ccaulfie@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/* + * This is a simple 'last man standing' algorithm for 2 node clusters + * + * If the node is the only one left in the cluster that can see the + * qdevice server then we return a vote. + * + * If more than one node can see the qdevice server but the nodes can't + * see each other then we return a vote to the nominated tie_breaker node + * + * If there are more than two nodes, then we don't return a vote. + * this is not our job. + */ + +#include + +#include +#include + +#include "qnetd-algo-2nodelms.h" +#include "qnetd-log.h" +#include "qnetd-cluster-list.h" +#include "qnetd-algo-utils.h" +#include "utils.h" + +struct qnetd_algo_2nodelms_info { + int num_config_nodes; + enum tlv_vote last_result; +}; + +enum tlv_reply_error_code +qnetd_algo_2nodelms_client_init(struct qnetd_client *client) +{ + struct qnetd_algo_2nodelms_info *info; + + info = malloc(sizeof(struct qnetd_algo_2nodelms_info)); + if (!info) { + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + client->algorithm_data = info; + info->last_result = 0; + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * Called after client sent configuration node list + * All client fields are already set. Nodes is actual node list, initial is used + * to distinquish between initial node list and changed node list. + * msg_seq_num is 32-bit number set by client. If client sent config file version, + * config_version_set is set to 1 and config_version contains valid config file version. + * + * Function has to return result_vote. This can be one of ack/nack, ask_later (client + * should ask later for a vote) or wait_for_reply (client should wait for reply). + * + * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_* + * on failure (error is sent back to client) + */ +enum tlv_reply_error_code +qnetd_algo_2nodelms_config_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, int config_version_set, uint64_t config_version, + const struct node_list *nodes, int initial, enum tlv_vote *result_vote) +{ + struct node_list_entry *node_info; + struct qnetd_algo_2nodelms_info *info = client->algorithm_data; + int node_count = 0; + + /* Check this is a 2 node cluster */ + TAILQ_FOREACH(node_info, nodes, entries) { + node_count++; + } + info->num_config_nodes = node_count; + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s config_list has %d nodes", client->cluster_name, node_count); + + if (node_count != 2) { + qnetd_log(LOG_INFO, "algo-2nodelms: cluster %s does not have 2 configured nodes, it has %d", client->cluster_name, node_count); + + *result_vote = TLV_VOTE_NACK; + return (TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM); + } + + *result_vote = TLV_VOTE_NO_CHANGE; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * Called after client sent membership node list. + * All client fields are already set. Nodes is actual node list. + * msg_seq_num is 32-bit number set by client. If client sent config file version, + * config_version_set is set to 1 and config_version contains valid config file version. + * ring_id and quorate are copied from client votequorum callback. + * + * Function has to return result_vote. This can be one of ack/nack, ask_later (client + * should ask later for a vote) or wait_for_reply (client should wait for reply). + * + * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_* + * on failure (error is sent back to client) + */ + +enum tlv_reply_error_code +qnetd_algo_2nodelms_membership_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, + enum tlv_vote *result_vote) +{ + struct node_list_entry *node_info; + struct qnetd_client *other_client; + struct qnetd_algo_2nodelms_info *info = client->algorithm_data; + int node_count = 0; + uint32_t low_node_id = UINT32_MAX; + uint32_t high_node_id = 0; + enum tlv_heuristics other_node_heuristics; + + /* If we're a newcomer and there is another active partition, then we must NACK + * to avoid quorum moving to us from already active nodes. + */ + if (info->last_result == 0) { + TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) { + struct qnetd_algo_2nodelms_info *other_info = other_client->algorithm_data; + if (!tlv_ring_id_eq(ring_id, &other_client->last_ring_id) && + other_info->last_result == TLV_VOTE_ACK) { + + /* Don't save NACK, we need to know subsequently if we haven't been voting */ + *result_vote = TLV_VOTE_NACK; + qnetd_log(LOG_DEBUG, "algo-2nodelms: we are a new partition and another active partition exists. NACK"); + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + } + } + + /* If both nodes are present, then we're OK. return a vote */ + TAILQ_FOREACH(node_info, nodes, entries) { + node_count++; + } + + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s (client %p nodeid "UTILS_PRI_NODE_ID") membership list has %d member nodes (ring ID "UTILS_PRI_RING_ID")", client->cluster_name, client, client->node_id, node_count, ring_id->node_id, ring_id->seq); + + if (node_count == 2) { + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running normally. Both nodes active", client->cluster_name); + *result_vote = info->last_result = TLV_VOTE_ACK; + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + /* Now look for other clients connected from this cluster that can't see us any more */ + node_count = 0; + other_node_heuristics = TLV_HEURISTICS_UNDEFINED; + TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) { + node_count++; + + qnetd_log(LOG_DEBUG, "algo-2nodelms: seen nodeid "UTILS_PRI_NODE_ID" on client %p (ring ID "UTILS_PRI_RING_ID")", other_client->node_id, other_client, other_client->last_ring_id.node_id, other_client->last_ring_id.seq); + if (other_client->node_id < low_node_id) { + low_node_id = other_client->node_id; + } + if (other_client->node_id > high_node_id) { + high_node_id = other_client->node_id; + } + if (other_client != client) { + other_node_heuristics = other_client->last_heuristics; + } + } + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s %d nodes running independently", client->cluster_name, node_count); + + /* Only 1 node alive .. allow it to continue */ + if (node_count == 1) { + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running on 'last-man'", client->cluster_name); + *result_vote = info->last_result = TLV_VOTE_ACK; + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + /* + * Both nodes are alive. + * Check their heuristics. + */ + if (tlv_heuristics_cmp(heuristics, other_node_heuristics) > 0) { + *result_vote = info->last_result = TLV_VOTE_ACK; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } else if (tlv_heuristics_cmp(heuristics, other_node_heuristics) < 0) { + *result_vote = info->last_result = TLV_VOTE_NACK; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + /* Heuristics are equal -> Only give a vote to the nominated tie-breaker node */ + switch (client->tie_breaker.mode) { + + case TLV_TIE_BREAKER_MODE_LOWEST: + if (client->node_id == low_node_id) { + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running on low node-id %d", client->cluster_name, low_node_id); + *result_vote = info->last_result = TLV_VOTE_ACK; + } + else { + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s node-id %d denied vote because low nodeid %d is active", client->cluster_name, client->node_id, low_node_id); + *result_vote = info->last_result = TLV_VOTE_NACK; + } + break; + case TLV_TIE_BREAKER_MODE_HIGHEST: + if (client->node_id == high_node_id) { + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running on high node-id %d", client->cluster_name, high_node_id); + *result_vote = info->last_result = TLV_VOTE_ACK; + } + else { + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s node-id %d denied vote because high nodeid %d is active", client->cluster_name, client->node_id, high_node_id); + *result_vote = info->last_result = TLV_VOTE_NACK; + } + break; + case TLV_TIE_BREAKER_MODE_NODE_ID: + if (client->node_id == client->tie_breaker.node_id) { + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s running on nominated tie-breaker node %d", client->cluster_name, client->tie_breaker.node_id); + *result_vote = info->last_result = TLV_VOTE_ACK; + } + else { + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s node-id %d denied vote because nominated tie-breaker nodeid %d is active", client->cluster_name, client->node_id, client->tie_breaker.node_id); + *result_vote = info->last_result = TLV_VOTE_NACK; + } + break; + default: + qnetd_log(LOG_DEBUG, "algo-2nodelms: cluster %s node-id %d denied vote because tie-breaker option is invalid: %d", client->cluster_name, client->node_id, client->tie_breaker.mode); + *result_vote = info->last_result = TLV_VOTE_NACK; + } + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_2nodelms_quorum_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes, + enum tlv_vote *result_vote) +{ + + *result_vote = TLV_VOTE_NO_CHANGE; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * Called after client disconnect. Client structure is still existing (and it's part + * of a client->cluster), but it is destroyed (and removed from cluster) right after + * this callback finishes. Callback is used mainly for destroing client->algorithm_data. + */ +void +qnetd_algo_2nodelms_client_disconnect(struct qnetd_client *client, int server_going_down) +{ + qnetd_log(LOG_INFO, "algo-2nodelms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "disconnect", client, client->cluster_name, client->node_id); + + qnetd_log(LOG_INFO, "algo-2nodelms: server going down %u", server_going_down); + + free(client->algorithm_data); +} + +/* + * Called after client sent ask for vote message. This is usually happening after server + * replied TLV_VOTE_ASK_LATER. + */ +enum tlv_reply_error_code +qnetd_algo_2nodelms_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_vote *result_vote) +{ + struct qnetd_algo_2nodelms_info *info = client->algorithm_data; + + qnetd_log(LOG_INFO, "algo-2nodelms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "asked for a vote", client, client->cluster_name, client->node_id); + + if (info->last_result == 0) { + *result_vote = TLV_VOTE_ASK_LATER; + } + else { + *result_vote = info->last_result; + } + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_2nodelms_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num) +{ + + qnetd_log(LOG_INFO, "algo-2nodelms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "replied back to vote info message", client, client->cluster_name, client->node_id); + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_2nodelms_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics, enum tlv_vote *result_vote) +{ + + qnetd_log(LOG_INFO, "algo-2nodelms: heuristics change is not supported."); + + *result_vote = TLV_VOTE_NO_CHANGE; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_2nodelms_timer_callback(struct qnetd_client *client, int *reschedule_timer, + int *send_vote, enum tlv_vote *result_vote) +{ + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +static struct qnetd_algorithm qnetd_algo_2nodelms = { + .init = qnetd_algo_2nodelms_client_init, + .config_node_list_received = qnetd_algo_2nodelms_config_node_list_received, + .membership_node_list_received = qnetd_algo_2nodelms_membership_node_list_received, + .quorum_node_list_received = qnetd_algo_2nodelms_quorum_node_list_received, + .client_disconnect = qnetd_algo_2nodelms_client_disconnect, + .ask_for_vote_received = qnetd_algo_2nodelms_ask_for_vote_received, + .vote_info_reply_received = qnetd_algo_2nodelms_vote_info_reply_received, + .heuristics_change_received = qnetd_algo_2nodelms_heuristics_change_received, + .timer_callback = qnetd_algo_2nodelms_timer_callback, +}; + +enum tlv_reply_error_code qnetd_algo_2nodelms_register() +{ + return qnetd_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_2NODELMS, &qnetd_algo_2nodelms); +} diff --git a/qdevices/qnetd-algo-2nodelms.h b/qdevices/qnetd-algo-2nodelms.h new file mode 100644 index 0000000..3a48861 --- /dev/null +++ b/qdevices/qnetd-algo-2nodelms.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_ALGO_2NODELMS_H_ +#define _QNETD_ALGO_2NODELMS_H_ + +#include "qnetd-algorithm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern enum tlv_reply_error_code qnetd_algo_2nodelms_client_init(struct qnetd_client *client); + +extern enum tlv_reply_error_code qnetd_algo_2nodelms_config_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set, + uint64_t config_version, const struct node_list *nodes, int initial, + enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_2nodelms_membership_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, + const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_2nodelms_quorum_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote); + +extern void qnetd_algo_2nodelms_client_disconnect( + struct qnetd_client *client, int server_going_down); + +extern enum tlv_reply_error_code qnetd_algo_2nodelms_ask_for_vote_received( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_2nodelms_vote_info_reply_received( + struct qnetd_client *client, uint32_t msg_seq_num); + +extern enum tlv_reply_error_code qnetd_algo_2nodelms_heuristics_change_received( + struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_2nodelms_timer_callback( + struct qnetd_client *client, int *reschedule_timer, + int *send_vote, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_2nodelms_register(void); + + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_ALGO_2NODELMS_H_ */ diff --git a/qdevices/qnetd-algo-ffsplit.c b/qdevices/qnetd-algo-ffsplit.c new file mode 100644 index 0000000..3c8e6db --- /dev/null +++ b/qdevices/qnetd-algo-ffsplit.c @@ -0,0 +1,885 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include "qnetd-algo-ffsplit.h" +#include "qnetd-log.h" +#include "qnetd-log-debug.h" +#include "qnetd-cluster-list.h" +#include "qnetd-cluster.h" +#include "qnetd-client-send.h" + +enum qnetd_algo_ffsplit_cluster_state { + QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE, + QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_STABLE_MEMBERSHIP, + QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_NACKS, + QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_ACKS, +}; + +struct qnetd_algo_ffsplit_cluster_data { + enum qnetd_algo_ffsplit_cluster_state cluster_state; + const struct node_list *quorate_partition_node_list; +}; + +enum qnetd_algo_ffsplit_client_state { + QNETD_ALGO_FFSPLIT_CLIENT_STATE_WAITING_FOR_CHANGE, + QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK, + QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK, +}; + +struct qnetd_algo_ffsplit_client_data { + enum qnetd_algo_ffsplit_client_state client_state; + uint32_t vote_info_expected_seq_num; +}; + +enum tlv_reply_error_code +qnetd_algo_ffsplit_client_init(struct qnetd_client *client) +{ + struct qnetd_algo_ffsplit_cluster_data *cluster_data; + struct qnetd_algo_ffsplit_client_data *client_data; + + if (qnetd_cluster_size(client->cluster) == 1) { + cluster_data = malloc(sizeof(*cluster_data)); + if (cluster_data == NULL) { + qnetd_log(LOG_ERR, "ffsplit: Can't initialize cluster data for client %s", + client->addr_str); + + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + memset(cluster_data, 0, sizeof(*cluster_data)); + cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE; + cluster_data->quorate_partition_node_list = NULL; + + client->cluster->algorithm_data = cluster_data; + } + + client_data = malloc(sizeof(*client_data)); + if (client_data == NULL) { + qnetd_log(LOG_ERR, "ffsplit: Can't initialize node data for client %s", + client->addr_str); + + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + memset(client_data, 0, sizeof(*client_data)); + client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_WAITING_FOR_CHANGE; + client->algorithm_data = client_data; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +static int +qnetd_algo_ffsplit_is_preferred_partition(const struct qnetd_client *client, + const struct node_list *config_node_list, const struct node_list *membership_node_list) +{ + uint32_t preferred_node_id; + struct node_list_entry *node_entry; + int case_processed; + + preferred_node_id = 0; + case_processed = 0; + + switch (client->tie_breaker.mode) { + case TLV_TIE_BREAKER_MODE_LOWEST: + node_entry = TAILQ_FIRST(config_node_list); + + preferred_node_id = node_entry->node_id; + + TAILQ_FOREACH(node_entry, config_node_list, entries) { + if (node_entry->node_id < preferred_node_id) { + preferred_node_id = node_entry->node_id; + } + } + case_processed = 1; + break; + case TLV_TIE_BREAKER_MODE_HIGHEST: + node_entry = TAILQ_FIRST(config_node_list); + + preferred_node_id = node_entry->node_id; + + TAILQ_FOREACH(node_entry, config_node_list, entries) { + if (node_entry->node_id > preferred_node_id) { + preferred_node_id = node_entry->node_id; + } + } + case_processed = 1; + break; + case TLV_TIE_BREAKER_MODE_NODE_ID: + preferred_node_id = client->tie_breaker.node_id; + case_processed = 1; + break; + } + + if (!case_processed) { + qnetd_log(LOG_CRIT, "qnetd_algo_ffsplit_is_preferred_partition unprocessed " + "tie_breaker.mode"); + exit(1); + } + + return (node_list_find_node_id(membership_node_list, preferred_node_id) != NULL); +} + +static int +qnetd_algo_ffsplit_is_membership_stable(const struct qnetd_client *client, int client_leaving, + const struct tlv_ring_id *ring_id, const struct node_list *config_node_list, + const struct node_list *membership_node_list) +{ + const struct qnetd_client *iter_client1, *iter_client2; + const struct node_list *config_node_list1, *config_node_list2; + const struct node_list *membership_node_list1, *membership_node_list2; + const struct node_list_entry *iter_node1, *iter_node2; + const struct node_list_entry *iter_node3, *iter_node4; + const struct tlv_ring_id *ring_id1, *ring_id2; + + /* + * Test if all active clients share same config list. + */ + TAILQ_FOREACH(iter_client1, &client->cluster->client_list, cluster_entries) { + TAILQ_FOREACH(iter_client2, &client->cluster->client_list, cluster_entries) { + if (iter_client1 == iter_client2) { + continue; + } + + if (iter_client1->node_id == client->node_id) { + if (client_leaving) { + continue; + } + + config_node_list1 = config_node_list; + } else { + config_node_list1 = &iter_client1->configuration_node_list; + } + + if (iter_client2->node_id == client->node_id) { + if (client_leaving) { + continue; + } + + config_node_list2 = config_node_list; + } else { + config_node_list2 = &iter_client2->configuration_node_list; + } + + /* + * Walk thru all node ids in given config node list... + */ + TAILQ_FOREACH(iter_node1, config_node_list1, entries) { + /* + * ... and try to find given node id in other list + */ + iter_node2 = node_list_find_node_id(config_node_list2, iter_node1->node_id); + + if (iter_node2 == NULL) { + /* + * Node with iter_node1->node_id was not found in + * config_node_list2 -> lists doesn't match + */ + return (0); + } + } + } + } + + /* + * Test if same partitions share same ring ids and membership node list + */ + TAILQ_FOREACH(iter_client1, &client->cluster->client_list, cluster_entries) { + if (iter_client1->node_id == client->node_id) { + if (client_leaving) { + continue; + } + + membership_node_list1 = membership_node_list; + ring_id1 = ring_id; + } else { + membership_node_list1 = &iter_client1->last_membership_node_list; + ring_id1 = &iter_client1->last_ring_id; + } + + /* + * Walk thru all memberships nodes + */ + TAILQ_FOREACH(iter_node1, membership_node_list1, entries) { + /* + * try to find client with given node id + */ + iter_client2 = qnetd_cluster_find_client_by_node_id(client->cluster, + iter_node1->node_id); + if (iter_client2 == NULL) { + /* + * Client with given id is not connected + */ + continue; + } + + if (iter_client2->node_id == client->node_id) { + if (client_leaving) { + continue; + } + + membership_node_list2 = membership_node_list; + ring_id2 = ring_id; + } else { + membership_node_list2 = &iter_client2->last_membership_node_list; + ring_id2 = &iter_client2->last_ring_id; + } + + /* + * Compare ring ids + */ + if (!tlv_ring_id_eq(ring_id1, ring_id2)) { + return (0); + } + + /* + * Now compare that membership node list equals, so walk thru all + * members ... + */ + TAILQ_FOREACH(iter_node3, membership_node_list1, entries) { + /* + * ... and try to find given node id in other membership node list + */ + iter_node4 = node_list_find_node_id(membership_node_list2, iter_node3->node_id); + + if (iter_node4 == NULL) { + /* + * Node with iter_node3->node_id was not found in + * membership_node_list2 -> lists doesn't match + */ + return (0); + } + } + } + } + + return (1); +} + +static void +qnetd_algo_ffsplit_get_active_clients_in_partition_stats(const struct qnetd_client *client, + const struct node_list *client_membership_node_list, enum tlv_heuristics client_heuristics, + size_t *no_clients, size_t *no_heuristics_pass, size_t *no_heuristics_fail) +{ + const struct node_list_entry *iter_node; + const struct qnetd_client *iter_client; + enum tlv_heuristics iter_heuristics; + + *no_clients = 0; + *no_heuristics_pass = 0; + *no_heuristics_fail = 0; + + if (client == NULL || client_membership_node_list == NULL) { + return ; + } + + TAILQ_FOREACH(iter_node, client_membership_node_list, entries) { + iter_client = qnetd_cluster_find_client_by_node_id(client->cluster, + iter_node->node_id); + if (iter_client != NULL) { + (*no_clients)++; + + if (iter_client == client) { + iter_heuristics = client_heuristics; + } else { + iter_heuristics = iter_client->last_heuristics; + } + + if (iter_heuristics == TLV_HEURISTICS_PASS) { + (*no_heuristics_pass)++; + } else if (iter_heuristics == TLV_HEURISTICS_FAIL) { + (*no_heuristics_fail)++; + } + } + } +} + +/* + * Compares two partitions. Return 1 if client1, config_node_list1, membership_node_list1 is + * "better" than client2, config_node_list2, membership_node_list2 + */ +static int +qnetd_algo_ffsplit_partition_cmp(const struct qnetd_client *client1, + const struct node_list *config_node_list1, const struct node_list *membership_node_list1, + enum tlv_heuristics heuristics_1, + const struct qnetd_client *client2, + const struct node_list *config_node_list2, const struct node_list *membership_node_list2, + enum tlv_heuristics heuristics_2) +{ + size_t part1_active_clients, part2_active_clients; + size_t part1_no_heuristics_pass, part2_no_heuristics_pass; + size_t part1_no_heuristics_fail, part2_no_heuristics_fail; + size_t part1_score, part2_score; + + int res; + + res = -1; + + if (node_list_size(config_node_list1) % 2 != 0) { + /* + * Odd clusters never split into 50:50. + */ + if (node_list_size(membership_node_list1) > node_list_size(config_node_list1) / 2) { + res = 1; goto exit_res; + } else { + res = 0; goto exit_res; + } + } else { + if (node_list_size(membership_node_list1) > node_list_size(config_node_list1) / 2) { + res = 1; goto exit_res; + } else if (node_list_size(membership_node_list1) < node_list_size(config_node_list1) / 2) { + res = 0; goto exit_res; + } + + /* + * 50:50 split + */ + + /* + * Check how many active clients are in partitions and heuristics results + */ + qnetd_algo_ffsplit_get_active_clients_in_partition_stats(client1, + membership_node_list1, heuristics_1, &part1_active_clients, + &part1_no_heuristics_pass, &part1_no_heuristics_fail); + qnetd_algo_ffsplit_get_active_clients_in_partition_stats(client2, + membership_node_list2, heuristics_2, &part2_active_clients, + &part2_no_heuristics_pass, &part2_no_heuristics_fail); + + /* + * Partition can contain clients with one of 4 states: + * 1. Not-connected to qnetd (D) + * 2. Disabled heuristics (U) + * 3. Enabled heuristics with pass result (P) + * 4. Enabled heuristics with fail result (F) + * + * The question is, what partition should get vote is kind of hard with + * so much states. Following simple "score" seems to be good enough, but may + * be suboptimal in some cases. As and example let's say there are + * 2 partitions with 4 nodes each. Partition 1 looks like PDDD and partition 2 looks + * like FUUU. Partition 1 score is 1 + (1 - 0), partition 2 score is 4 + (0 - 1). + * Partition 2 wins eventho there is one processor with failed heuristics. + */ + part1_score = part1_active_clients + (part1_no_heuristics_pass - part1_no_heuristics_fail); + part2_score = part2_active_clients + (part2_no_heuristics_pass - part2_no_heuristics_fail); + + if (part1_score > part2_score) { + res = 1; goto exit_res; + } else if (part1_score < part2_score) { + res = 0; goto exit_res; + } + + if (part1_active_clients > part2_active_clients) { + res = 1; goto exit_res; + } else if (part1_active_clients < part2_active_clients) { + res = 0; goto exit_res; + } + + /* + * Number of active clients in both partitions equals. Use tie-breaker. + */ + + if (qnetd_algo_ffsplit_is_preferred_partition(client1, config_node_list1, + membership_node_list1)) { + res = 1; goto exit_res; + } else { + res = 0; goto exit_res; + } + } + +exit_res: + if (res == -1) { + qnetd_log(LOG_CRIT, "qnetd_algo_ffsplit_partition_cmp unhandled case"); + exit(1); + /* NOTREACHED */ + } + + return (res); +} + +/* + * Select best partition for given client->cluster. + * If there is no partition which could become quorate, NULL is returned + */ +static const struct node_list * +qnetd_algo_ffsplit_select_partition(const struct qnetd_client *client, int client_leaving, + const struct node_list *config_node_list, const struct node_list *membership_node_list, + enum tlv_heuristics client_heuristics) +{ + const struct qnetd_client *iter_client; + const struct qnetd_client *best_client; + const struct node_list *best_config_node_list, *best_membership_node_list; + const struct node_list *iter_config_node_list, *iter_membership_node_list; + enum tlv_heuristics iter_heuristics, best_heuristics; + + best_client = NULL; + best_config_node_list = best_membership_node_list = NULL; + best_heuristics = TLV_HEURISTICS_UNDEFINED; + + /* + * Get highest score + */ + TAILQ_FOREACH(iter_client, &client->cluster->client_list, cluster_entries) { + if (iter_client->node_id == client->node_id) { + if (client_leaving) { + continue; + } + + iter_config_node_list = config_node_list; + iter_membership_node_list = membership_node_list; + iter_heuristics = client_heuristics; + } else { + iter_config_node_list = &iter_client->configuration_node_list; + iter_membership_node_list = &iter_client->last_membership_node_list; + iter_heuristics = iter_client->last_heuristics; + } + + if (qnetd_algo_ffsplit_partition_cmp(iter_client, iter_config_node_list, + iter_membership_node_list, iter_heuristics, best_client, best_config_node_list, + best_membership_node_list, best_heuristics) > 0) { + best_client = iter_client; + best_config_node_list = iter_config_node_list; + best_membership_node_list = iter_membership_node_list; + best_heuristics = iter_heuristics; + } + } + + return (best_membership_node_list); +} + +/* + * Update state of all nodes to match quorate_partition_node_list + */ +static void +qnetd_algo_ffsplit_update_nodes_state(struct qnetd_client *client, int client_leaving, + const struct node_list *quorate_partition_node_list) +{ + const struct qnetd_client *iter_client; + struct qnetd_algo_ffsplit_client_data *iter_client_data; + + TAILQ_FOREACH(iter_client, &client->cluster->client_list, cluster_entries) { + iter_client_data = (struct qnetd_algo_ffsplit_client_data *)iter_client->algorithm_data; + + if (iter_client->node_id == client->node_id && client_leaving) { + iter_client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_WAITING_FOR_CHANGE; + + continue; + } + + if (quorate_partition_node_list == NULL || + node_list_find_node_id(quorate_partition_node_list, iter_client->node_id) == NULL) { + iter_client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK; + } else { + iter_client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK; + } + } +} + +/* + * Send vote info. If client_leaving is set, client is ignored. if send_acks + * is set, only ACK votes are sent (nodes in QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK state), + * otherwise only NACK votes are sent (nodes in QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK state) + * + * Returns number of send votes + */ +static size_t +qnetd_algo_ffsplit_send_votes(struct qnetd_client *client, int client_leaving, + const struct tlv_ring_id *ring_id, int send_acks) +{ + size_t sent_votes; + struct qnetd_client *iter_client; + struct qnetd_algo_ffsplit_client_data *iter_client_data; + const struct tlv_ring_id *ring_id_to_send; + enum tlv_vote vote_to_send; + + sent_votes = 0; + + TAILQ_FOREACH(iter_client, &client->cluster->client_list, cluster_entries) { + if (iter_client->node_id == client->node_id) { + if (client_leaving) { + continue; + } + + ring_id_to_send = ring_id; + } else { + ring_id_to_send = &iter_client->last_ring_id; + } + + iter_client_data = (struct qnetd_algo_ffsplit_client_data *)iter_client->algorithm_data; + vote_to_send = TLV_VOTE_UNDEFINED; + + if (send_acks) { + if (iter_client_data->client_state == QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK) { + vote_to_send = TLV_VOTE_ACK; + } + } else { + if (iter_client_data->client_state == QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK) { + vote_to_send = TLV_VOTE_NACK; + } + } + + if (vote_to_send != TLV_VOTE_UNDEFINED) { + iter_client_data->vote_info_expected_seq_num++; + sent_votes++; + + if (qnetd_client_send_vote_info(iter_client, + iter_client_data->vote_info_expected_seq_num, ring_id_to_send, + vote_to_send) == -1) { + client->schedule_disconnect = 1; + } + } + } + + return (sent_votes); +} + +/* + * Return number of clients in QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK state if sending_acks is + * set or number of nodes in QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK state if sending_acks is + * not set + */ +static size_t +qnetd_algo_ffsplit_no_clients_in_sending_state(struct qnetd_client *client, int sending_acks) +{ + size_t no_clients; + struct qnetd_client *iter_client; + struct qnetd_algo_ffsplit_client_data *iter_client_data; + + no_clients = 0; + + TAILQ_FOREACH(iter_client, &client->cluster->client_list, cluster_entries) { + iter_client_data = (struct qnetd_algo_ffsplit_client_data *)iter_client->algorithm_data; + + if (sending_acks && + iter_client_data->client_state == QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_ACK) { + no_clients++; + } + + if (!sending_acks && + iter_client_data->client_state == QNETD_ALGO_FFSPLIT_CLIENT_STATE_SENDING_NACK) { + no_clients++; + } + } + + return (no_clients); +} + +static enum tlv_vote +qnetd_algo_ffsplit_do(struct qnetd_client *client, int client_leaving, + const struct tlv_ring_id *ring_id, const struct node_list *config_node_list, + const struct node_list *membership_node_list, enum tlv_heuristics client_heuristics) +{ + struct qnetd_algo_ffsplit_cluster_data *cluster_data; + const struct node_list *quorate_partition_node_list; + + cluster_data = (struct qnetd_algo_ffsplit_cluster_data *)client->cluster->algorithm_data; + + cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_STABLE_MEMBERSHIP; + + if (!qnetd_algo_ffsplit_is_membership_stable(client, client_leaving, + ring_id, config_node_list, membership_node_list)) { + /* + * Wait until membership is stable + */ + qnetd_log(LOG_DEBUG, "ffsplit: Membership for cluster %s is not yet stable", client->cluster_name); + + return (TLV_VOTE_WAIT_FOR_REPLY); + } + + qnetd_log(LOG_DEBUG, "ffsplit: Membership for cluster %s is now stable", client->cluster_name); + + quorate_partition_node_list = qnetd_algo_ffsplit_select_partition(client, client_leaving, + config_node_list, membership_node_list, client_heuristics); + cluster_data->quorate_partition_node_list = quorate_partition_node_list; + + if (quorate_partition_node_list == NULL) { + qnetd_log(LOG_DEBUG, "ffsplit: No quorate partition was selected"); + } else { + qnetd_log(LOG_DEBUG, "ffsplit: Quorate partition selected"); + qnetd_log_debug_dump_node_list(client, quorate_partition_node_list); + } + + qnetd_algo_ffsplit_update_nodes_state(client, client_leaving, quorate_partition_node_list); + + cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_NACKS; + + if (qnetd_algo_ffsplit_send_votes(client, client_leaving, ring_id, 0) == 0) { + qnetd_log(LOG_DEBUG, "ffsplit: No client gets NACK"); + /* + * No one gets nack -> send acks + */ + cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_ACKS; + + if (qnetd_algo_ffsplit_send_votes(client, client_leaving, ring_id, 1) == 0) { + qnetd_log(LOG_DEBUG, "ffsplit: No client gets ACK"); + /* + * No one gets acks -> finished + */ + cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE; + } + } + + return (TLV_VOTE_NO_CHANGE); +} + +enum tlv_reply_error_code +qnetd_algo_ffsplit_config_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, int config_version_set, uint64_t config_version, + const struct node_list *nodes, int initial, enum tlv_vote *result_vote) +{ + + if (node_list_size(nodes) == 0) { + /* + * Empty node list shouldn't happen + */ + qnetd_log(LOG_ERR, "ffsplit: Received empty config node list for client %s", + client->addr_str); + + return (TLV_REPLY_ERROR_CODE_INVALID_CONFIG_NODE_LIST); + } + + if (node_list_find_node_id(nodes, client->node_id) == NULL) { + /* + * Current node is not in node list + */ + qnetd_log(LOG_ERR, "ffsplit: Received config node list without client %s", + client->addr_str); + + return (TLV_REPLY_ERROR_CODE_INVALID_CONFIG_NODE_LIST); + } + + if (initial || node_list_size(&client->last_membership_node_list) == 0) { + /* + * Initial node list -> membership is going to be send by client + */ + *result_vote = TLV_VOTE_ASK_LATER; + } else { + *result_vote = qnetd_algo_ffsplit_do(client, 0, &client->last_ring_id, + nodes, &client->last_membership_node_list, client->last_heuristics); + } + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * Called after client sent membership node list. + * All client fields are already set. Nodes is actual node list. + * msg_seq_num is 32-bit number set by client. If client sent config file version, + * config_version_set is set to 1 and config_version contains valid config file version. + * ring_id and quorate are copied from client votequorum callback. + * + * Function has to return result_vote. This can be one of ack/nack, ask_later (client + * should ask later for a vote) or wait_for_reply (client should wait for reply). + * + * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_* + * on failure (error is send back to client) + */ + +enum tlv_reply_error_code +qnetd_algo_ffsplit_membership_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote) +{ + + if (node_list_size(nodes) == 0) { + /* + * Empty node list shouldn't happen + */ + qnetd_log(LOG_ERR, "ffsplit: Received empty membership node list for client %s", + client->addr_str); + + return (TLV_REPLY_ERROR_CODE_INVALID_MEMBERSHIP_NODE_LIST); + } + + if (node_list_find_node_id(nodes, client->node_id) == NULL) { + /* + * Current node is not in node list + */ + qnetd_log(LOG_ERR, "ffsplit: Received membership node list without client %s", + client->addr_str); + + return (TLV_REPLY_ERROR_CODE_INVALID_MEMBERSHIP_NODE_LIST); + } + + if (node_list_size(&client->configuration_node_list) == 0) { + /* + * Config node list not received -> it's going to be sent later + */ + *result_vote = TLV_VOTE_ASK_LATER; + } else { + *result_vote = qnetd_algo_ffsplit_do(client, 0, ring_id, + &client->configuration_node_list, nodes, heuristics); + } + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_ffsplit_quorum_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes, + enum tlv_vote *result_vote) +{ + + /* + * Quorum node list is informative -> no change + */ + *result_vote = TLV_VOTE_NO_CHANGE; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +void +qnetd_algo_ffsplit_client_disconnect(struct qnetd_client *client, int server_going_down) +{ + + (void)qnetd_algo_ffsplit_do(client, 1, &client->last_ring_id, + &client->configuration_node_list, &client->last_membership_node_list, + client->last_heuristics); + + free(client->algorithm_data); + + if (qnetd_cluster_size(client->cluster) == 1) { + /* + * Last client in the cluster + */ + free(client->cluster->algorithm_data); + } +} + +enum tlv_reply_error_code +qnetd_algo_ffsplit_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_vote *result_vote) +{ + + /* + * Ask for vote is not supported in current algorithm + */ + return (TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM_MESSAGE); +} + +enum tlv_reply_error_code +qnetd_algo_ffsplit_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num) +{ + struct qnetd_algo_ffsplit_cluster_data *cluster_data; + struct qnetd_algo_ffsplit_client_data *client_data; + + cluster_data = (struct qnetd_algo_ffsplit_cluster_data *)client->cluster->algorithm_data; + client_data = (struct qnetd_algo_ffsplit_client_data *)client->algorithm_data; + + if (client_data->vote_info_expected_seq_num != msg_seq_num) { + qnetd_log(LOG_DEBUG, "ffsplit: Received old vote info reply from client %s", + client->addr_str); + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + client_data->client_state = QNETD_ALGO_FFSPLIT_CLIENT_STATE_WAITING_FOR_CHANGE; + + if (cluster_data->cluster_state != QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_NACKS && + cluster_data->cluster_state != QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_ACKS) { + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + if (cluster_data->cluster_state == QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_NACKS) { + if (qnetd_algo_ffsplit_no_clients_in_sending_state(client, 0) == 0) { + qnetd_log(LOG_DEBUG, "ffsplit: All NACK votes sent for cluster %s", + client->cluster_name); + + cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_SENDING_ACKS; + + if (qnetd_algo_ffsplit_send_votes(client, 0, &client->last_ring_id, 1) == 0) { + qnetd_log(LOG_DEBUG, "ffsplit: No client gets ACK"); + /* + * No one gets acks -> finished + */ + cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE; + } + } + } else { + if (qnetd_algo_ffsplit_no_clients_in_sending_state(client, 1) == 0) { + qnetd_log(LOG_DEBUG, "ffsplit: All ACK votes sent for cluster %s", + client->cluster_name); + + cluster_data->cluster_state = QNETD_ALGO_FFSPLIT_CLUSTER_STATE_WAITING_FOR_CHANGE; + } + } + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_ffsplit_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics, enum tlv_vote *result_vote) +{ + + if (node_list_size(&client->configuration_node_list) == 0 || + node_list_size(&client->last_membership_node_list) == 0) { + /* + * Config or membership node list not received -> it's going to be sent later + */ + *result_vote = TLV_VOTE_ASK_LATER; + } else { + *result_vote = qnetd_algo_ffsplit_do(client, 0, &client->last_ring_id, + &client->configuration_node_list, &client->last_membership_node_list, + heuristics); + } + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_ffsplit_timer_callback(struct qnetd_client *client, int *reschedule_timer, + int *send_vote, enum tlv_vote *result_vote) +{ + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +static struct qnetd_algorithm qnetd_algo_ffsplit = { + .init = qnetd_algo_ffsplit_client_init, + .config_node_list_received = qnetd_algo_ffsplit_config_node_list_received, + .membership_node_list_received = qnetd_algo_ffsplit_membership_node_list_received, + .quorum_node_list_received = qnetd_algo_ffsplit_quorum_node_list_received, + .client_disconnect = qnetd_algo_ffsplit_client_disconnect, + .ask_for_vote_received = qnetd_algo_ffsplit_ask_for_vote_received, + .vote_info_reply_received = qnetd_algo_ffsplit_vote_info_reply_received, + .heuristics_change_received = qnetd_algo_ffsplit_heuristics_change_received, + .timer_callback = qnetd_algo_ffsplit_timer_callback, +}; + +enum tlv_reply_error_code qnetd_algo_ffsplit_register() +{ + + return (qnetd_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_FFSPLIT, &qnetd_algo_ffsplit)); +} diff --git a/qdevices/qnetd-algo-ffsplit.h b/qdevices/qnetd-algo-ffsplit.h new file mode 100644 index 0000000..4891b89 --- /dev/null +++ b/qdevices/qnetd-algo-ffsplit.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_ALGO_FFSPLIT_H_ +#define _QNETD_ALGO_FFSPLIT_H_ + +#include "qnetd-algorithm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern enum tlv_reply_error_code qnetd_algo_ffsplit_client_init(struct qnetd_client *client); + +extern enum tlv_reply_error_code qnetd_algo_ffsplit_config_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set, + uint64_t config_version, const struct node_list *nodes, int initial, + enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_ffsplit_membership_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, + const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_ffsplit_quorum_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote); + +extern void qnetd_algo_ffsplit_client_disconnect( + struct qnetd_client *client, int server_going_down); + +extern enum tlv_reply_error_code qnetd_algo_ffsplit_ask_for_vote_received( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_ffsplit_vote_info_reply_received( + struct qnetd_client *client, uint32_t msg_seq_num); + +extern enum tlv_reply_error_code qnetd_algo_ffsplit_heuristics_change_received( + struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_ffsplit_timer_callback( + struct qnetd_client *client, int *reschedule_timer, + int *send_vote, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_ffsplit_register(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_ALGO_FFSPLIT_H_ */ diff --git a/qdevices/qnetd-algo-lms.c b/qdevices/qnetd-algo-lms.c new file mode 100644 index 0000000..47b5262 --- /dev/null +++ b/qdevices/qnetd-algo-lms.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Christine Caulfield (ccaulfie@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/* + * This is a 'last man standing' algorithm for 2+ node clusters + * + * If the node is the only one left in the cluster that can see the + * qdevice server then we return a vote. + * + * If more than one node can see the qdevice server but some nodes can't + * see each other then we divide the cluster up into 'partitions' based on + * their ring_id and return a vote to nodes in the partition that contains + * a nominated nodeid. (lowest, highest, etc) + * + */ + +#include +#include + +#include +#include + +#include "qnetd-algo-lms.h" +#include "qnetd-log.h" +#include "qnetd-cluster-list.h" +#include "qnetd-algo-utils.h" +#include "qnetd-client-algo-timer.h" +#include "utils.h" + +struct qnetd_algo_lms_info { + int num_config_nodes; + enum tlv_vote last_result; + partitions_list_t partition_list; +}; + +static enum tlv_reply_error_code do_lms_algorithm(struct qnetd_client *client, const struct tlv_ring_id *cur_ring_id, enum tlv_vote *result_vote) +{ + struct qnetd_client *other_client; + struct qnetd_algo_lms_info *info = client->algorithm_data; + struct qnetd_algo_partition *cur_partition; + struct qnetd_algo_partition *largest_partition; + struct qnetd_algo_partition *best_score_partition; + const struct tlv_ring_id *ring_id = cur_ring_id; + int num_partitions; + int joint_leader; + + /* We are running the algorithm, don't do it again unless we say so */ + qnetd_client_algo_timer_abort(client); + + if (qnetd_algo_all_ring_ids_match(client, ring_id) == -1) { + qnetd_log(LOG_DEBUG, "algo-lms: nodeid %d: ring ID (" UTILS_PRI_RING_ID ") not unique in this membership, waiting", + client->node_id, ring_id->node_id, ring_id->seq); + + qnetd_client_algo_timer_schedule(client); + *result_vote = info->last_result = TLV_VOTE_WAIT_FOR_REPLY; + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + /* Create and count the number of separate partitions */ + if ( (num_partitions = qnetd_algo_create_partitions(client, &info->partition_list, ring_id)) == -1) { + qnetd_log(LOG_DEBUG, "algo-lms: Error creating partition list"); + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + /* This can happen if we are first on the block */ + if (num_partitions == 0) { + qnetd_log(LOG_DEBUG, "algo-lms: No partitions found"); + + qnetd_client_algo_timer_schedule(client); + *result_vote = info->last_result = TLV_VOTE_WAIT_FOR_REPLY; + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + qnetd_algo_dump_partitions(&info->partition_list); + + /* Only 1 partition - let votequorum sort it out */ + if (num_partitions == 1) { + qnetd_log(LOG_DEBUG, "algo-lms: Only 1 partition. This is votequorum's problem, not ours"); + qnetd_algo_free_partitions(&info->partition_list); + *result_vote = info->last_result = TLV_VOTE_ACK; + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + + /* If we're a newcomer and there is another active partition, then we must NACK + * to avoid quorum moving to us from already active nodes. + */ + if (info->last_result == 0) { + TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) { + struct qnetd_algo_lms_info *other_info = other_client->algorithm_data; + if (!tlv_ring_id_eq(ring_id, &other_client->last_ring_id) && + other_info->last_result == TLV_VOTE_ACK) { + qnetd_algo_free_partitions(&info->partition_list); + + /* Don't save NACK, we need to know subsequently if we haven't been voting */ + *result_vote = TLV_VOTE_NACK; + qnetd_log(LOG_DEBUG, "algo-lms: we are a new partition and another active partition exists. NACK"); + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + } + } + + /* + * Find the partition with highest score + */ + best_score_partition = NULL; + TAILQ_FOREACH(cur_partition, &info->partition_list, entries) { + if (!best_score_partition || + best_score_partition->score < cur_partition->score) { + best_score_partition = cur_partition; + } + } + qnetd_log(LOG_DEBUG, "algo-lms: best score partition is (" UTILS_PRI_RING_ID ") with score %d", + best_score_partition->ring_id.node_id, best_score_partition->ring_id.seq, best_score_partition->score); + + /* Now check if it's really the highest score, and not just the joint-highest */ + joint_leader = 0; + TAILQ_FOREACH(cur_partition, &info->partition_list, entries) { + if (best_score_partition != cur_partition && + best_score_partition->score == cur_partition->score) { + joint_leader = 1; + } + } + + if (!joint_leader) { + /* Partition with highest score is unique, allow us to run if we're in that partition. */ + if (tlv_ring_id_eq(&best_score_partition->ring_id, ring_id)) { + qnetd_log(LOG_DEBUG, "algo-lms: We are in the best score partition. ACK"); + *result_vote = info->last_result = TLV_VOTE_ACK; + } + else { + qnetd_log(LOG_DEBUG, "algo-lms: We are NOT in the best score partition. NACK"); + *result_vote = info->last_result = TLV_VOTE_NACK; + } + + qnetd_algo_free_partitions(&info->partition_list); + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + /* + * There are multiple partitions with same score. Find the largest partition + */ + largest_partition = NULL; + TAILQ_FOREACH(cur_partition, &info->partition_list, entries) { + if (!largest_partition || + largest_partition->num_nodes < cur_partition->num_nodes) { + largest_partition = cur_partition; + } + } + + qnetd_log(LOG_DEBUG, "algo-lms: largest partition is (" UTILS_PRI_RING_ID ") with %d nodes", + largest_partition->ring_id.node_id, largest_partition->ring_id.seq, largest_partition->num_nodes); + + /* Now check if it's really the largest, and not just the joint-largest */ + joint_leader = 0; + TAILQ_FOREACH(cur_partition, &info->partition_list, entries) { + if (largest_partition != cur_partition && + largest_partition->num_nodes == cur_partition->num_nodes) { + joint_leader = 1; + } + } + + if (!joint_leader) { + /* Largest partition is unique, allow us to run if we're in that partition. */ + if (tlv_ring_id_eq(&largest_partition->ring_id, ring_id)) { + qnetd_log(LOG_DEBUG, "algo-lms: We are in the largest partition. ACK"); + *result_vote = info->last_result = TLV_VOTE_ACK; + } + else { + qnetd_log(LOG_DEBUG, "algo-lms: We are NOT in the largest partition. NACK"); + *result_vote = info->last_result = TLV_VOTE_NACK; + } + } + else { + uint32_t tb_node_id; + struct tlv_ring_id tb_node_ring_id = {0LL, 0}; + + /* Look for the tie-breaker node */ + if (client->tie_breaker.mode == TLV_TIE_BREAKER_MODE_LOWEST) { + tb_node_id = INT_MAX; + } + else if (client->tie_breaker.mode == TLV_TIE_BREAKER_MODE_HIGHEST) { + tb_node_id = 0; + } + else if (client->tie_breaker.mode == TLV_TIE_BREAKER_MODE_NODE_ID) { + tb_node_id = client->tie_breaker.node_id; + } + else { + qnetd_log(LOG_DEBUG, "algo-lms: denied vote because tie-breaker option is invalid: %d", + client->tie_breaker.mode); + tb_node_id = -1; + } + + /* Find the tie_breaker node */ + TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) { + switch (client->tie_breaker.mode) { + + case TLV_TIE_BREAKER_MODE_LOWEST: + if (other_client->node_id < tb_node_id) { + tb_node_id = other_client->node_id; + memcpy(&tb_node_ring_id, &other_client->last_ring_id, sizeof(struct tlv_ring_id)); + qnetd_log(LOG_DEBUG, "algo-lms: Looking for low node ID. found %d (" UTILS_PRI_RING_ID ")", + tb_node_id, tb_node_ring_id.node_id, tb_node_ring_id.seq); + } + break; + + case TLV_TIE_BREAKER_MODE_HIGHEST: + if (other_client->node_id > tb_node_id) { + tb_node_id = other_client->node_id; + memcpy(&tb_node_ring_id, &other_client->last_ring_id, sizeof(struct tlv_ring_id)); + qnetd_log(LOG_DEBUG, "algo-lms: Looking for high node ID. found %d (" UTILS_PRI_RING_ID ")", + tb_node_id, tb_node_ring_id.node_id, tb_node_ring_id.seq); + } + break; + case TLV_TIE_BREAKER_MODE_NODE_ID: + if (client->tie_breaker.node_id == client->node_id) { + memcpy(&tb_node_ring_id, &other_client->last_ring_id, sizeof(struct tlv_ring_id)); + qnetd_log(LOG_DEBUG, "algo-lms: Looking for nominated node ID. found %d (" UTILS_PRI_RING_ID ")", + tb_node_id, tb_node_ring_id.node_id, tb_node_ring_id.seq); + + } + break; + default: + qnetd_log(LOG_DEBUG, "algo-lms: denied vote because tie-breaker option is invalid: %d", + client->tie_breaker.mode); + memset(&tb_node_ring_id, 0, sizeof(struct tlv_ring_id)); + } + } + + if (client->node_id == tb_node_id || tlv_ring_id_eq(&tb_node_ring_id, ring_id)) { + qnetd_log(LOG_DEBUG, "algo-lms: We are in the same partition (" UTILS_PRI_RING_ID ") as tie-breaker node id %d. ACK", + tb_node_ring_id.node_id, tb_node_ring_id.seq, tb_node_id); + *result_vote = info->last_result = TLV_VOTE_ACK; + } + else { + qnetd_log(LOG_DEBUG, "algo-lms: We are NOT in the same partition (" UTILS_PRI_RING_ID ") as tie-breaker node id %d. NACK", + tb_node_ring_id.node_id, tb_node_ring_id.seq, tb_node_id); + *result_vote = info->last_result = TLV_VOTE_NACK; + } + } + + qnetd_algo_free_partitions(&info->partition_list); + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_lms_client_init(struct qnetd_client *client) +{ + struct qnetd_algo_lms_info *info; + + info = malloc(sizeof(struct qnetd_algo_lms_info)); + if (!info) { + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + memset(info, 0, sizeof(*info)); + client->algorithm_data = info; + info->last_result = 0; /* status unknown, or NEW */ + TAILQ_INIT(&info->partition_list); + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * We got the config node list. Simply count the number of available nodes + * and wait for the quorum list. + */ +enum tlv_reply_error_code +qnetd_algo_lms_config_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, int config_version_set, uint64_t config_version, + const struct node_list *nodes, int initial, enum tlv_vote *result_vote) +{ + struct node_list_entry *node_info; + struct qnetd_algo_lms_info *info = client->algorithm_data; + int node_count = 0; + + TAILQ_FOREACH(node_info, nodes, entries) { + node_count++; + } + info->num_config_nodes = node_count; + qnetd_log(LOG_DEBUG, "algo-lms: cluster %s config_list has %d nodes", client->cluster_name, node_count); + + *result_vote = TLV_VOTE_NO_CHANGE; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * membership node list. This is where we get to work. + */ + +enum tlv_reply_error_code +qnetd_algo_lms_membership_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote) +{ + qnetd_log(LOG_DEBUG, " "); + qnetd_log(LOG_DEBUG, "algo-lms: membership list from node %d partition (" UTILS_PRI_RING_ID ")", client->node_id, ring_id->node_id, ring_id->seq); + + return do_lms_algorithm(client, ring_id, result_vote); +} + +/* + * The quorum node list is received after corosync has decided which nodes are in the cluster. + * We run our algorithm again to be sure that things still match. By this time we will (or should) + * all know the current ring_id (not guaranteed when the membership list is received). So this + * might be the most reliable return. + */ +enum tlv_reply_error_code +qnetd_algo_lms_quorum_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote) +{ + qnetd_log(LOG_DEBUG, " "); + qnetd_log(LOG_DEBUG, "algo-lms: quorum node list from node %d partition (" UTILS_PRI_RING_ID ")", client->node_id, client->last_ring_id.node_id, client->last_ring_id.seq); + return do_lms_algorithm(client, &client->last_ring_id, result_vote); +} + +/* + * Called after client disconnect. Client structure is still existing (and it's part + * of a client->cluster), but it is destroyed (and removed from cluster) right after + * this callback finishes. Callback is used mainly for destroing client->algorithm_data. + */ +void +qnetd_algo_lms_client_disconnect(struct qnetd_client *client, int server_going_down) +{ + qnetd_log(LOG_DEBUG, "algo-lms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "disconnect", client, client->cluster_name, client->node_id); + + qnetd_log(LOG_INFO, "algo-lms: server going down %u", server_going_down); + + free(client->algorithm_data); +} + +/* + * Called after client sent ask for vote message. This is usually happening after server + * replied TLV_VOTE_WAIT_FOR_REPLY. + */ +enum tlv_reply_error_code +qnetd_algo_lms_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_vote *result_vote) +{ + qnetd_log(LOG_DEBUG, " "); + qnetd_log(LOG_DEBUG, "algo-lms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "asked for a vote", client, client->cluster_name, client->node_id); + + return do_lms_algorithm(client, &client->last_ring_id, result_vote); +} + +enum tlv_reply_error_code +qnetd_algo_lms_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num) +{ + qnetd_log(LOG_DEBUG, "algo-lms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "replied back to vote info message", client, client->cluster_name, client->node_id); + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_lms_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics, enum tlv_vote *result_vote) +{ + + qnetd_log(LOG_INFO, "algo-lms: heuristics change is not supported."); + + *result_vote = TLV_VOTE_NO_CHANGE; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_lms_timer_callback(struct qnetd_client *client, int *reschedule_timer, + int *send_vote, enum tlv_vote *result_vote) +{ + enum tlv_reply_error_code ret; + + qnetd_log(LOG_DEBUG, "algo-lms: Client %p (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "Timer callback", client, client->cluster_name, client->node_id); + + ret = do_lms_algorithm(client, &client->last_ring_id, result_vote); + + if (ret == TLV_REPLY_ERROR_CODE_NO_ERROR && + (*result_vote == TLV_VOTE_ACK || *result_vote == TLV_VOTE_NACK)) { + *send_vote = 1; + } + + if (ret == TLV_REPLY_ERROR_CODE_NO_ERROR && + *result_vote == TLV_VOTE_WAIT_FOR_REPLY) { + /* + * Reschedule was called in the do_lms_algorithm but algo_timer is + * not stack based so there can only be one. So if do_lms aborted + * the active timer, and scheduled it again the timer would be aborted + * if reschedule_timer was not set. + */ + *reschedule_timer = 1; + } + + return ret; +} + +static struct qnetd_algorithm qnetd_algo_lms = { + .init = qnetd_algo_lms_client_init, + .config_node_list_received = qnetd_algo_lms_config_node_list_received, + .membership_node_list_received = qnetd_algo_lms_membership_node_list_received, + .quorum_node_list_received = qnetd_algo_lms_quorum_node_list_received, + .client_disconnect = qnetd_algo_lms_client_disconnect, + .ask_for_vote_received = qnetd_algo_lms_ask_for_vote_received, + .vote_info_reply_received = qnetd_algo_lms_vote_info_reply_received, + .heuristics_change_received = qnetd_algo_lms_heuristics_change_received, + .timer_callback = qnetd_algo_lms_timer_callback, +}; + +enum tlv_reply_error_code qnetd_algo_lms_register() +{ + return qnetd_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_LMS, &qnetd_algo_lms); +} diff --git a/qdevices/qnetd-algo-lms.h b/qdevices/qnetd-algo-lms.h new file mode 100644 index 0000000..19901c6 --- /dev/null +++ b/qdevices/qnetd-algo-lms.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_ALGO_LMS_H_ +#define _QNETD_ALGO_LMS_H_ + +#include "qnetd-algorithm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern enum tlv_reply_error_code qnetd_algo_lms_client_init(struct qnetd_client *client); + +extern enum tlv_reply_error_code qnetd_algo_lms_config_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set, + uint64_t config_version, const struct node_list *nodes, int initial, + enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_lms_membership_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, + const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_lms_quorum_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote); + +extern void qnetd_algo_lms_client_disconnect( + struct qnetd_client *client, int server_going_down); + +extern enum tlv_reply_error_code qnetd_algo_lms_ask_for_vote_received( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_lms_vote_info_reply_received( + struct qnetd_client *client, uint32_t msg_seq_num); + +extern enum tlv_reply_error_code qnetd_algo_lms_heuristics_change_received( + struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_lms_timer_callback( + struct qnetd_client *client, int *reschedule_timer, + int *send_vote, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_lms_register(void); + + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_ALGO_LMS_H_ */ diff --git a/qdevices/qnetd-algo-test.c b/qdevices/qnetd-algo-test.c new file mode 100644 index 0000000..4276cf7 --- /dev/null +++ b/qdevices/qnetd-algo-test.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include "qnetd-algo-test.h" +#include "qnetd-log.h" +#include "qnetd-cluster-list.h" +#include "qnetd-client-send.h" +#include "qnetd-log-debug.h" +#include "qnetd-client-algo-timer.h" +#include "utils.h" + +/* + * Called right after client sent init message. This happens after initial accept of client, + * tls handshake and sending basic information about cluster/client. + * Known information: + * - client->cluster_name (client->cluster_name_len) + * - client->node_id (client->node_id_set = 1) + * - client->decision_algorithm + * - client->cluster + * - client->last_ring_id + * + * Callback is designed mainly for allocating client->algorithm_data. It's also already + * part of the cluster, so can access (alloc) client->cluster->algorithm_data. + * + * client is initialized qnetd_client structure. + * + * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_* + * on failure (error is sent back to client) + */ +enum tlv_reply_error_code +qnetd_algo_test_client_init(struct qnetd_client *client) +{ + int *algo_data; + + qnetd_log(LOG_WARNING, "algo-test: Client %s (cluster = '%s', node_id = " + UTILS_PRI_NODE_ID") initiated test algorithm. It's not recommended to use test " + "algorithm because it can create multiple quorate partitions!", client->addr_str, + client->cluster_name, client->node_id); + + qnetd_log(LOG_INFO, "algo-test: client_init"); + + client->algorithm_data = malloc(sizeof(int)); + if (client->algorithm_data == NULL) { + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + algo_data = client->algorithm_data; + *algo_data = 42; + + if (qnetd_cluster_size(client->cluster) == 1) { + /* + * First client in the cluster + */ + qnetd_log(LOG_INFO, "algo-test: Initializing cluster->algorithm data"); + + client->cluster->algorithm_data = malloc(sizeof(int)); + if (client->cluster->algorithm_data == NULL) { + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + algo_data = client->cluster->algorithm_data; + *algo_data = 42; + } + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + + +/* + * Called after client sent configuration node list + * All client fields are already set. Nodes is actual node list, initial is used + * for distrinquish between initial node list and changed node list. + * msg_seq_num is 32-bit number set by client. If client sent config file version, + * config_version_set is set to 1 and config_version contains valid config file version. + * + * Function has to return result_vote. This can be one of ack/nack, ask_later (client + * should ask later for a vote) or wait_for_reply (client should wait for reply). + * + * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_* + * on failure (error is send back to client) + */ +enum tlv_reply_error_code +qnetd_algo_test_config_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, int config_version_set, uint64_t config_version, + const struct node_list *nodes, int initial, enum tlv_vote *result_vote) +{ + + qnetd_log(LOG_INFO, "algo-test: node_list_received"); + + *result_vote = TLV_VOTE_NO_CHANGE; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * Called after client sent membership node list. + * All client fields are already set. Nodes is actual node list. + * msg_seq_num is 32-bit number set by client. + * ring_id is copied from client votequorum callback. + * heuristics is result of client heuristics (or TLV_HEURISTICS_UNDEFINED if heuristics + * are disabled or not supported by client) + * + * Function has to return result_vote. This can be one of ack/nack, ask_later (client + * should ask later for a vote) or wait_for_reply (client should wait for reply). + * + * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_* + * on failure (error is sent back to client) + */ + +enum tlv_reply_error_code +qnetd_algo_test_membership_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote) +{ + + qnetd_log(LOG_INFO, "algo-test: membership_node_list_received"); + + *result_vote = TLV_VOTE_ACK; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * Called after client sent quorum node list. + * All client fields are already set. Nodes is actual node list. + * msg_seq_num is 32-bit number set by client. + * quorate is copied from client votequorum callback. + * Function is just informative. If client vote is required to change, it's possible + * to use qnetd_client_send_vote_info. + * + * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_* + * on failure (error is sent back to client) + */ +enum tlv_reply_error_code +qnetd_algo_test_quorum_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes, + enum tlv_vote *result_vote) +{ + + qnetd_log(LOG_INFO, "algo-test: quorum_node_list_received"); + + *result_vote = TLV_VOTE_NO_CHANGE; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * Called after client disconnect. Client structure is still existing (and it's part + * of a client->cluster), but it is destroyed (and removed from cluster) right after + * this callback finishes. Callback is used mainly for destroing client->algorithm_data. + */ +void +qnetd_algo_test_client_disconnect(struct qnetd_client *client, int server_going_down) +{ + + qnetd_log(LOG_INFO, "algo-test: client_disconnect"); + + free(client->algorithm_data); + + if (qnetd_cluster_size(client->cluster) == 1) { + /* + * Last client in the cluster + */ + qnetd_log(LOG_INFO, "algo-test: Finalizing cluster->algorithm data"); + + free(client->cluster->algorithm_data); + } +} + +/* + * Called after client sent ask for vote message. This is usually happening after server + * replied TLV_VOTE_ASK_LATER. + */ +enum tlv_reply_error_code +qnetd_algo_test_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_vote *result_vote) +{ + + qnetd_log(LOG_INFO, "algo-test: ask_for_vote_received"); + + *result_vote = TLV_VOTE_ACK; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +enum tlv_reply_error_code +qnetd_algo_test_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num) +{ + + qnetd_log(LOG_INFO, "algo-test: vote_info_reply_received"); + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * Called after client sent heuristics change message. + * heuristics is result of client regular heuristics (cannot be TLV_HEURISTICS_UNDEFINED) + * Variables client->last_regular_heuristics and client->last_heuristics are updated after + * the call. + */ +enum tlv_reply_error_code +qnetd_algo_test_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics, enum tlv_vote *result_vote) +{ + + qnetd_log(LOG_INFO, "algo-test: heuristics_change_received"); + + *result_vote = TLV_VOTE_NO_CHANGE; + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +/* + * Called as a result of qnetd_client_algo_timer_schedule function call after timeout expires. + * + * If send_vote is set by callback to non zero value, result_vote must also be set and such vote is + * sent to client. Result_vote is ignored if send_vote = 0 (default). + * + * If reschedule timer (default value = 0) is set to non zero value, callback is called again later + * with same timeout as originaly created. + * + * Return TLV_REPLY_ERROR_CODE_NO_ERROR on success, different TLV_REPLY_ERROR_CODE_* + * on failure (error is sent back to client) + */ +enum tlv_reply_error_code +qnetd_algo_test_timer_callback(struct qnetd_client *client, int *reschedule_timer, + int *send_vote, enum tlv_vote *result_vote) +{ + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +static struct qnetd_algorithm qnetd_algo_test = { + .init = qnetd_algo_test_client_init, + .config_node_list_received = qnetd_algo_test_config_node_list_received, + .membership_node_list_received = qnetd_algo_test_membership_node_list_received, + .quorum_node_list_received = qnetd_algo_test_quorum_node_list_received, + .client_disconnect = qnetd_algo_test_client_disconnect, + .ask_for_vote_received = qnetd_algo_test_ask_for_vote_received, + .vote_info_reply_received = qnetd_algo_test_vote_info_reply_received, + .heuristics_change_received = qnetd_algo_test_heuristics_change_received, + .timer_callback = qnetd_algo_test_timer_callback, +}; + +enum tlv_reply_error_code qnetd_algo_test_register() +{ + + return (qnetd_algorithm_register(TLV_DECISION_ALGORITHM_TYPE_TEST, &qnetd_algo_test)); +} diff --git a/qdevices/qnetd-algo-test.h b/qdevices/qnetd-algo-test.h new file mode 100644 index 0000000..f2d3084 --- /dev/null +++ b/qdevices/qnetd-algo-test.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_ALGO_TEST_H_ +#define _QNETD_ALGO_TEST_H_ + +#include "qnetd-algorithm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern enum tlv_reply_error_code qnetd_algo_test_client_init(struct qnetd_client *client); + +extern enum tlv_reply_error_code qnetd_algo_test_config_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set, + uint64_t config_version, const struct node_list *nodes, int initial, + enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_test_membership_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, + const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, + enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_test_quorum_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_quorate quorate, const struct node_list *nodes, enum tlv_vote *result_vote); + +extern void qnetd_algo_test_client_disconnect( + struct qnetd_client *client, int server_going_down); + +extern enum tlv_reply_error_code qnetd_algo_test_ask_for_vote_received( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_test_vote_info_reply_received( + struct qnetd_client *client, uint32_t msg_seq_num); + +extern enum tlv_reply_error_code qnetd_algo_test_heuristics_change_received( + struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_test_timer_callback( + struct qnetd_client *client, int *reschedule_timer, + int *send_vote, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algo_test_register(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_ALGO_TEST_H_ */ diff --git a/qdevices/qnetd-algo-utils.c b/qdevices/qnetd-algo-utils.c new file mode 100644 index 0000000..b4c8d92 --- /dev/null +++ b/qdevices/qnetd-algo-utils.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Christine Caulfield (ccaulfie@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include + +#include "qnetd-log.h" +#include "qnetd-cluster-list.h" +#include "qnetd-algo-utils.h" +#include "utils.h" + +/* + * Returns -1 if any node that is supposedly in the same cluster partition + * as us has a different ring_id. + * If this happens it simply means that qnetd does not yet have the full current view + * of the cluster and should wait until all of the ring_ids in this membership list match up + */ +int +qnetd_algo_all_ring_ids_match(struct qnetd_client *client, const struct tlv_ring_id *ring_id) +{ + struct node_list_entry *node_info; + struct qnetd_client *other_client; + + TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) { + int in_our_partition = 0; + + if (other_client == client) { + continue; /* We've seen our membership list */ + } + qnetd_log(LOG_DEBUG, "algo-util: all_ring_ids_match: seen nodeid %d (client %p) ring_id (" UTILS_PRI_RING_ID ")", other_client->node_id, other_client, other_client->last_ring_id.node_id, other_client->last_ring_id.seq); + + /* Look down our node list and see if this client is known to us */ + TAILQ_FOREACH(node_info, &client->last_membership_node_list, entries) { + if (node_info->node_id == other_client->node_id) { + in_our_partition = 1; + } + } + + if (in_our_partition == 0) { + /* + * Also try to look from the other side to see if we are + * not in the other node's membership list. + * Because if so it may mean the membership lists are not equal + */ + TAILQ_FOREACH(node_info, &other_client->last_membership_node_list, entries) { + if (node_info->node_id == client->node_id) { + in_our_partition = 1; + } + } + } + + /* + * If the other nodes on our side of a partition have a different ring ID then + * we need to wait until they have all caught up before making a decision + */ + if (in_our_partition && !tlv_ring_id_eq(ring_id, &other_client->last_ring_id)) { + qnetd_log(LOG_DEBUG, "algo-util: nodeid %d in our partition has different ring_id (" UTILS_PRI_RING_ID ") to us (" UTILS_PRI_RING_ID ")", other_client->node_id, other_client->last_ring_id.node_id, other_client->last_ring_id.seq, ring_id->node_id, ring_id->seq); + return (-1); /* ring IDs don't match */ + } + } + + return (0); +} + +struct qnetd_algo_partition * +qnetd_algo_find_partition(partitions_list_t *partitions_list, const struct tlv_ring_id *ring_id) +{ + struct qnetd_algo_partition *cur_partition; + + TAILQ_FOREACH(cur_partition, partitions_list, entries) { + if (tlv_ring_id_eq(&cur_partition->ring_id, ring_id)) { + return (cur_partition); + } + } + + return (NULL); +} + +int +qnetd_algo_create_partitions(struct qnetd_client *client, partitions_list_t *partitions_list, const struct tlv_ring_id *ring_id) +{ + struct qnetd_client *other_client; + int num_partitions = 0; + + TAILQ_FOREACH(other_client, &client->cluster->client_list, cluster_entries) { + struct qnetd_algo_partition *partition; + + if (other_client->last_ring_id.seq == 0){ + continue; /* not initialised yet */ + } + partition = qnetd_algo_find_partition(partitions_list, &other_client->last_ring_id); + if (!partition) { + partition = malloc(sizeof(struct qnetd_algo_partition)); + if (!partition) { + return (-1); + } + partition->num_nodes = 0; + partition->score = 0; + memcpy(&partition->ring_id, &other_client->last_ring_id, sizeof(*ring_id)); + num_partitions++; + TAILQ_INSERT_TAIL(partitions_list, partition, entries); + } + partition->num_nodes++; + + /* + * Score is computer similar way as in the ffsplit algorithm + */ + partition->score++; + if (other_client->last_heuristics == TLV_HEURISTICS_PASS) { + partition->score++; + } else if (other_client->last_heuristics == TLV_HEURISTICS_FAIL) { + partition->score--; + } + + } + + return (num_partitions); +} + + +void +qnetd_algo_free_partitions(partitions_list_t *partitions_list) +{ + struct qnetd_algo_partition *cur_partition; + struct qnetd_algo_partition *partition_next; + + cur_partition = TAILQ_FIRST(partitions_list); + + while (cur_partition != NULL) { + partition_next = TAILQ_NEXT(cur_partition, entries); + + free(cur_partition); + + cur_partition = partition_next; + } + + TAILQ_INIT(partitions_list); +} + +void +qnetd_algo_dump_partitions(partitions_list_t *partitions_list) +{ + struct qnetd_algo_partition *partition; + + TAILQ_FOREACH(partition, partitions_list, entries) { + qnetd_log(LOG_DEBUG, "algo-util: partition (" UTILS_PRI_RING_ID ") (%p) has %d nodes", + partition->ring_id.node_id, partition->ring_id.seq, partition, partition->num_nodes); + } +} diff --git a/qdevices/qnetd-algo-utils.h b/qdevices/qnetd-algo-utils.h new file mode 100644 index 0000000..83f4101 --- /dev/null +++ b/qdevices/qnetd-algo-utils.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Christine Caulfield (ccaulfie@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_ALGO_UTILS_H_ +#define _QNETD_ALGO_UTILS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct qnetd_algo_partition { + struct tlv_ring_id ring_id; + int num_nodes; + int score; + TAILQ_ENTRY(qnetd_algo_partition) entries; +}; + +typedef TAILQ_HEAD(, qnetd_algo_partition) partitions_list_t; + +extern int qnetd_algo_all_ring_ids_match(struct qnetd_client *client, + const struct tlv_ring_id *ring_id); + +extern struct qnetd_algo_partition *qnetd_algo_find_partition(partitions_list_t *partitions, + const struct tlv_ring_id *ring_id); + +extern int qnetd_algo_create_partitions(struct qnetd_client *client, + partitions_list_t *partitions, const struct tlv_ring_id *ring_id); + +extern void qnetd_algo_free_partitions(partitions_list_t *partitions); + +extern void qnetd_algo_dump_partitions(partitions_list_t *partitions); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_ALGO_UTILS_H_ */ diff --git a/qdevices/qnetd-algorithm.c b/qdevices/qnetd-algorithm.c new file mode 100644 index 0000000..e88d44d --- /dev/null +++ b/qdevices/qnetd-algorithm.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "qnet-config.h" +#include "qnetd-algorithm.h" +#include "qnetd-algo-test.h" +#include "qnetd-algo-ffsplit.h" +#include "qnetd-algo-2nodelms.h" +#include "qnetd-algo-lms.h" +#include "qnetd-log.h" + +static struct qnetd_algorithm *qnetd_algorithm_array[QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE]; + +enum tlv_reply_error_code +qnetd_algorithm_client_init(struct qnetd_client *client) +{ + if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qnetd_algorithm_array[client->decision_algorithm] == NULL) { + qnetd_log(LOG_CRIT, "qnetd_algorithm_client_init unhandled decision algorithm"); + + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + return (qnetd_algorithm_array[client->decision_algorithm]->init(client)); +} + +enum tlv_reply_error_code +qnetd_algorithm_config_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, int config_version_set, uint64_t config_version, + const struct node_list *nodes, int initial, enum tlv_vote *result_vote) +{ + if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qnetd_algorithm_array[client->decision_algorithm] == NULL) { + qnetd_log(LOG_CRIT, "qnetd_algorithm_config_node_list_received unhandled " + "decision algorithm"); + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + return (qnetd_algorithm_array[client->decision_algorithm]->config_node_list_received( + client, msg_seq_num, + config_version_set, config_version, nodes, initial, result_vote)); +} + +enum tlv_reply_error_code +qnetd_algorithm_membership_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote) +{ + + if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qnetd_algorithm_array[client->decision_algorithm] == NULL) { + qnetd_log(LOG_CRIT, "qnetd_algorithm_membership_node_list_received unhandled " + "decision algorithm"); + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + return (qnetd_algorithm_array[client->decision_algorithm]->membership_node_list_received( + client, msg_seq_num, + ring_id, nodes, heuristics, result_vote)); +} + +enum tlv_reply_error_code +qnetd_algorithm_quorum_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, enum tlv_quorate quorate, + const struct node_list *nodes, enum tlv_vote *result_vote) +{ + + if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qnetd_algorithm_array[client->decision_algorithm] == NULL) { + qnetd_log(LOG_CRIT, "algorithm_quorum_node_list_received unhandled " + "decision algorithm"); + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + return (qnetd_algorithm_array[client->decision_algorithm]->quorum_node_list_received( + client, msg_seq_num, quorate, nodes, result_vote)); +} + +void +qnetd_algorithm_client_disconnect(struct qnetd_client *client, int server_going_down) +{ + + if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qnetd_algorithm_array[client->decision_algorithm] == NULL) { + qnetd_log(LOG_CRIT, "qnetd_algorithm_client_disconnect unhandled decision " + "algorithm"); + return; + } + + qnetd_algorithm_array[client->decision_algorithm]->client_disconnect(client, server_going_down); +} + +enum tlv_reply_error_code +qnetd_algorithm_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_vote *result_vote) +{ + + if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qnetd_algorithm_array[client->decision_algorithm] == NULL) { + qnetd_log(LOG_CRIT, "qnetd_algorithm_ask_for_vote_received unhandled " + "decision algorithm"); + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + return (qnetd_algorithm_array[client->decision_algorithm]->ask_for_vote_received( + client, msg_seq_num, result_vote)); +} + +enum tlv_reply_error_code +qnetd_algorithm_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num) +{ + + if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qnetd_algorithm_array[client->decision_algorithm] == NULL) { + qnetd_log(LOG_CRIT, "qnetd_algorithm_vote_info_reply_received unhandled decision algorithm"); + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + return (qnetd_algorithm_array[client->decision_algorithm]->vote_info_reply_received( + client, msg_seq_num)); + +} + +enum tlv_reply_error_code +qnetd_algorithm_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics, enum tlv_vote *result_vote) +{ + + if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qnetd_algorithm_array[client->decision_algorithm] == NULL) { + qnetd_log(LOG_CRIT, "qnetd_algorithm_ask_for_vote_received unhandled " + "decision algorithm"); + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + return (qnetd_algorithm_array[client->decision_algorithm]->heuristics_change_received( + client, msg_seq_num, heuristics, result_vote)); +} + +enum tlv_reply_error_code +qnetd_algorithm_timer_callback(struct qnetd_client *client, int *reschedule_timer, + int *send_vote, enum tlv_vote *result_vote) +{ + + if (client->decision_algorithm >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE || + qnetd_algorithm_array[client->decision_algorithm] == NULL) { + qnetd_log(LOG_CRIT, "qnetd_algorithm_timer_callback unhandled decision algorithm"); + return (TLV_REPLY_ERROR_CODE_INTERNAL_ERROR); + } + + return (qnetd_algorithm_array[client->decision_algorithm]->timer_callback( + client, reschedule_timer, send_vote, result_vote)); +} + +int +qnetd_algorithm_register(enum tlv_decision_algorithm_type algorithm_number, + struct qnetd_algorithm *algorithm) +{ + + if (algorithm_number >= QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE) { + qnetd_log(LOG_CRIT, "Failed to register unsupported decision algorithm %u", + algorithm_number); + return (-1); + } + + if (qnetd_algorithm_array[algorithm_number] != NULL) { + qnetd_log(LOG_CRIT, "Failed to register decision algorithm %u, " + "it's already registered.", algorithm_number); + return (-1); + } + + qnetd_algorithm_array[algorithm_number] = algorithm; + + return (0); +} + +int +qnetd_algorithm_register_all(void) +{ + + if (qnetd_algo_test_register() != 0) { + qnetd_log(LOG_CRIT, "Failed to register decision algorithm 'test'"); + return (-1); + } + if (qnetd_algo_ffsplit_register() != 0) { + qnetd_log(LOG_CRIT, "Failed to register decision algorithm 'ffsplit'"); + return (-1); + } + if (qnetd_algo_2nodelms_register() != 0) { + qnetd_log(LOG_CRIT, "Failed to register decision algorithm '2nodelms'"); + return (-1); + } + if (qnetd_algo_lms_register() != 0) { + qnetd_log(LOG_CRIT, "Failed to register decision algorithm 'lms'"); + return (-1); + } + + return (0); +} diff --git a/qdevices/qnetd-algorithm.h b/qdevices/qnetd-algorithm.h new file mode 100644 index 0000000..a1f19bb --- /dev/null +++ b/qdevices/qnetd-algorithm.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_ALGORITHM_H_ +#define _QNETD_ALGORITHM_H_ + +#include +#include + +#include "tlv.h" +#include "qnetd-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern enum tlv_reply_error_code qnetd_algorithm_client_init(struct qnetd_client *client); + +extern enum tlv_reply_error_code qnetd_algorithm_config_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, int config_version_set, + uint64_t config_version, const struct node_list *nodes, int initial, + enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algorithm_membership_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics heuristics, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algorithm_quorum_node_list_received( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_quorate quorate, + const struct node_list *nodes, enum tlv_vote *result_vote); + +extern void qnetd_algorithm_client_disconnect( + struct qnetd_client *client, int server_going_down); + +extern enum tlv_reply_error_code qnetd_algorithm_ask_for_vote_received( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algorithm_vote_info_reply_received( + struct qnetd_client *client, uint32_t msg_seq_num); + +extern enum tlv_reply_error_code qnetd_algorithm_heuristics_change_received( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_heuristics heuristics, + enum tlv_vote *result_vote); + +extern enum tlv_reply_error_code qnetd_algorithm_timer_callback( + struct qnetd_client *client, int *reschedule_timer, int *send_vote, enum tlv_vote *result_vote); + +struct qnetd_algorithm { + enum tlv_reply_error_code (*init)(struct qnetd_client *client); + + void (*client_disconnect)(struct qnetd_client *client, int server_going_down); + + enum tlv_reply_error_code (*membership_node_list_received)( + struct qnetd_client *client, uint32_t msg_seq_num, + const struct tlv_ring_id *ring_id, + const struct node_list *nodes, enum tlv_heuristics, enum tlv_vote *result_vote); + + enum tlv_reply_error_code (*quorum_node_list_received)( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_quorate quorate, + const struct node_list *nodes, enum tlv_vote *result_vote); + + enum tlv_reply_error_code (*config_node_list_received)( + struct qnetd_client *client, + uint32_t msg_seq_num, int config_version_set, uint64_t config_version, + const struct node_list *nodes, int initial, enum tlv_vote *result_vote); + + enum tlv_reply_error_code (*ask_for_vote_received)( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote *result_vote); + + enum tlv_reply_error_code (*vote_info_reply_received)(struct qnetd_client *client, + uint32_t msg_seq_num); + + enum tlv_reply_error_code (*heuristics_change_received)( + struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_heuristics heuristics, + enum tlv_vote *result_vote); + + enum tlv_reply_error_code (*timer_callback)(struct qnetd_client *client, + int *reschedule_timer, int *send_vote, enum tlv_vote *result_vote); +}; + +extern int qnetd_algorithm_register( + enum tlv_decision_algorithm_type algorithm_number, struct qnetd_algorithm *algorithm); + +extern int qnetd_algorithm_register_all(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_ALGORITHM_H_ */ diff --git a/qdevices/qnetd-client-algo-timer.c b/qdevices/qnetd-client-algo-timer.c new file mode 100644 index 0000000..35336f0 --- /dev/null +++ b/qdevices/qnetd-client-algo-timer.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qnetd-log.h" +#include "qnetd-client-algo-timer.h" +#include "qnetd-client-send.h" +#include "qnetd-algorithm.h" +#include "timer-list.h" + +static int +qnetd_client_algo_timer_callback(void *data1, void *data2) +{ + struct qnetd_client *client; + enum tlv_vote result_vote; + int send_vote; + int reschedule_timer; + enum tlv_reply_error_code reply_error_code; + + client = (struct qnetd_client *)data1; + + result_vote = TLV_VOTE_WAIT_FOR_REPLY; + send_vote = 0; + reschedule_timer = 0; + + reply_error_code = qnetd_algorithm_timer_callback(client, &reschedule_timer, + &send_vote, &result_vote); + + if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) { + qnetd_log(LOG_ERR, "Algorithm for client %s returned error code. " + "Sending error reply.", client->addr_str); + + if (qnetd_client_send_err(client, 0, 0, reply_error_code) != 0) { + client->schedule_disconnect = 1; + return (0); + } + + return (0); + } else { + qnetd_log(LOG_DEBUG, "Algorithm for client %s decided to %s timer and %s vote " + "with value %s", client->addr_str, + (reschedule_timer ? "reschedule" : "not reschedule"), + (send_vote ? "send" : "not send"), + tlv_vote_to_str(result_vote)); + } + + if (send_vote) { + client->algo_timer_vote_info_msq_seq_number++; + + if (qnetd_client_send_vote_info(client, + client->algo_timer_vote_info_msq_seq_number, &client->last_ring_id, + result_vote) != 0) { + client->schedule_disconnect = 1; + return (0); + } + } + + if (reschedule_timer) { + /* + * Timer list makes sure to schedule callback again + */ + return (-1); + } + + client->algo_timer = NULL; + return (0); +} + +int +qnetd_client_algo_timer_is_scheduled(struct qnetd_client *client) +{ + + return (client->algo_timer != NULL); +} + +int +qnetd_client_algo_timer_schedule_timeout(struct qnetd_client *client, uint32_t timeout) +{ + + if (qnetd_client_algo_timer_is_scheduled(client)) { + if (qnetd_client_algo_timer_abort(client) != 0) { + qnetd_log(LOG_ERR, "Can't abort algo timer"); + + return (-1); + } + } + + client->algo_timer = timer_list_add(client->main_timer_list, timeout, + qnetd_client_algo_timer_callback, (void *)client, NULL); + if (client->algo_timer == NULL) { + qnetd_log(LOG_ERR, "Can't schedule algo timer"); + + return (-1); + } + + return (0); +} + +int +qnetd_client_algo_timer_schedule(struct qnetd_client *client) +{ + + return (qnetd_client_algo_timer_schedule_timeout(client, client->heartbeat_interval / 4)); +} + +int +qnetd_client_algo_timer_abort(struct qnetd_client *client) +{ + + if (qnetd_client_algo_timer_is_scheduled(client)) { + timer_list_delete(client->main_timer_list, client->algo_timer); + client->algo_timer = NULL; + } + + return (0); +} diff --git a/qdevices/qnetd-client-algo-timer.h b/qdevices/qnetd-client-algo-timer.h new file mode 100644 index 0000000..59a6178 --- /dev/null +++ b/qdevices/qnetd-client-algo-timer.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_CLIENT_ALGO_TIMER_H_ +#define _QNETD_CLIENT_ALGO_TIMER_H_ + +#include "qnetd-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qnetd_client_algo_timer_is_scheduled(struct qnetd_client *client); + +extern int qnetd_client_algo_timer_schedule_timeout(struct qnetd_client *client, + uint32_t timeout); + +extern int qnetd_client_algo_timer_schedule(struct qnetd_client *client); + +extern int qnetd_client_algo_timer_abort(struct qnetd_client *client); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_CLIENT_ALGO_TIMER_H_ */ diff --git a/qdevices/qnetd-client-list.c b/qdevices/qnetd-client-list.c new file mode 100644 index 0000000..993bb71 --- /dev/null +++ b/qdevices/qnetd-client-list.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include + +#include "qnetd-client-list.h" + +void +qnetd_client_list_init(struct qnetd_client_list *client_list) +{ + + TAILQ_INIT(client_list); +} + +struct qnetd_client * +qnetd_client_list_add(struct qnetd_client_list *client_list, PRFileDesc *sock, PRNetAddr *addr, + char *addr_str, + size_t max_receive_size, size_t max_send_buffers, size_t max_send_size, + struct timer_list *main_timer_list) +{ + struct qnetd_client *client; + + client = (struct qnetd_client *)malloc(sizeof(*client)); + if (client == NULL) { + return (NULL); + } + + qnetd_client_init(client, sock, addr, addr_str, max_receive_size, max_send_buffers, + max_send_size, main_timer_list); + + TAILQ_INSERT_TAIL(client_list, client, entries); + + return (client); +} + +void +qnetd_client_list_free(struct qnetd_client_list *client_list) +{ + struct qnetd_client *client; + struct qnetd_client *client_next; + + client = TAILQ_FIRST(client_list); + + while (client != NULL) { + client_next = TAILQ_NEXT(client, entries); + + qnetd_client_destroy(client); + free(client); + + client = client_next; + } + + TAILQ_INIT(client_list); +} + +void +qnetd_client_list_del(struct qnetd_client_list *client_list, struct qnetd_client *client) +{ + + TAILQ_REMOVE(client_list, client, entries); + + qnetd_client_destroy(client); + free(client); +} + +size_t +qnetd_client_list_no_clients(struct qnetd_client_list *client_list) +{ + size_t res; + struct qnetd_client *client; + + res = 0; + + TAILQ_FOREACH(client, client_list, entries) { + res++; + } + + return (res); +} diff --git a/qdevices/qnetd-client-list.h b/qdevices/qnetd-client-list.h new file mode 100644 index 0000000..d44014b --- /dev/null +++ b/qdevices/qnetd-client-list.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_CLIENT_LIST_H_ +#define _QNETD_CLIENT_LIST_H_ + +#include +#include + +#include "qnetd-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +TAILQ_HEAD(qnetd_client_list, qnetd_client); + +extern void qnetd_client_list_init(struct qnetd_client_list *client_list); + +extern struct qnetd_client *qnetd_client_list_add(struct qnetd_client_list *client_list, + PRFileDesc *sock, PRNetAddr *addr, char *addr_str, size_t max_receive_size, + size_t max_send_buffers, size_t max_send_size, struct timer_list *main_timer_list); + +extern void qnetd_client_list_free(struct qnetd_client_list *client_list); + +extern void qnetd_client_list_del(struct qnetd_client_list *client_list, + struct qnetd_client *client); + +extern size_t qnetd_client_list_no_clients( + struct qnetd_client_list *client_list); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_CLIENT_LIST_H_ */ diff --git a/qdevices/qnetd-client-msg-received.c b/qdevices/qnetd-client-msg-received.c new file mode 100644 index 0000000..65711e1 --- /dev/null +++ b/qdevices/qnetd-client-msg-received.c @@ -0,0 +1,1253 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "qnetd-algorithm.h" +#include "qnetd-instance.h" +#include "qnetd-log.h" +#include "qnetd-log-debug.h" +#include "qnetd-client-send.h" +#include "msg.h" +#include "nss-sock.h" + +#include "qnetd-client-msg-received.h" + +/* + * 0 - Success + * -1 - Disconnect client + * -2 - Error reply sent, but no need to disconnect client + */ +static int +qnetd_client_msg_received_check_tls(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + int check_certificate; + int tls_required; + CERTCertificate *peer_cert; + int case_processed; + + check_certificate = 0; + tls_required = 0; + + case_processed = 0; + + switch (instance->tls_supported) { + case TLV_TLS_UNSUPPORTED: + case_processed = 1; + tls_required = 0; + check_certificate = 0; + break; + case TLV_TLS_SUPPORTED: + case_processed = 1; + tls_required = 0; + + if (client->tls_started && instance->tls_client_cert_required && + !client->tls_peer_certificate_verified) { + check_certificate = 1; + } + break; + case TLV_TLS_REQUIRED: + case_processed = 1; + tls_required = 1; + + if (instance->tls_client_cert_required && !client->tls_peer_certificate_verified) { + check_certificate = 1; + } + break; + /* + * Default is not defined intentionally. Compiler shows warning when new + * tls supported is added + */ + } + + if (!case_processed) { + qnetd_log(LOG_ERR, "Unhandled instance tls supported %u", instance->tls_supported); + exit(1); + } + + if (tls_required && !client->tls_started) { + qnetd_log(LOG_ERR, "TLS is required but doesn't started yet. " + "Sending back error message"); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_TLS_REQUIRED) != 0) { + return (-1); + } + + return (-2); + } + + if (check_certificate) { + peer_cert = SSL_PeerCertificate(client->socket); + + if (peer_cert == NULL) { + qnetd_log(LOG_ERR, "Client doesn't sent valid certificate. " + "Disconnecting client"); + + return (-1); + } + + if (CERT_VerifyCertName(peer_cert, client->cluster_name) != SECSuccess) { + qnetd_log(LOG_ERR, "Client doesn't sent certificate with valid CN. " + "Disconnecting client"); + + CERT_DestroyCertificate(peer_cert); + + return (-1); + } + + CERT_DestroyCertificate(peer_cert); + + client->tls_peer_certificate_verified = 1; + } + + return (0); +} + +static int +qnetd_client_msg_received_preinit(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + struct send_buffer_list_entry *send_buffer; + + if (msg->cluster_name == NULL) { + qnetd_log(LOG_ERR, "Received preinit message without cluster name. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) { + return (-1); + } + + return (0); + } + + client->cluster_name = malloc(msg->cluster_name_len + 1); + if (client->cluster_name == NULL) { + qnetd_log(LOG_ERR, "Can't allocate cluster name. Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_INTERNAL_ERROR) != 0) { + return (-1); + } + + return (0); + } + memset(client->cluster_name, 0, msg->cluster_name_len + 1); + memcpy(client->cluster_name, msg->cluster_name, msg->cluster_name_len); + + client->cluster_name_len = msg->cluster_name_len; + client->preinit_received = 1; + + send_buffer = send_buffer_list_get_new(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log(LOG_ERR, "Can't alloc preinit reply msg from list. " + "Disconnecting client connection."); + + return (-1); + } + + if (msg_create_preinit_reply(&send_buffer->buffer, msg->seq_number_set, msg->seq_number, + instance->tls_supported, instance->tls_client_cert_required) == 0) { + qnetd_log(LOG_ERR, "Can't alloc preinit reply msg. " + "Disconnecting client connection."); + + send_buffer_list_discard_new(&client->send_buffer_list, send_buffer); + + return (-1); + }; + + send_buffer_list_put(&client->send_buffer_list, send_buffer); + + return (0); +} + +static int +qnetd_client_msg_received_unexpected_msg(struct qnetd_client *client, + const struct msg_decoded *msg, const char *msg_str) +{ + + qnetd_log(LOG_ERR, "Received %s message. Sending back error message", msg_str); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE) != 0) { + return (-1); + } + + return (0); +} + +static int +qnetd_client_msg_received_preinit_reply(struct qnetd_instance *instance, + struct qnetd_client *client, const struct msg_decoded *msg) +{ + + return (qnetd_client_msg_received_unexpected_msg(client, msg, "preinit reply")); +} + +static int +qnetd_client_msg_received_starttls(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + PRFileDesc *new_pr_fd; + + if (!client->preinit_received) { + qnetd_log(LOG_ERR, "Received starttls before preinit message. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_PREINIT_REQUIRED) != 0) { + return (-1); + } + + return (0); + } + + if ((new_pr_fd = nss_sock_start_ssl_as_server(client->socket, instance->server.cert, + instance->server.private_key, instance->tls_client_cert_required, 0, NULL)) == NULL) { + qnetd_log_nss(LOG_ERR, "Can't start TLS. Disconnecting client."); + + return (-1); + } + + client->tls_started = 1; + client->tls_peer_certificate_verified = 0; + client->socket = new_pr_fd; + + return (0); +} + +static int +qnetd_client_msg_received_server_error(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + + return (qnetd_client_msg_received_unexpected_msg(client, msg, "server error")); +} + +/* + * Checks if new client send information are valid. It means: + * - in cluster is no duplicate node with same nodeid + * - it has same tie_breaker as other nodes in cluster + * - it has same algorithm as other nodes in cluster + */ +static enum tlv_reply_error_code +qnetd_client_msg_received_init_check_new_client(struct qnetd_instance *instance, + struct qnetd_client *new_client) +{ + struct qnetd_cluster *cluster; + struct qnetd_client *client; + + cluster = qnetd_cluster_list_find_by_name(&instance->clusters, new_client->cluster_name, + new_client->cluster_name_len); + + if (cluster == NULL) { + return (TLV_REPLY_ERROR_CODE_NO_ERROR); + } + + TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) { + if (!tlv_tie_breaker_eq(&new_client->tie_breaker, &client->tie_breaker)) { + qnetd_log(LOG_ERR, "Received init message contains tie-breaker which " + "differs from rest of cluster. Sending error reply"); + + return (TLV_REPLY_ERROR_CODE_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES); + } + + if (new_client->decision_algorithm != client->decision_algorithm) { + qnetd_log(LOG_ERR, "Received init message contains algorithm which " + "differs from rest of cluster. Sending error reply"); + + return (TLV_REPLY_ERROR_CODE_ALGORITHM_DIFFERS_FROM_OTHER_NODES); + } + + if (new_client->node_id == client->node_id) { + qnetd_log(LOG_ERR, "Received init message contains node id which is " + "duplicate of other node in cluster. Sending error reply"); + + return (TLV_REPLY_ERROR_CODE_DUPLICATE_NODE_ID); + } + } + + return (TLV_REPLY_ERROR_CODE_NO_ERROR); +} + +static int +qnetd_client_msg_received_init(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + int res; + size_t zi; + enum msg_type *supported_msgs; + size_t no_supported_msgs; + enum tlv_opt_type *supported_opts; + size_t no_supported_opts; + struct send_buffer_list_entry *send_buffer; + enum tlv_reply_error_code reply_error_code; + struct qnetd_cluster *cluster; + + supported_msgs = NULL; + supported_opts = NULL; + no_supported_msgs = 0; + no_supported_opts = 0; + + reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR; + + if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) { + return (res == -1 ? -1 : 0); + } + + if (!client->preinit_received) { + qnetd_log(LOG_ERR, "Received init before preinit message. Sending error reply."); + + reply_error_code = TLV_REPLY_ERROR_CODE_PREINIT_REQUIRED; + } + + if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->node_id_set) { + qnetd_log(LOG_ERR, "Received init message without node id set. " + "Sending error reply."); + + reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION; + } else { + client->node_id_set = 1; + client->node_id = msg->node_id; + } + + if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->ring_id_set) { + qnetd_log(LOG_ERR, "Received init message without ring id set. " + "Sending error reply."); + + reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION; + } else { + memcpy(&client->last_ring_id, &msg->ring_id, sizeof(struct tlv_ring_id)); + } + + if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->heartbeat_interval_set) { + qnetd_log(LOG_ERR, "Received init message without heartbeat interval set. " + "Sending error reply."); + + reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION; + } else { + if (msg->heartbeat_interval < instance->advanced_settings->heartbeat_interval_min || + msg->heartbeat_interval > instance->advanced_settings->heartbeat_interval_max) { + qnetd_log(LOG_ERR, "Client requested invalid heartbeat interval %u. " + "Sending error reply.", msg->heartbeat_interval); + + reply_error_code = TLV_REPLY_ERROR_CODE_INVALID_HEARTBEAT_INTERVAL; + } else { + client->heartbeat_interval = msg->heartbeat_interval; + } + } + + if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->tie_breaker_set) { + qnetd_log(LOG_ERR, "Received init message without tie-breaker set. " + "Sending error reply."); + + reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION; + } else { + memcpy(&client->tie_breaker, &msg->tie_breaker, sizeof(msg->tie_breaker)); + } + + if (msg->supported_messages != NULL) { + /* + * Client sent supported messages. For now this is ignored but in the future + * this may be used to ensure backward compatibility. + */ +/* + for (i = 0; i < msg->no_supported_messages; i++) { + qnetd_log(LOG_DEBUG, "Client supports %u message", + (int)msg->supported_messages[i]); + } +*/ + + /* + * Sent back supported messages + */ + msg_get_supported_messages(&supported_msgs, &no_supported_msgs); + } + + if (msg->supported_options != NULL) { + /* + * Client sent supported options. For now this is ignored but in the future + * this may be used to ensure backward compatibility. + */ +/* + for (i = 0; i < msg->no_supported_options; i++) { + qnetd_log(LOG_DEBUG, "Client supports %u option", + (int)msg->supported_messages[i]); + } +*/ + + /* + * Send back supported options + */ + tlv_get_supported_options(&supported_opts, &no_supported_opts); + } + + if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR && !msg->decision_algorithm_set) { + qnetd_log(LOG_ERR, "Received init message without decision algorithm. " + "Sending error reply."); + + reply_error_code = TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION; + } else { + /* + * Check if decision algorithm requested by client is supported + */ + res = 0; + + for (zi = 0; zi < QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE && !res; zi++) { + if (qnetd_static_supported_decision_algorithms[zi] == + msg->decision_algorithm) { + res = 1; + } + } + + if (!res) { + qnetd_log(LOG_ERR, "Client requested unsupported decision algorithm %u. " + "Sending error reply.", msg->decision_algorithm); + + reply_error_code = TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM; + } + + client->decision_algorithm = msg->decision_algorithm; + } + + if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR) { + reply_error_code = qnetd_client_msg_received_init_check_new_client(instance, + client); + } + + if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR) { + cluster = qnetd_cluster_list_add_client(&instance->clusters, client); + if (cluster == NULL) { + qnetd_log(LOG_ERR, "Can't add client to cluster list. " + "Sending error reply."); + + reply_error_code = TLV_REPLY_ERROR_CODE_INTERNAL_ERROR; + } else { + client->cluster = cluster; + client->cluster_list = &instance->clusters; + } + } + + if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR) { + qnetd_log_debug_new_client_connected(client); + + reply_error_code = qnetd_algorithm_client_init(client); + } + + if (reply_error_code == TLV_REPLY_ERROR_CODE_NO_ERROR) { + /* + * Correct init received + */ + client->init_received = 1; + } else { + qnetd_log(LOG_ERR, "Algorithm returned error code. Sending error reply."); + } + + send_buffer = send_buffer_list_get_new(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log(LOG_ERR, "Can't alloc init reply msg from list. " + "Disconnecting client connection."); + + return (-1); + } + + if (msg_create_init_reply(&send_buffer->buffer, msg->seq_number_set, msg->seq_number, + reply_error_code, + supported_msgs, no_supported_msgs, supported_opts, no_supported_opts, + instance->advanced_settings->max_client_receive_size, + instance->advanced_settings->max_client_send_size, + qnetd_static_supported_decision_algorithms, + QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE) == 0) { + qnetd_log(LOG_ERR, "Can't alloc init reply msg. Disconnecting client connection."); + + send_buffer_list_discard_new(&client->send_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&client->send_buffer_list, send_buffer); + + return (0); +} + +static int +qnetd_client_msg_received_init_reply(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + + return (qnetd_client_msg_received_unexpected_msg(client, msg, "init reply")); +} + +static int +qnetd_client_msg_received_set_option_reply(struct qnetd_instance *instance, + struct qnetd_client *client, const struct msg_decoded *msg) +{ + + return (qnetd_client_msg_received_unexpected_msg(client, msg, "set option reply")); +} + +static int +qnetd_client_msg_received_set_option(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + int res; + struct send_buffer_list_entry *send_buffer; + + if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) { + return (res == -1 ? -1 : 0); + } + + if (!client->init_received) { + qnetd_log(LOG_ERR, "Received set option message before init message. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) { + return (-1); + } + + return (0); + } + + if (msg->heartbeat_interval_set) { + /* + * Check if heartbeat interval is valid + */ + if (msg->heartbeat_interval < instance->advanced_settings->heartbeat_interval_min || + msg->heartbeat_interval > instance->advanced_settings->heartbeat_interval_max) { + qnetd_log(LOG_ERR, "Client requested invalid heartbeat interval %u. " + "Sending error reply.", msg->heartbeat_interval); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_INVALID_HEARTBEAT_INTERVAL) != 0) { + return (-1); + } + + return (0); + } + + client->heartbeat_interval = msg->heartbeat_interval; + } + + send_buffer = send_buffer_list_get_new(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log(LOG_ERR, "Can't alloc set option reply msg from list. " + "Disconnecting client connection."); + + return (-1); + } + + if (msg_create_set_option_reply(&send_buffer->buffer, msg->seq_number_set, msg->seq_number, + client->heartbeat_interval) == 0) { + qnetd_log(LOG_ERR, "Can't alloc set option reply msg. " + "Disconnecting client connection."); + + send_buffer_list_discard_new(&client->send_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&client->send_buffer_list, send_buffer); + + return (0); +} + +static int +qnetd_client_msg_received_echo_reply(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + + return (qnetd_client_msg_received_unexpected_msg(client, msg, "echo reply")); +} + +static int +qnetd_client_msg_received_echo_request(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg, const struct dynar *msg_orig) +{ + int res; + struct send_buffer_list_entry *send_buffer; + + if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) { + return (res == -1 ? -1 : 0); + } + + if (!client->init_received) { + qnetd_log(LOG_ERR, "Received echo request before init message. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) { + return (-1); + } + + return (0); + } + + send_buffer = send_buffer_list_get_new(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log(LOG_ERR, "Can't alloc echo reply msg from list. " + "Disconnecting client connection."); + + return (-1); + } + + if (msg_create_echo_reply(&send_buffer->buffer, msg_orig) == 0) { + qnetd_log(LOG_ERR, "Can't alloc echo reply msg. Disconnecting client connection."); + + send_buffer_list_discard_new(&client->send_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&client->send_buffer_list, send_buffer); + + return (0); +} + +static int +qnetd_client_msg_received_node_list(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + int res; + struct send_buffer_list_entry *send_buffer; + enum tlv_reply_error_code reply_error_code; + enum tlv_vote result_vote; + int case_processed; + + reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR; + + if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) { + return (res == -1 ? -1 : 0); + } + + if (!client->init_received) { + qnetd_log(LOG_ERR, "Received node list message before init message. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) { + return (-1); + } + + return (0); + } + + if (!msg->node_list_type_set) { + qnetd_log(LOG_ERR, "Received node list message without node list type set. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) { + return (-1); + } + + return (0); + } + + if (!msg->seq_number_set) { + qnetd_log(LOG_ERR, "Received node list message without seq number set. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) { + return (-1); + } + + return (0); + } + + result_vote = TLV_VOTE_NO_CHANGE; + + case_processed = 0; + switch (msg->node_list_type) { + case TLV_NODE_LIST_TYPE_INITIAL_CONFIG: + case TLV_NODE_LIST_TYPE_CHANGED_CONFIG: + case_processed = 1; + qnetd_log_debug_config_node_list_received(client, msg->seq_number, + msg->config_version_set, msg->config_version, &msg->nodes, + (msg->node_list_type == TLV_NODE_LIST_TYPE_INITIAL_CONFIG)); + + reply_error_code = qnetd_algorithm_config_node_list_received(client, + msg->seq_number, msg->config_version_set, msg->config_version, + &msg->nodes, + (msg->node_list_type == TLV_NODE_LIST_TYPE_INITIAL_CONFIG), + &result_vote); + break; + case TLV_NODE_LIST_TYPE_MEMBERSHIP: + case_processed = 1; + if (!msg->ring_id_set) { + qnetd_log(LOG_ERR, "Received node list message without ring id number set. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) { + return (-1); + } + + return (0); + } + + qnetd_log_debug_membership_node_list_received(client, msg->seq_number, &msg->ring_id, + msg->heuristics, &msg->nodes); + + reply_error_code = qnetd_algorithm_membership_node_list_received(client, + msg->seq_number, &msg->ring_id, &msg->nodes, msg->heuristics, &result_vote); + break; + case TLV_NODE_LIST_TYPE_QUORUM: + case_processed = 1; + if (!msg->quorate_set) { + qnetd_log(LOG_ERR, "Received quorum list message without quorate set. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) { + return (-1); + } + + return (0); + } + + qnetd_log_debug_quorum_node_list_received(client, msg->seq_number,msg->quorate, + &msg->nodes); + + reply_error_code = qnetd_algorithm_quorum_node_list_received(client, + msg->seq_number,msg->quorate, &msg->nodes, &result_vote); + break; + /* + * Default is not defined intentionally. Compiler shows warning when new + * node list type is added + */ + } + + if (!case_processed) { + qnetd_log(LOG_ERR, "qnetd_client_msg_received_node_list fatal error. " + "Unhandled node_list_type"); + exit(1); + } + + if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) { + qnetd_log(LOG_ERR, "Algorithm returned error code. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + reply_error_code) != 0) { + return (-1); + } + + return (0); + } else { + qnetd_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote)); + } + + /* + * Store node list for future use + */ + case_processed = 0; + switch (msg->node_list_type) { + case TLV_NODE_LIST_TYPE_INITIAL_CONFIG: + case TLV_NODE_LIST_TYPE_CHANGED_CONFIG: + case_processed = 1; + node_list_free(&client->configuration_node_list); + if (node_list_clone(&client->configuration_node_list, &msg->nodes) == -1) { + qnetd_log(LOG_ERR, "Can't alloc config node list clone. " + "Disconnecting client connection."); + + return (-1); + } + client->config_version_set = msg->config_version_set; + client->config_version = msg->config_version; + + break; + case TLV_NODE_LIST_TYPE_MEMBERSHIP: + case_processed = 1; + node_list_free(&client->last_membership_node_list); + if (node_list_clone(&client->last_membership_node_list, &msg->nodes) == -1) { + qnetd_log(LOG_ERR, "Can't alloc membership node list clone. " + "Disconnecting client connection."); + + return (-1); + } + memcpy(&client->last_ring_id, &msg->ring_id, sizeof(struct tlv_ring_id)); + client->last_membership_heuristics = msg->heuristics; + client->last_heuristics = msg->heuristics; + break; + case TLV_NODE_LIST_TYPE_QUORUM: + case_processed = 1; + node_list_free(&client->last_quorum_node_list); + if (node_list_clone(&client->last_quorum_node_list, &msg->nodes) == -1) { + qnetd_log(LOG_ERR, "Can't alloc quorum node list clone. " + "Disconnecting client connection."); + + return (-1); + } + break; + /* + * Default is not defined intentionally. Compiler shows warning when new + * node list type is added + */ + } + + if (!case_processed) { + qnetd_log(LOG_ERR, "qnetd_client_msg_received_node_list fatal error. " + "Unhandled node_list_type"); + exit(1); + } + + /* + * Store result vote + */ + client->last_sent_vote = result_vote; + if (result_vote == TLV_VOTE_ACK || result_vote == TLV_VOTE_NACK) { + client->last_sent_ack_nack_vote = result_vote; + } + + send_buffer = send_buffer_list_get_new(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log(LOG_ERR, "Can't alloc node list reply msg from list. " + "Disconnecting client connection."); + + return (-1); + } + + if (msg_create_node_list_reply(&send_buffer->buffer, msg->seq_number, msg->node_list_type, + &client->last_ring_id, result_vote) == 0) { + qnetd_log(LOG_ERR, "Can't alloc node list reply msg. " + "Disconnecting client connection."); + + send_buffer_list_discard_new(&client->send_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&client->send_buffer_list, send_buffer); + + return (0); +} + +static int +qnetd_client_msg_received_node_list_reply(struct qnetd_instance *instance, + struct qnetd_client *client, const struct msg_decoded *msg) +{ + + return (qnetd_client_msg_received_unexpected_msg(client, msg, "node list reply")); +} + +static int +qnetd_client_msg_received_ask_for_vote(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + int res; + struct send_buffer_list_entry *send_buffer; + enum tlv_reply_error_code reply_error_code; + enum tlv_vote result_vote; + + reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR; + + if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) { + return (res == -1 ? -1 : 0); + } + + if (!client->init_received) { + qnetd_log(LOG_ERR, "Received ask for vote message before init message. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) { + return (-1); + } + + return (0); + } + + if (!msg->seq_number_set) { + qnetd_log(LOG_ERR, "Received ask for vote message without seq number set. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) { + return (-1); + } + + return (0); + } + + qnetd_log_debug_ask_for_vote_received(client, msg->seq_number); + + reply_error_code = qnetd_algorithm_ask_for_vote_received(client, msg->seq_number, + &result_vote); + + if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) { + qnetd_log(LOG_ERR, "Algorithm returned error code. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + reply_error_code) != 0) { + return (-1); + } + + return (0); + } else { + qnetd_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote)); + } + + /* + * Store result vote + */ + client->last_sent_vote = result_vote; + if (result_vote == TLV_VOTE_ACK || result_vote == TLV_VOTE_NACK) { + client->last_sent_ack_nack_vote = result_vote; + } + + send_buffer = send_buffer_list_get_new(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log(LOG_ERR, "Can't alloc ask for vote reply msg from list. " + "Disconnecting client connection."); + + return (-1); + } + + if (msg_create_ask_for_vote_reply(&send_buffer->buffer, msg->seq_number, + &client->last_ring_id, result_vote) == 0) { + qnetd_log(LOG_ERR, "Can't alloc ask for vote reply msg. " + "Disconnecting client connection."); + + send_buffer_list_discard_new(&client->send_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&client->send_buffer_list, send_buffer); + + return (0); +} + +static int +qnetd_client_msg_received_ask_for_vote_reply(struct qnetd_instance *instance, + struct qnetd_client *client, const struct msg_decoded *msg) +{ + + return (qnetd_client_msg_received_unexpected_msg(client, msg, "ask for vote reply")); +} + +static int +qnetd_client_msg_received_vote_info(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + + return (qnetd_client_msg_received_unexpected_msg(client, msg, "vote info")); +} + +static int +qnetd_client_msg_received_vote_info_reply(struct qnetd_instance *instance, + struct qnetd_client *client, const struct msg_decoded *msg) +{ + int res; + enum tlv_reply_error_code reply_error_code; + + reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR; + + if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) { + return (res == -1 ? -1 : 0); + } + + if (!client->init_received) { + qnetd_log(LOG_ERR, "Received vote info reply before init message. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) { + return (-1); + } + + return (0); + } + + if (!msg->seq_number_set) { + qnetd_log(LOG_ERR, "Received vote info reply message without seq number set. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) { + return (-1); + } + + return (0); + } + + qnetd_log_debug_vote_info_reply_received(client, msg->seq_number); + + reply_error_code = qnetd_algorithm_vote_info_reply_received(client, msg->seq_number); + + if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) { + qnetd_log(LOG_ERR, "Algorithm returned error code. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + reply_error_code) != 0) { + return (-1); + } + + return (0); + } + + return (0); +} + +static int +qnetd_client_msg_received_heuristics_change(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + int res; + struct send_buffer_list_entry *send_buffer; + enum tlv_reply_error_code reply_error_code; + enum tlv_vote result_vote; + + reply_error_code = TLV_REPLY_ERROR_CODE_NO_ERROR; + + if ((res = qnetd_client_msg_received_check_tls(instance, client, msg)) != 0) { + return (res == -1 ? -1 : 0); + } + + if (!client->init_received) { + qnetd_log(LOG_ERR, "Received heuristics change message before init message. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) { + return (-1); + } + + return (0); + } + + if (!msg->seq_number_set || msg->heuristics == TLV_HEURISTICS_UNDEFINED) { + qnetd_log(LOG_ERR, "Received heuristics change message without seq number set or " + "with undefined heuristics. Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) { + return (-1); + } + + return (0); + } + + qnetd_log_debug_heuristics_change_received(client, msg->seq_number, msg->heuristics); + + reply_error_code = qnetd_algorithm_heuristics_change_received(client, msg->seq_number, + msg->heuristics, &result_vote); + + if (reply_error_code != TLV_REPLY_ERROR_CODE_NO_ERROR) { + qnetd_log(LOG_ERR, "Algorithm returned error code. " + "Sending error reply."); + + if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number, + reply_error_code) != 0) { + return (-1); + } + + return (0); + } else { + qnetd_log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(result_vote)); + } + + /* + * Store result vote and heuristics result + */ + client->last_sent_vote = result_vote; + if (result_vote == TLV_VOTE_ACK || result_vote == TLV_VOTE_NACK) { + client->last_sent_ack_nack_vote = result_vote; + } + client->last_regular_heuristics = msg->heuristics; + client->last_heuristics = msg->heuristics; + + send_buffer = send_buffer_list_get_new(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log(LOG_ERR, "Can't alloc heuristics change reply msg from list. " + "Disconnecting client connection."); + + return (-1); + } + + if (msg_create_heuristics_change_reply(&send_buffer->buffer, msg->seq_number, + &client->last_ring_id, msg->heuristics, result_vote) == 0) { + qnetd_log(LOG_ERR, "Can't alloc heuristics change reply msg. " + "Disconnecting client connection."); + + send_buffer_list_discard_new(&client->send_buffer_list, send_buffer); + + return (-1); + } + + send_buffer_list_put(&client->send_buffer_list, send_buffer); + + return (0); +} + +static int +qnetd_client_msg_received_heuristics_change_reply(struct qnetd_instance *instance, struct qnetd_client *client, + const struct msg_decoded *msg) +{ + + return (qnetd_client_msg_received_unexpected_msg(client, msg, "heuristics change reply")); +} + +int +qnetd_client_msg_received(struct qnetd_instance *instance, struct qnetd_client *client) +{ + struct msg_decoded msg; + int res; + int ret_val; + int msg_processed; + + client->dpd_msg_received_since_last_check = 1; + + msg_decoded_init(&msg); + + res = msg_decode(&client->receive_buffer, &msg); + if (res != 0) { + /* + * Error occurred. Send server error. + */ + qnetd_log_msg_decode_error(res); + qnetd_log(LOG_INFO, "Sending back error message"); + + if (qnetd_client_send_err(client, msg.seq_number_set, msg.seq_number, + TLV_REPLY_ERROR_CODE_ERROR_DECODING_MSG) != 0) { + return (-1); + } + + return (0); + } + + ret_val = 0; + + msg_processed = 0; + switch (msg.type) { + case MSG_TYPE_PREINIT: + msg_processed = 1; + ret_val = qnetd_client_msg_received_preinit(instance, client, &msg); + break; + case MSG_TYPE_PREINIT_REPLY: + msg_processed = 1; + ret_val = qnetd_client_msg_received_preinit_reply(instance, client, &msg); + break; + case MSG_TYPE_STARTTLS: + msg_processed = 1; + ret_val = qnetd_client_msg_received_starttls(instance, client, &msg); + break; + case MSG_TYPE_INIT: + msg_processed = 1; + ret_val = qnetd_client_msg_received_init(instance, client, &msg); + break; + case MSG_TYPE_INIT_REPLY: + msg_processed = 1; + ret_val = qnetd_client_msg_received_init_reply(instance, client, &msg); + break; + case MSG_TYPE_SERVER_ERROR: + msg_processed = 1; + ret_val = qnetd_client_msg_received_server_error(instance, client, &msg); + break; + case MSG_TYPE_SET_OPTION: + msg_processed = 1; + ret_val = qnetd_client_msg_received_set_option(instance, client, &msg); + break; + case MSG_TYPE_SET_OPTION_REPLY: + msg_processed = 1; + ret_val = qnetd_client_msg_received_set_option_reply(instance, client, &msg); + break; + case MSG_TYPE_ECHO_REQUEST: + msg_processed = 1; + ret_val = qnetd_client_msg_received_echo_request(instance, client, &msg, + &client->receive_buffer); + break; + case MSG_TYPE_ECHO_REPLY: + msg_processed = 1; + ret_val = qnetd_client_msg_received_echo_reply(instance, client, &msg); + break; + case MSG_TYPE_NODE_LIST: + msg_processed = 1; + ret_val = qnetd_client_msg_received_node_list(instance, client, &msg); + break; + case MSG_TYPE_NODE_LIST_REPLY: + msg_processed = 1; + ret_val = qnetd_client_msg_received_node_list_reply(instance, client, &msg); + break; + case MSG_TYPE_ASK_FOR_VOTE: + msg_processed = 1; + ret_val = qnetd_client_msg_received_ask_for_vote(instance, client, &msg); + break; + case MSG_TYPE_ASK_FOR_VOTE_REPLY: + msg_processed = 1; + ret_val = qnetd_client_msg_received_ask_for_vote_reply(instance, client, &msg); + break; + case MSG_TYPE_VOTE_INFO: + msg_processed = 1; + ret_val = qnetd_client_msg_received_vote_info(instance, client, &msg); + break; + case MSG_TYPE_VOTE_INFO_REPLY: + msg_processed = 1; + ret_val = qnetd_client_msg_received_vote_info_reply(instance, client, &msg); + break; + case MSG_TYPE_HEURISTICS_CHANGE: + msg_processed = 1; + ret_val = qnetd_client_msg_received_heuristics_change(instance, client, &msg); + break; + case MSG_TYPE_HEURISTICS_CHANGE_REPLY: + msg_processed = 1; + ret_val = qnetd_client_msg_received_heuristics_change_reply(instance, client, + &msg); + break; + /* + * Default is not defined intentionally. Compiler shows warning when new + * msg type is added. + */ + } + + if (!msg_processed) { + qnetd_log(LOG_ERR, "Unsupported message %u received from client. " + "Sending back error message", msg.type); + + if (qnetd_client_send_err(client, msg.seq_number_set, msg.seq_number, + TLV_REPLY_ERROR_CODE_UNSUPPORTED_MESSAGE) != 0) { + ret_val = -1; + } + } + + msg_decoded_destroy(&msg); + + return (ret_val); +} diff --git a/qdevices/qnetd-client-msg-received.h b/qdevices/qnetd-client-msg-received.h new file mode 100644 index 0000000..072a77a --- /dev/null +++ b/qdevices/qnetd-client-msg-received.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 _QNETD_CLIENT_MSG_RECEIVED_H_ +#define _QNETD_CLIENT_MSG_RECEIVED_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qnetd_client_msg_received(struct qnetd_instance *instance, + struct qnetd_client *client); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_CLIENT_MSG_RECEIVED_H_ */ diff --git a/qdevices/qnetd-client-net.c b/qdevices/qnetd-client-net.c new file mode 100644 index 0000000..93ee50e --- /dev/null +++ b/qdevices/qnetd-client-net.c @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "msgio.h" +#include "msg.h" +#include "nss-sock.h" +#include "qnetd-log.h" +#include "qnetd-client-net.h" +#include "qnetd-client-send.h" +#include "qnetd-client-msg-received.h" + +#define CLIENT_ADDR_STR_LEN_COLON_PORT (1 + 5 + 1) +#define CLIENT_ADDR_STR_LEN (INET6_ADDRSTRLEN + CLIENT_ADDR_STR_LEN_COLON_PORT) + +static int +qnetd_client_net_write_finished(struct qnetd_instance *instance, struct qnetd_client *client) +{ + + /* + * Callback is currently unused + */ + + return (0); +} + +int +qnetd_client_net_write(struct qnetd_instance *instance, struct qnetd_client *client) +{ + int res; + struct send_buffer_list_entry *send_buffer; + + send_buffer = send_buffer_list_get_active(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log_nss(LOG_CRIT, "send_buffer_list_get_active returned NULL"); + + return (-1); + } + + res = msgio_write(client->socket, &send_buffer->buffer, + &send_buffer->msg_already_sent_bytes); + + if (res == 1) { + send_buffer_list_delete(&client->send_buffer_list, send_buffer); + + if (qnetd_client_net_write_finished(instance, client) == -1) { + return (-1); + } + } + + if (res == -1) { + qnetd_log_nss(LOG_CRIT, "PR_Send returned 0"); + + return (-1); + } + + if (res == -2) { + qnetd_log_nss(LOG_ERR, "Unhandled error when sending message to client"); + + return (-1); + } + + return (0); +} + + +/* + * -1 means end of connection (EOF) or some other unhandled error. 0 = success + */ +int +qnetd_client_net_read(struct qnetd_instance *instance, struct qnetd_client *client) +{ + int res; + int ret_val; + int orig_skipping_msg; + + orig_skipping_msg = client->skipping_msg; + + res = msgio_read(client->socket, &client->receive_buffer, + &client->msg_already_received_bytes, &client->skipping_msg); + + if (!orig_skipping_msg && client->skipping_msg) { + qnetd_log(LOG_DEBUG, "msgio_read set skipping_msg"); + } + + ret_val = 0; + + switch (res) { + case 0: + /* + * Partial read + */ + break; + case -1: + qnetd_log(LOG_DEBUG, "Client closed connection"); + ret_val = -1; + break; + case -2: + qnetd_log_nss(LOG_ERR, "Unhandled error when reading from client. " + "Disconnecting client"); + ret_val = -1; + break; + case -3: + qnetd_log(LOG_ERR, "Can't store message header from client. Disconnecting client"); + ret_val = -1; + break; + case -4: + qnetd_log(LOG_ERR, "Can't store message from client. Skipping message"); + client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_ERROR_DECODING_MSG; + break; + case -5: + qnetd_log(LOG_WARNING, "Client sent unsupported msg type %u. Skipping message", + msg_get_type(&client->receive_buffer)); + client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_UNSUPPORTED_MESSAGE; + break; + case -6: + qnetd_log(LOG_WARNING, + "Client wants to send too long message %u bytes. Skipping message", + msg_get_len(&client->receive_buffer)); + client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_MESSAGE_TOO_LONG; + break; + case 1: + /* + * Full message received / skipped + */ + if (!client->skipping_msg) { + if (qnetd_client_msg_received(instance, client) == -1) { + ret_val = -1; + } + } else { + if (qnetd_client_send_err(client, 0, 0, client->skipping_msg_reason) != 0) { + ret_val = -1; + } + } + + client->skipping_msg = 0; + client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_NO_ERROR; + client->msg_already_received_bytes = 0; + dynar_clean(&client->receive_buffer); + break; + default: + qnetd_log(LOG_ERR, "Unhandled msgio_read error %d\n", res); + exit(1); + break; + } + + return (ret_val); +} + +int +qnetd_client_net_accept(struct qnetd_instance *instance) +{ + PRNetAddr client_addr; + PRFileDesc *client_socket; + struct qnetd_client *client; + char *client_addr_str; + int res_err; + + client_addr_str = NULL; + + res_err = -1; + + if ((client_socket = PR_Accept(instance->server.socket, &client_addr, + PR_INTERVAL_NO_TIMEOUT)) == NULL) { + qnetd_log_nss(LOG_ERR, "Can't accept connection"); + return (-1); + } + + if (nss_sock_set_non_blocking(client_socket) != 0) { + qnetd_log_nss(LOG_ERR, "Can't set client socket to non blocking mode"); + goto exit_close; + } + + if (instance->max_clients != 0 && + qnetd_client_list_no_clients(&instance->clients) >= instance->max_clients) { + qnetd_log(LOG_ERR, "Maximum clients reached. Not accepting connection"); + goto exit_close; + } + + client_addr_str = malloc(CLIENT_ADDR_STR_LEN); + if (client_addr_str == NULL) { + qnetd_log(LOG_ERR, "Can't alloc client addr str memory. Not accepting connection"); + goto exit_close; + } + + if (PR_NetAddrToString(&client_addr, client_addr_str, CLIENT_ADDR_STR_LEN) != PR_SUCCESS) { + qnetd_log_nss(LOG_ERR, "Can't convert client address to string. Not accepting connection"); + goto exit_close; + } + + if (snprintf(client_addr_str + strlen(client_addr_str), + CLIENT_ADDR_STR_LEN_COLON_PORT, ":%"PRIu16, + ntohs(client_addr.ipv6.port)) >= CLIENT_ADDR_STR_LEN_COLON_PORT) { + qnetd_log(LOG_ERR, "Can't store port to client addr str. Not accepting connection"); + goto exit_close; + } + + client = qnetd_client_list_add(&instance->clients, client_socket, &client_addr, + client_addr_str, + instance->advanced_settings->max_client_receive_size, + instance->advanced_settings->max_client_send_buffers, + instance->advanced_settings->max_client_send_size, &instance->main_timer_list); + if (client == NULL) { + qnetd_log(LOG_ERR, "Can't add client to list"); + res_err = -2; + goto exit_close; + } + + return (0); + +exit_close: + free(client_addr_str); + PR_Close(client_socket); + + return (res_err); +} diff --git a/qdevices/qnetd-client-net.h b/qdevices/qnetd-client-net.h new file mode 100644 index 0000000..b9a2230 --- /dev/null +++ b/qdevices/qnetd-client-net.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_CLIENT_NET_H_ +#define _QNETD_CLIENT_NET_H_ + +#include + +#include "qnetd-client.h" +#include "qnetd-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qnetd_client_net_write(struct qnetd_instance *instance, + struct qnetd_client *client); + +extern int qnetd_client_net_read(struct qnetd_instance *instance, + struct qnetd_client *client); + +extern int qnetd_client_net_accept(struct qnetd_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_CLIENT_NET_H_ */ diff --git a/qdevices/qnetd-client-send.c b/qdevices/qnetd-client-send.c new file mode 100644 index 0000000..072e467 --- /dev/null +++ b/qdevices/qnetd-client-send.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include "qnetd-client-send.h" +#include "qnetd-log.h" +#include "qnetd-log-debug.h" +#include "msg.h" + +int +qnetd_client_send_err(struct qnetd_client *client, int add_msg_seq_number, uint32_t msg_seq_number, + enum tlv_reply_error_code reply) +{ + struct send_buffer_list_entry *send_buffer; + + send_buffer = send_buffer_list_get_new(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log(LOG_ERR, "Can't alloc server error msg from list. " + "Disconnecting client connection."); + + return (-1); + } + + if (msg_create_server_error(&send_buffer->buffer, add_msg_seq_number, + msg_seq_number, reply) == 0) { + qnetd_log(LOG_ERR, "Can't alloc server error msg. " + "Disconnecting client connection."); + + send_buffer_list_discard_new(&client->send_buffer_list, send_buffer); + return (-1); + }; + + send_buffer_list_put(&client->send_buffer_list, send_buffer); + + return (0); +} + +int +qnetd_client_send_vote_info(struct qnetd_client *client, uint32_t msg_seq_number, + const struct tlv_ring_id *ring_id, enum tlv_vote vote) +{ + struct send_buffer_list_entry *send_buffer; + + /* + * Store result vote + */ + client->last_sent_vote = vote; + if (vote == TLV_VOTE_ACK || vote == TLV_VOTE_NACK) { + client->last_sent_ack_nack_vote = vote; + } + + qnetd_log_debug_send_vote_info(client, msg_seq_number, vote); + + send_buffer = send_buffer_list_get_new(&client->send_buffer_list); + if (send_buffer == NULL) { + qnetd_log(LOG_ERR, "Can't alloc vote info msg from list. " + "Disconnecting client connection."); + + return (-1); + } + + if (msg_create_vote_info(&send_buffer->buffer, msg_seq_number, ring_id, vote) == 0) { + qnetd_log(LOG_ERR, "Can't alloc vote info msg. " + "Disconnecting client connection."); + + send_buffer_list_discard_new(&client->send_buffer_list, send_buffer); + return (-1); + }; + + send_buffer_list_put(&client->send_buffer_list, send_buffer); + + return (0); +} diff --git a/qdevices/qnetd-client-send.h b/qdevices/qnetd-client-send.h new file mode 100644 index 0000000..229f8a0 --- /dev/null +++ b/qdevices/qnetd-client-send.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_CLIENT_SEND_H_ +#define _QNETD_CLIENT_SEND_H_ + +#include + +#include "qnetd-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qnetd_client_send_err(struct qnetd_client *client, + int add_msg_seq_number, uint32_t msg_seq_number, enum tlv_reply_error_code reply); + +extern int qnetd_client_send_vote_info(struct qnetd_client *client, + uint32_t msg_seq_number, const struct tlv_ring_id *ring_id, enum tlv_vote vote); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_CLIENT_SEND_H_ */ diff --git a/qdevices/qnetd-client.c b/qdevices/qnetd-client.c new file mode 100644 index 0000000..229123e --- /dev/null +++ b/qdevices/qnetd-client.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#include "qnet-config.h" +#include "qnetd-client.h" + +void +qnetd_client_init(struct qnetd_client *client, PRFileDesc *sock, PRNetAddr *addr, + char *addr_str, + size_t max_receive_size, size_t max_send_buffers, size_t max_send_size, + struct timer_list *main_timer_list) +{ + + memset(client, 0, sizeof(*client)); + client->socket = sock; + client->addr_str = addr_str; + memcpy(&client->addr, addr, sizeof(*addr)); + dynar_init(&client->receive_buffer, max_receive_size); + send_buffer_list_init(&client->send_buffer_list, max_send_buffers, max_send_size); + node_list_init(&client->configuration_node_list); + node_list_init(&client->last_membership_node_list); + node_list_init(&client->last_quorum_node_list); + client->main_timer_list = main_timer_list; +} + +void +qnetd_client_destroy(struct qnetd_client *client) +{ + + free(client->cluster_name); + free(client->addr_str); + node_list_free(&client->last_quorum_node_list); + node_list_free(&client->last_membership_node_list); + node_list_free(&client->configuration_node_list); + send_buffer_list_free(&client->send_buffer_list); + dynar_destroy(&client->receive_buffer); +} diff --git a/qdevices/qnetd-client.h b/qdevices/qnetd-client.h new file mode 100644 index 0000000..4930d71 --- /dev/null +++ b/qdevices/qnetd-client.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_CLIENT_H_ +#define _QNETD_CLIENT_H_ + +#include + +#include +#include + +#include +#include "dynar.h" +#include "tlv.h" +#include "send-buffer-list.h" +#include "node-list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct qnetd_client { + PRFileDesc *socket; + PRNetAddr addr; + char *addr_str; + struct dynar receive_buffer; + struct send_buffer_list send_buffer_list; + size_t msg_already_received_bytes; + int skipping_msg; /* When incorrect message was received skip it */ + int tls_started; /* Set after TLS started */ + int tls_peer_certificate_verified; /* Certificate is verified only once */ + int preinit_received; + int init_received; + char *cluster_name; + size_t cluster_name_len; + uint8_t node_id_set; + uint32_t node_id; + enum tlv_decision_algorithm_type decision_algorithm; + struct tlv_tie_breaker tie_breaker; + uint32_t heartbeat_interval; + enum tlv_reply_error_code skipping_msg_reason; + void *algorithm_data; + struct node_list configuration_node_list; + uint8_t config_version_set; + uint64_t config_version; + struct node_list last_membership_node_list; + struct node_list last_quorum_node_list; + struct tlv_ring_id last_ring_id; + struct qnetd_cluster *cluster; + struct qnetd_cluster_list *cluster_list; + struct timer_list *main_timer_list; + struct timer_list_entry *algo_timer; + uint32_t algo_timer_vote_info_msq_seq_number; + int schedule_disconnect; + uint32_t dpd_time_since_last_check; + uint32_t dpd_msg_received_since_last_check; + enum tlv_vote last_sent_vote; + enum tlv_vote last_sent_ack_nack_vote; + enum tlv_heuristics last_membership_heuristics; /* Passed in membership node list */ + enum tlv_heuristics last_regular_heuristics; /* Passed in heuristics change callback */ + enum tlv_heuristics last_heuristics; /* Latest heuristics both membership and regular */ + TAILQ_ENTRY(qnetd_client) entries; + TAILQ_ENTRY(qnetd_client) cluster_entries; +}; + +extern void qnetd_client_init(struct qnetd_client *client, PRFileDesc *sock, + PRNetAddr *addr, char *addr_str, size_t max_receive_size, size_t max_send_buffers, + size_t max_send_size, struct timer_list *main_timer_list); + +extern void qnetd_client_destroy(struct qnetd_client *client); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_CLIENT_H_ */ diff --git a/qdevices/qnetd-cluster-list.c b/qdevices/qnetd-cluster-list.c new file mode 100644 index 0000000..8739bb3 --- /dev/null +++ b/qdevices/qnetd-cluster-list.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "qnetd-cluster-list.h" + +void +qnetd_cluster_list_init(struct qnetd_cluster_list *list) +{ + + TAILQ_INIT(list); +} + +struct qnetd_cluster * +qnetd_cluster_list_find_by_name(struct qnetd_cluster_list *list, + const char *cluster_name, size_t cluster_name_len) +{ + struct qnetd_cluster *cluster; + + TAILQ_FOREACH(cluster, list, entries) { + if (cluster->cluster_name_len == cluster_name_len && + memcmp(cluster->cluster_name, cluster_name, cluster_name_len) == 0) { + return (cluster); + } + } + + return (NULL); +} + +struct qnetd_cluster * +qnetd_cluster_list_add_client(struct qnetd_cluster_list *list, struct qnetd_client *client) +{ + struct qnetd_cluster *cluster; + + cluster = qnetd_cluster_list_find_by_name(list, client->cluster_name, + client->cluster_name_len); + if (cluster == NULL) { + cluster = (struct qnetd_cluster *)malloc(sizeof(*cluster)); + if (cluster == NULL) { + return (NULL); + } + + if (qnetd_cluster_init(cluster, client->cluster_name, + client->cluster_name_len) != 0) { + free(cluster); + + return (NULL); + } + + TAILQ_INSERT_TAIL(list, cluster, entries); + } + + TAILQ_INSERT_TAIL(&cluster->client_list, client, cluster_entries); + + return (cluster); +} + +void +qnetd_cluster_list_del_client(struct qnetd_cluster_list *list, struct qnetd_cluster *cluster, + struct qnetd_client *client) +{ + + TAILQ_REMOVE(&cluster->client_list, client, cluster_entries); + + if (TAILQ_EMPTY(&cluster->client_list)) { + TAILQ_REMOVE(list, cluster, entries); + + qnetd_cluster_destroy(cluster); + free(cluster); + } +} + +void +qnetd_cluster_list_free(struct qnetd_cluster_list *list) +{ + struct qnetd_cluster *cluster; + struct qnetd_cluster *cluster_next; + + cluster = TAILQ_FIRST(list); + while (cluster != NULL) { + cluster_next = TAILQ_NEXT(cluster, entries); + + qnetd_cluster_destroy(cluster); + free(cluster); + + cluster = cluster_next; + } + + TAILQ_INIT(list); +} + +size_t +qnetd_cluster_list_size(const struct qnetd_cluster_list *list) +{ + size_t res; + struct qnetd_cluster *cluster; + + res = 0; + + TAILQ_FOREACH(cluster, list, entries) { + res++; + } + + return (res); +} diff --git a/qdevices/qnetd-cluster-list.h b/qdevices/qnetd-cluster-list.h new file mode 100644 index 0000000..45f49df --- /dev/null +++ b/qdevices/qnetd-cluster-list.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_CLUSTER_LIST_H_ +#define _QNETD_CLUSTER_LIST_H_ + +#include + +#include +#include + +#include "qnetd-client-list.h" +#include "qnetd-cluster.h" + +#ifdef __cplusplus +extern "C" { +#endif + +TAILQ_HEAD(qnetd_cluster_list, qnetd_cluster); + +extern void qnetd_cluster_list_init(struct qnetd_cluster_list *list); + +extern struct qnetd_cluster *qnetd_cluster_list_find_by_name( + struct qnetd_cluster_list *list, const char *cluster_name, size_t cluster_name_len); + +extern struct qnetd_cluster *qnetd_cluster_list_add_client( + struct qnetd_cluster_list *list, struct qnetd_client *client); + +extern void qnetd_cluster_list_del_client( + struct qnetd_cluster_list *list, struct qnetd_cluster *cluster, struct qnetd_client *client); + +extern void qnetd_cluster_list_free(struct qnetd_cluster_list *list); + +extern size_t qnetd_cluster_list_size( + const struct qnetd_cluster_list *list); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_CLUSTER_LIST_H_ */ diff --git a/qdevices/qnetd-cluster.c b/qdevices/qnetd-cluster.c new file mode 100644 index 0000000..32c2d3b --- /dev/null +++ b/qdevices/qnetd-cluster.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "qnetd-cluster.h" + +int +qnetd_cluster_init(struct qnetd_cluster *cluster, const char *cluster_name, size_t cluster_name_len) +{ + + memset(cluster, 0, sizeof(*cluster)); + + cluster->cluster_name = malloc(cluster_name_len + 1); + if (cluster->cluster_name == NULL) { + return (-1); + } + memset(cluster->cluster_name, 0, cluster_name_len + 1); + memcpy(cluster->cluster_name, cluster_name, cluster_name_len); + + cluster->cluster_name_len = cluster_name_len; + TAILQ_INIT(&cluster->client_list); + + return (0); +} + +void +qnetd_cluster_destroy(struct qnetd_cluster *cluster) +{ + + free(cluster->cluster_name); + cluster->cluster_name = NULL; +} + +size_t +qnetd_cluster_size(const struct qnetd_cluster *cluster) +{ + size_t res; + struct qnetd_client *client; + + res = 0; + + TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) { + res++; + } + + return (res); +} + +struct qnetd_client * +qnetd_cluster_find_client_by_node_id(const struct qnetd_cluster *cluster, uint32_t node_id) +{ + struct qnetd_client *client; + + TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) { + if (client->node_id == node_id) { + return (client); + } + } + + return (NULL); +} diff --git a/qdevices/qnetd-cluster.h b/qdevices/qnetd-cluster.h new file mode 100644 index 0000000..98d6614 --- /dev/null +++ b/qdevices/qnetd-cluster.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_CLUSTER_H_ +#define _QNETD_CLUSTER_H_ + +#include + +#include +#include + +#include "tlv.h" +#include "qnetd-client-list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct qnetd_cluster { + char *cluster_name; + size_t cluster_name_len; + void *algorithm_data; + struct qnetd_client_list client_list; + TAILQ_ENTRY(qnetd_cluster) entries; +}; + +extern int qnetd_cluster_init(struct qnetd_cluster *cluster, + const char *cluster_name, size_t cluster_name_len); + +extern void qnetd_cluster_destroy(struct qnetd_cluster *cluster); + +extern size_t qnetd_cluster_size(const struct qnetd_cluster *cluster); + +extern struct qnetd_client *qnetd_cluster_find_client_by_node_id( + const struct qnetd_cluster *cluster, uint32_t node_id); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_CLUSTER_H_ */ diff --git a/qdevices/qnetd-dpd-timer.c b/qdevices/qnetd-dpd-timer.c new file mode 100644 index 0000000..73d569a --- /dev/null +++ b/qdevices/qnetd-dpd-timer.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qnetd-dpd-timer.h" +#include "qnetd-log.h" + +static int +qnetd_dpd_timer_cb(void *data1, void *data2) +{ + struct qnetd_instance *instance; + struct qnetd_client *client; + + instance = (struct qnetd_instance *)data1; + + TAILQ_FOREACH(client, &instance->clients, entries) { + if (!client->init_received) { + continue; + } + + client->dpd_time_since_last_check += instance->advanced_settings->dpd_interval; + + if (client->dpd_time_since_last_check > (client->heartbeat_interval * 2)) { + if (!client->dpd_msg_received_since_last_check) { + qnetd_log(LOG_WARNING, "Client %s doesn't sent any message during " + "%"PRIu32"ms. Disconnecting", + client->addr_str, client->dpd_time_since_last_check); + + client->schedule_disconnect = 1; + } else { + client->dpd_time_since_last_check = 0; + client->dpd_msg_received_since_last_check = 0; + } + } + } + + return (-1); +} + +int +qnetd_dpd_timer_init(struct qnetd_instance *instance) +{ + + if (!instance->advanced_settings->dpd_enabled) { + return (0); + } + + instance->dpd_timer = timer_list_add(&instance->main_timer_list, + instance->advanced_settings->dpd_interval, + qnetd_dpd_timer_cb, (void *)instance, NULL); + if (instance->dpd_timer == NULL) { + qnetd_log(LOG_ERR, "Can't initialize dpd timer"); + + return (-1); + } + + return (0); +} + +void +qnetd_dpd_timer_destroy(struct qnetd_instance *instance) +{ + + if (instance->dpd_timer != NULL) { + timer_list_delete(&instance->main_timer_list, instance->dpd_timer); + instance->dpd_timer = NULL; + } +} diff --git a/qdevices/qnetd-dpd-timer.h b/qdevices/qnetd-dpd-timer.h new file mode 100644 index 0000000..ddd8fac --- /dev/null +++ b/qdevices/qnetd-dpd-timer.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 _QNETD_DPD_TIMER_H_ +#define _QNETD_DPD_TIMER_H_ + +#include "qnetd-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qnetd_dpd_timer_init(struct qnetd_instance *instance); + +extern void qnetd_dpd_timer_destroy(struct qnetd_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_DPD_TIMER_H_ */ diff --git a/qdevices/qnetd-instance.c b/qdevices/qnetd-instance.c new file mode 100644 index 0000000..931b18a --- /dev/null +++ b/qdevices/qnetd-instance.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include "qnetd-instance.h" +#include "qnetd-client.h" +#include "qnetd-algorithm.h" +#include "qnetd-log-debug.h" +#include "qnetd-dpd-timer.h" +#include "qnetd-poll-array-user-data.h" +#include "qnetd-client-algo-timer.h" + +int +qnetd_instance_init(struct qnetd_instance *instance, + enum tlv_tls_supported tls_supported, int tls_client_cert_required, size_t max_clients, + const struct qnetd_advanced_settings *advanced_settings) +{ + + memset(instance, 0, sizeof(*instance)); + + instance->advanced_settings = advanced_settings; + + pr_poll_array_init(&instance->poll_array, sizeof(struct qnetd_poll_array_user_data)); + qnetd_client_list_init(&instance->clients); + qnetd_cluster_list_init(&instance->clusters); + + instance->tls_supported = tls_supported; + instance->tls_client_cert_required = tls_client_cert_required; + + instance->max_clients = max_clients; + + timer_list_init(&instance->main_timer_list); + + if (qnetd_dpd_timer_init(instance) != 0) { + return (0); + } + + return (0); +} + +int +qnetd_instance_destroy(struct qnetd_instance *instance) +{ + struct qnetd_client *client; + struct qnetd_client *client_next; + + qnetd_dpd_timer_destroy(instance); + + client = TAILQ_FIRST(&instance->clients); + while (client != NULL) { + client_next = TAILQ_NEXT(client, entries); + + qnetd_instance_client_disconnect(instance, client, 1); + + client = client_next; + } + + pr_poll_array_destroy(&instance->poll_array); + qnetd_cluster_list_free(&instance->clusters); + qnetd_client_list_free(&instance->clients); + timer_list_free(&instance->main_timer_list); + + return (0); +} + +void +qnetd_instance_client_disconnect(struct qnetd_instance *instance, struct qnetd_client *client, + int server_going_down) +{ + + qnetd_log_debug_client_disconnect(client, server_going_down); + + if (client->init_received) { + qnetd_algorithm_client_disconnect(client, server_going_down); + } + + PR_Close(client->socket); + if (client->cluster != NULL) { + qnetd_cluster_list_del_client(&instance->clusters, client->cluster, client); + } + qnetd_client_algo_timer_abort(client); + qnetd_client_list_del(&instance->clients, client); +} + +int +qnetd_instance_init_certs(struct qnetd_instance *instance) +{ + + instance->server.cert = PK11_FindCertFromNickname( + instance->advanced_settings->cert_nickname, NULL); + if (instance->server.cert == NULL) { + return (-1); + } + + instance->server.private_key = PK11_FindKeyByAnyCert(instance->server.cert, NULL); + if (instance->server.private_key == NULL) { + return (-1); + } + + return (0); +} diff --git a/qdevices/qnetd-instance.h b/qdevices/qnetd-instance.h new file mode 100644 index 0000000..d2c4733 --- /dev/null +++ b/qdevices/qnetd-instance.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_INSTANCE_H_ +#define _QNETD_INSTANCE_H_ + +#include + +#include +#include +#include + +#include "qnetd-client-list.h" +#include "qnetd-cluster-list.h" +#include "pr-poll-array.h" +#include "qnet-config.h" +#include "timer-list.h" +#include "unix-socket-ipc.h" +#include "qnetd-advanced-settings.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct qnetd_instance { + struct { + PRFileDesc *socket; + CERTCertificate *cert; + SECKEYPrivateKey *private_key; + } server; + size_t max_clients; + struct qnetd_client_list clients; + struct qnetd_cluster_list clusters; + struct pr_poll_array poll_array; + enum tlv_tls_supported tls_supported; + int tls_client_cert_required; + const char *host_addr; + uint16_t host_port; + struct timer_list main_timer_list; + struct timer_list_entry *dpd_timer; /* Dead peer detection timer */ + struct unix_socket_ipc local_ipc; + PRFileDesc *ipc_socket_poll_fd; + const struct qnetd_advanced_settings *advanced_settings; +}; + +extern int qnetd_instance_init(struct qnetd_instance *instance, + enum tlv_tls_supported tls_supported, int tls_client_cert_required, size_t max_clients, + const struct qnetd_advanced_settings *advanced_settings); + +extern int qnetd_instance_destroy(struct qnetd_instance *instance); + +extern void qnetd_instance_client_disconnect(struct qnetd_instance *instance, + struct qnetd_client *client, int server_going_down); + +extern int qnetd_instance_init_certs(struct qnetd_instance *instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_INSTANCE_H_ */ diff --git a/qdevices/qnetd-ipc-cmd.c b/qdevices/qnetd-ipc-cmd.c new file mode 100644 index 0000000..3c44f34 --- /dev/null +++ b/qdevices/qnetd-ipc-cmd.c @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dynar-str.h" +#include "qnetd-ipc-cmd.h" +#include "qnetd-log.h" +#include "utils.h" + +int +qnetd_ipc_cmd_status(struct qnetd_instance *instance, struct dynar *outbuf, int verbose) +{ + + if (dynar_str_catf(outbuf, "QNetd address:\t\t\t%s:%"PRIu16"\n", + (instance->host_addr != NULL ? instance->host_addr : "*"), instance->host_port) == -1) { + return (-1); + } + + if (dynar_str_catf(outbuf, "TLS:\t\t\t\t%s%s\n", + tlv_tls_supported_to_str(instance->tls_supported), + ((instance->tls_supported != TLV_TLS_UNSUPPORTED && instance->tls_client_cert_required) ? + " (client certificate required)" : "")) == -1) { + return (-1); + } + + if (dynar_str_catf(outbuf, "Connected clients:\t\t%zu\n", + qnetd_client_list_no_clients(&instance->clients)) == -1) { + return (-1); + } + + if (dynar_str_catf(outbuf, "Connected clusters:\t\t%zu\n", + qnetd_cluster_list_size(&instance->clusters)) == -1) { + return (-1); + } + + if (!verbose) { + return (0); + } + + if (instance->max_clients != 0) { + if (dynar_str_catf(outbuf, "Maximum allowed clients:\t%zu\n", + instance->max_clients) == -1) { + return (-1); + } + } + + if (dynar_str_catf(outbuf, "Maximum send/receive size:\t%zu/%zu bytes\n", + instance->advanced_settings->max_client_send_size, + instance->advanced_settings->max_client_receive_size) == -1) { + return (-1); + } + + return (0); +} + +static int +qnetd_ipc_cmd_add_tie_breaker(const struct qnetd_client *client, + struct dynar *outbuf) +{ + + if (dynar_str_catf(outbuf, " Tie-breaker:\t") == -1) { + return (0); + } + + switch (client->tie_breaker.mode) { + case TLV_TIE_BREAKER_MODE_LOWEST: + if (dynar_str_catf(outbuf, "Node with lowest node ID") == -1) { + return (0); + } + break; + case TLV_TIE_BREAKER_MODE_HIGHEST: + if (dynar_str_catf(outbuf, "Node with highest node ID") == -1) { + return (0); + } + break; + case TLV_TIE_BREAKER_MODE_NODE_ID: + if (dynar_str_catf(outbuf, "Node with node ID "UTILS_PRI_NODE_ID, + client->tie_breaker.node_id) == -1) { + return (0); + } + break; + } + + return (dynar_str_catf(outbuf, "\n") != -1); +} + +static int +qnetd_ipc_cmd_list_add_node_list(struct dynar *outbuf, int verbose, const struct node_list *nlist) +{ + struct node_list_entry *node_info; + int i; + + i = 0; + + TAILQ_FOREACH(node_info, nlist, entries) { + if (i != 0) { + if (dynar_str_catf(outbuf, ", ") == -1) { + return (-1); + } + } + + if (dynar_str_catf(outbuf, UTILS_PRI_NODE_ID, node_info->node_id) == -1) { + return (-1); + } + + if (node_info->data_center_id != 0) { + if (dynar_str_catf(outbuf, " (" UTILS_PRI_DATACENTER_ID ")", + node_info->data_center_id) == -1) { + return (-1); + } + + } + + i++; + } + + return (0); +} + +static int +qnetd_ipc_cmd_list_add_client_info(const struct qnetd_client *client, struct dynar *outbuf, + int verbose, size_t client_no) +{ + + if (dynar_str_catf(outbuf, " Node ID "UTILS_PRI_NODE_ID":\n", + client->node_id) == -1) { + return (-1); + } + + if (dynar_str_catf(outbuf, " Client address:\t\t%s\n", + client->addr_str) == -1) { + return (-1); + } + + if (verbose) { + if (dynar_str_catf(outbuf, " HB interval:\t\t%"PRIu32"ms\n", + client->heartbeat_interval) == -1) { + return (-1); + } + } + + if (client->config_version_set) { + if (dynar_str_catf(outbuf, " Configuration version:\t" + UTILS_PRI_CONFIG_VERSION"\n", client->config_version) == -1) { + return (-1); + } + } + + if (!node_list_is_empty(&client->configuration_node_list)) { + if ((dynar_str_catf(outbuf, " Configured node list:\t") == -1) || + (qnetd_ipc_cmd_list_add_node_list(outbuf, verbose, + &client->configuration_node_list) == -1) || + (dynar_str_catf(outbuf, "\n") == -1)) { + return (-1); + } + } + + if (verbose) { + if (dynar_str_catf(outbuf, " Ring ID:\t\t"UTILS_PRI_RING_ID"\n", + client->last_ring_id.node_id, client->last_ring_id.seq) == -1) { + return (-1); + } + } + + if (!node_list_is_empty(&client->last_membership_node_list)) { + if ((dynar_str_catf(outbuf, " Membership node list:\t") == -1) || + (qnetd_ipc_cmd_list_add_node_list(outbuf, verbose, + &client->last_membership_node_list) == -1) || + (dynar_str_catf(outbuf, "\n") == -1)) { + return (-1); + } + } + + if (client->last_heuristics != TLV_HEURISTICS_UNDEFINED || verbose) { + if (dynar_str_catf(outbuf, " Heuristics:\t\t%s", + tlv_heuristics_to_str(client->last_heuristics)) == -1) { + return (-1); + } + + if (verbose) { + if (dynar_str_catf(outbuf, " (membership: %s, regular: %s)", + tlv_heuristics_to_str(client->last_membership_heuristics), + tlv_heuristics_to_str(client->last_regular_heuristics)) == -1) { + return (-1); + } + } + + if (dynar_str_catf(outbuf, "\n") == -1) { + return (-1); + } + } + + if (verbose) { + if (dynar_str_catf(outbuf, " TLS active:\t\t%s", + (client->tls_started ? "Yes" : "No")) == -1) { + return (-1); + } + + if (client->tls_started && client->tls_peer_certificate_verified) { + if (dynar_str_catf(outbuf, " (client certificate verified)") == -1) { + return (-1); + } + } + + if (dynar_str_catf(outbuf, "\n") == -1) { + return (-1); + } + } + + if (client->last_sent_vote != TLV_VOTE_UNDEFINED) { + if (dynar_str_catf(outbuf, " Vote:\t\t\t%s", + tlv_vote_to_str(client->last_sent_vote)) == -1) { + return (-1); + } + + if (client->last_sent_ack_nack_vote != TLV_VOTE_UNDEFINED) { + if (dynar_str_catf(outbuf, " (%s)", + tlv_vote_to_str(client->last_sent_ack_nack_vote)) == -1) { + return (-1); + } + } + + if (dynar_str_catf(outbuf, "\n") == -1) { + return (-1); + } + } + + return (0); +} + +int +qnetd_ipc_cmd_list(struct qnetd_instance *instance, struct dynar *outbuf, int verbose, + const char *cluster_name) +{ + struct qnetd_cluster *cluster; + struct qnetd_client *client; + size_t cluster_no, client_no; + + cluster_no = 0; + TAILQ_FOREACH(cluster, &instance->clusters, entries) { + if (cluster_name != NULL && strcmp(cluster_name, "") != 0 && + strcmp(cluster_name, cluster->cluster_name) != 0) { + continue; + } + + if (dynar_str_catf(outbuf, "Cluster \"%s\":\n", cluster->cluster_name) == -1) { + return (-1); + } + + client_no = 0; + + TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) { + if (client_no == 0) { + if (dynar_str_catf(outbuf, " Algorithm:\t\t%s\n", + tlv_decision_algorithm_type_to_str( + client->decision_algorithm)) == -1) { + return (-1); + } + + if (!qnetd_ipc_cmd_add_tie_breaker(client, outbuf)) { + return (-1); + } + } + + if (qnetd_ipc_cmd_list_add_client_info(client, outbuf, verbose, + client_no) == -1) { + return (-1); + } + + client_no++; + } + + cluster_no++; + } + + return (0); +} diff --git a/qdevices/qnetd-ipc-cmd.h b/qdevices/qnetd-ipc-cmd.h new file mode 100644 index 0000000..dbbf705 --- /dev/null +++ b/qdevices/qnetd-ipc-cmd.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_IPC_CMD_H_ +#define _QNETD_IPC_CMD_H_ + +#include "dynar.h" +#include "qnetd-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int qnetd_ipc_cmd_status(struct qnetd_instance *instance, + struct dynar *outbuf, int verbose); + +extern int qnetd_ipc_cmd_list(struct qnetd_instance *instance, + struct dynar *outbuf, int verbose, const char *cluster_name); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_IPC_CMD_H_ */ diff --git a/qdevices/qnetd-ipc.c b/qdevices/qnetd-ipc.c new file mode 100644 index 0000000..0d40275 --- /dev/null +++ b/qdevices/qnetd-ipc.c @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qnet-config.h" +#include "qnetd-ipc.h" +#include "qnetd-ipc-cmd.h" +#include "qnetd-log.h" +#include "unix-socket-ipc.h" +#include "dynar-simple-lex.h" +#include "dynar-str.h" + +/* + * Needed for creating nspr handle from unix fd + */ +#include + +int +qnetd_ipc_init(struct qnetd_instance *instance) +{ + + if (unix_socket_ipc_init(&instance->local_ipc, + instance->advanced_settings->local_socket_file, + instance->advanced_settings->local_socket_backlog, + instance->advanced_settings->ipc_max_clients, + instance->advanced_settings->ipc_max_receive_size, + instance->advanced_settings->ipc_max_send_size) != 0) { + qnetd_log_err(LOG_ERR, "Can't create unix socket"); + + return (-1); + } + + if ((instance->ipc_socket_poll_fd = PR_CreateSocketPollFd(instance->local_ipc.socket)) == NULL) { + qnetd_log_nss(LOG_CRIT, "Can't create NSPR IPC socket poll fd"); + + return (-1); + } + + return (0); +} + +int +qnetd_ipc_close(struct qnetd_instance *instance) +{ + int res; + + res = unix_socket_ipc_close(&instance->local_ipc); + if (res != 0) { + qnetd_log_err(LOG_WARNING, "Can't close local IPC"); + } + + return (res); +} + +int +qnetd_ipc_is_closed(struct qnetd_instance *instance) +{ + + return (unix_socket_ipc_is_closed(&instance->local_ipc)); +} + +int +qnetd_ipc_destroy(struct qnetd_instance *instance) +{ + int res; + struct unix_socket_client *client; + const struct unix_socket_client_list *ipc_client_list; + + ipc_client_list = &instance->local_ipc.clients; + + TAILQ_FOREACH(client, ipc_client_list, entries) { + free(client->user_data); + } + + if (PR_DestroySocketPollFd(instance->ipc_socket_poll_fd) != PR_SUCCESS) { + qnetd_log_nss(LOG_WARNING, "Unable to destroy IPC poll socket fd"); + } + + res = unix_socket_ipc_destroy(&instance->local_ipc); + if (res != 0) { + qnetd_log_err(LOG_WARNING, "Can't destroy local IPC"); + } + + return (res); +} + +int +qnetd_ipc_accept(struct qnetd_instance *instance, struct unix_socket_client **res_client) +{ + int res; + int accept_res; + PRFileDesc *prfd; + + accept_res = unix_socket_ipc_accept(&instance->local_ipc, res_client); + + switch (accept_res) { + case -1: + qnetd_log_err(LOG_ERR, "Can't accept local IPC connection"); + res = -1; + goto return_res; + break; + case -2: + qnetd_log(LOG_ERR, "Maximum IPC clients reached. Not accepting connection"); + res = -1; + goto return_res; + break; + case -3: + qnetd_log(LOG_ERR, "Can't add client to list"); + res = -1; + goto return_res; + break; + default: + unix_socket_client_read_line(*res_client, 1); + res = 0; + break; + } + + (*res_client)->user_data = malloc(sizeof(struct qnetd_ipc_user_data)); + if ((*res_client)->user_data == NULL) { + qnetd_log(LOG_ERR, "Can't alloc IPC client user data"); + res = -1; + qnetd_ipc_client_disconnect(instance, *res_client); + + goto return_res; + } + memset((*res_client)->user_data, 0, sizeof(struct qnetd_ipc_user_data)); + + prfd = PR_CreateSocketPollFd((*res_client)->socket); + if (prfd == NULL) { + qnetd_log_nss(LOG_CRIT, "Can't create NSPR poll fd for IPC client. Disconnecting client"); + qnetd_ipc_client_disconnect(instance, *res_client); + res = -1; + + goto return_res; + } + + ((struct qnetd_ipc_user_data *)(*res_client)->user_data)->nspr_poll_fd = prfd; + +return_res: + return (res); +} + +void +qnetd_ipc_client_disconnect(struct qnetd_instance *instance, struct unix_socket_client *client) +{ + + if (PR_DestroySocketPollFd( + ((struct qnetd_ipc_user_data *)(client)->user_data)->nspr_poll_fd) != PR_SUCCESS) { + qnetd_log_nss(LOG_WARNING, "Unable to destroy client IPC poll socket fd"); + } + + free(client->user_data); + unix_socket_ipc_client_disconnect(&instance->local_ipc, client); +} + +int +qnetd_ipc_send_error(struct qnetd_instance *instance, struct unix_socket_client *client, + const char *error_fmt, ...) +{ + va_list ap; + int res; + + va_start(ap, error_fmt); + res = ((dynar_str_cpy(&client->send_buffer, "Error\n") == 0) && + (dynar_str_vcatf(&client->send_buffer, error_fmt, ap) > 0) && + (dynar_str_cat(&client->send_buffer, "\n") == 0)); + + va_end(ap); + + if (res) { + unix_socket_client_write_buffer(client, 1); + } else { + qnetd_log(LOG_ERR, "Can't send ipc error to client (buffer too small)"); + } + + return (res ? 0 : -1); +} + +int +qnetd_ipc_send_buffer(struct qnetd_instance *instance, struct unix_socket_client *client) +{ + + if (dynar_str_prepend(&client->send_buffer, "OK\n") != 0) { + qnetd_log(LOG_ERR, "Can't send ipc message to client (buffer too small)"); + + if (qnetd_ipc_send_error(instance, client, "Internal IPC buffer too small") != 0) { + return (-1); + } + + return (0); + } + + unix_socket_client_write_buffer(client, 1); + + return (0); +} + +static void +qnetd_ipc_parse_line(struct qnetd_instance *instance, struct unix_socket_client *client) +{ + struct dynar_simple_lex lex; + struct dynar *token; + char *str; + struct qnetd_ipc_user_data *ipc_user_data; + int verbose; + char *cluster_name; + + ipc_user_data = (struct qnetd_ipc_user_data *)client->user_data; + + dynar_simple_lex_init(&lex, &client->receive_buffer, DYNAR_SIMPLE_LEX_TYPE_QUOTE); + token = dynar_simple_lex_token_next(&lex); + + verbose = 0; + cluster_name = NULL; + + if (token == NULL) { + goto exit_err_low_mem; + } + + str = dynar_data(token); + if (strcasecmp(str, "") == 0) { + qnetd_log(LOG_DEBUG, "IPC client doesn't send command"); + if (qnetd_ipc_send_error(instance, client, "No command specified") != 0) { + client->schedule_disconnect = 1; + } + } else if (strcasecmp(str, "shutdown") == 0) { + qnetd_log(LOG_DEBUG, "IPC client requested shutdown"); + + ipc_user_data->shutdown_requested = 1; + + if (qnetd_ipc_send_buffer(instance, client) != 0) { + client->schedule_disconnect = 1; + } + } else if (strcasecmp(str, "status") == 0) { + token = dynar_simple_lex_token_next(&lex); + if (token == NULL) { + goto exit_err_low_mem; + } + + str = dynar_data(token); + + if (token != NULL && strcmp(str, "") != 0) { + if (strcasecmp(str, "verbose") == 0) { + verbose = 1; + } + } + + if (qnetd_ipc_cmd_status(instance, &client->send_buffer, verbose) != 0) { + if (qnetd_ipc_send_error(instance, client, "Can't get QNetd status") != 0) { + client->schedule_disconnect = 1; + } + } else { + if (qnetd_ipc_send_buffer(instance, client) != 0) { + client->schedule_disconnect = 1; + } + } + } else if (strcasecmp(str, "list") == 0) { + while (((token = dynar_simple_lex_token_next(&lex)) != NULL) && + (str = dynar_data(token), strcmp(str, "") != 0)) { + if (strcasecmp(str, "verbose") == 0) { + verbose = 1; + } else if (strcasecmp(str, "cluster") == 0) { + token = dynar_simple_lex_token_next(&lex); + if (token == NULL) { + goto exit_err_low_mem; + } + + free(cluster_name); cluster_name = NULL; + if ((cluster_name = strdup(dynar_data(token))) == NULL) { + goto exit_err_low_mem; + } + } else { + break; + } + } + + if (qnetd_ipc_cmd_list(instance, &client->send_buffer, verbose, cluster_name) != 0) { + if (qnetd_ipc_send_error(instance, client, "Can't get QNetd cluster list") != 0) { + client->schedule_disconnect = 1; + } + } else { + if (qnetd_ipc_send_buffer(instance, client) != 0) { + client->schedule_disconnect = 1; + } + } + + free(cluster_name); cluster_name = NULL; + } else { + qnetd_log(LOG_DEBUG, "IPC client sent unknown command"); + if (qnetd_ipc_send_error(instance, client, "Unknown command '%s'", str) != 0) { + client->schedule_disconnect = 1; + } + } + + dynar_simple_lex_destroy(&lex); + + return ; + +exit_err_low_mem: + free(cluster_name); cluster_name = NULL; + + qnetd_log(LOG_ERR, "Can't alloc memory for simple lex"); + + if (qnetd_ipc_send_error(instance, client, "Command too long") != 0) { + client->schedule_disconnect = 1; + } +} + +void +qnetd_ipc_io_read(struct qnetd_instance *instance, struct unix_socket_client *client) +{ + int res; + + res = unix_socket_client_io_read(client); + + switch (res) { + case 0: + /* + * Partial read + */ + break; + case -1: + qnetd_log(LOG_DEBUG, "IPC client closed connection"); + client->schedule_disconnect = 1; + break; + case -2: + qnetd_log(LOG_ERR, "Can't store message from IPC client. Disconnecting client."); + client->schedule_disconnect = 1; + break; + case -3: + qnetd_log_err(LOG_ERR, "Can't receive message from IPC client. Disconnecting client."); + client->schedule_disconnect = 1; + break; + case 1: + /* + * Full message received + */ + unix_socket_client_read_line(client, 0); + + qnetd_ipc_parse_line(instance, client); + break; + } +} + +void +qnetd_ipc_io_write(struct qnetd_instance *instance, struct unix_socket_client *client) +{ + int res; + struct qnetd_ipc_user_data *ipc_user_data; + + ipc_user_data = (struct qnetd_ipc_user_data *)client->user_data; + + res = unix_socket_client_io_write(client); + + switch (res) { + case 0: + /* + * Partial send + */ + break; + case -1: + qnetd_log(LOG_DEBUG, "IPC client closed connection"); + client->schedule_disconnect = 1; + break; + case -2: + qnetd_log_err(LOG_ERR, "Can't send message to IPC client. Disconnecting client"); + client->schedule_disconnect = 1; + break; + case 1: + /* + * Full message sent + */ + unix_socket_client_write_buffer(client, 0); + client->schedule_disconnect = 1; + + if (ipc_user_data->shutdown_requested) { + qnetd_ipc_close(instance); + } + + break; + } +} diff --git a/qdevices/qnetd-ipc.h b/qdevices/qnetd-ipc.h new file mode 100644 index 0000000..b8fbdd8 --- /dev/null +++ b/qdevices/qnetd-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 _QNETD_IPC_H_ +#define _QNETD_IPC_H_ + +#include "qnetd-instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct qnetd_ipc_user_data { + int shutdown_requested; + PRFileDesc *nspr_poll_fd; +}; + +extern int qnetd_ipc_init(struct qnetd_instance *instance); + +extern int qnetd_ipc_close(struct qnetd_instance *instance); + +extern int qnetd_ipc_is_closed(struct qnetd_instance *instance); + +extern int qnetd_ipc_destroy(struct qnetd_instance *instance); + +extern int qnetd_ipc_accept(struct qnetd_instance *instance, + struct unix_socket_client **res_client); + +extern void qnetd_ipc_client_disconnect(struct qnetd_instance *instance, + struct unix_socket_client *client); + +extern void qnetd_ipc_io_read(struct qnetd_instance *instance, + struct unix_socket_client *client); + +extern void qnetd_ipc_io_write(struct qnetd_instance *instance, + struct unix_socket_client *client); + +extern int qnetd_ipc_send_error(struct qnetd_instance *instance, + struct unix_socket_client *client, const char *error_fmt, ...) + __attribute__((__format__(__printf__, 3, 4))); + +extern int qnetd_ipc_send_buffer(struct qnetd_instance *instance, + struct unix_socket_client *client); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_IPC_H_ */ diff --git a/qdevices/qnetd-log-debug.c b/qdevices/qnetd-log-debug.c new file mode 100644 index 0000000..64b0bb5 --- /dev/null +++ b/qdevices/qnetd-log-debug.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qnetd-log.h" +#include "qnetd-log-debug.h" +#include "utils.h" + +void +qnetd_log_debug_dump_cluster(struct qnetd_cluster *cluster) +{ + struct qnetd_client *client; + + qnetd_log(LOG_DEBUG, " cluster dump:"); + TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) { + qnetd_log(LOG_DEBUG, " client = %s, node_id = "UTILS_PRI_NODE_ID, + client->addr_str, client->node_id); + } +} + +void +qnetd_log_debug_new_client_connected(struct qnetd_client *client) +{ + + qnetd_log(LOG_DEBUG, "New client connected"); + qnetd_log(LOG_DEBUG, " cluster name = %s", client->cluster_name); + qnetd_log(LOG_DEBUG, " tls started = %u", client->tls_started); + qnetd_log(LOG_DEBUG, " tls peer certificate verified = %u", + client->tls_peer_certificate_verified); + qnetd_log(LOG_DEBUG, " node_id = "UTILS_PRI_NODE_ID, client->node_id); + qnetd_log(LOG_DEBUG, " pointer = %p", client); + qnetd_log(LOG_DEBUG, " addr_str = %s", client->addr_str); + qnetd_log(LOG_DEBUG, " ring id = (" UTILS_PRI_RING_ID ")", client->last_ring_id.node_id, + client->last_ring_id.seq); + + qnetd_log_debug_dump_cluster(client->cluster); +} + +void +qnetd_log_debug_dump_node_list(struct qnetd_client *client, const struct node_list *nodes) +{ + struct node_list_entry *node_info; + + qnetd_log(LOG_DEBUG, " node list:"); + TAILQ_FOREACH(node_info, nodes, entries) { + qnetd_log(LOG_DEBUG, " node_id = "UTILS_PRI_NODE_ID", " + "data_center_id = "UTILS_PRI_DATACENTER_ID", " + "node_state = %s", node_info->node_id, node_info->data_center_id, + tlv_node_state_to_str(node_info->node_state)); + } +} + +void +qnetd_log_debug_config_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, int config_version_set, uint64_t config_version, + const struct node_list *nodes, int initial) +{ + + qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "sent %s node list.", client->addr_str, client->cluster_name, client->node_id, + (initial ? "initial" : "changed")); + + qnetd_log(LOG_DEBUG, " msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num); + + if (config_version_set) { + qnetd_log(LOG_DEBUG, " config version = " UTILS_PRI_CONFIG_VERSION, config_version); + } + + qnetd_log_debug_dump_node_list(client, nodes); +} + +void +qnetd_log_debug_membership_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, const struct tlv_ring_id *ring_id, + enum tlv_heuristics heuristics, const struct node_list *nodes) +{ + qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "sent membership node list.", client->addr_str, client->cluster_name, client->node_id); + + qnetd_log(LOG_DEBUG, " msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num); + + qnetd_log(LOG_DEBUG, " ring id = (" UTILS_PRI_RING_ID ")", ring_id->node_id, ring_id->seq); + + qnetd_log(LOG_DEBUG, " heuristics = %s ", tlv_heuristics_to_str(heuristics)); + + qnetd_log_debug_dump_node_list(client, nodes); +} + +void +qnetd_log_debug_quorum_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes) +{ + + qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "sent quorum node list.", client->addr_str, client->cluster_name, client->node_id); + + qnetd_log(LOG_DEBUG, " msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num); + qnetd_log(LOG_DEBUG, " quorate = %u", quorate); + + qnetd_log_debug_dump_node_list(client, nodes); +} + +void +qnetd_log_debug_client_disconnect(struct qnetd_client *client, int server_going_down) +{ + + qnetd_log(LOG_DEBUG, "Client %s (init_received %u, cluster %s, node_id " + UTILS_PRI_NODE_ID") disconnect%s", client->addr_str, client->init_received, + client->cluster_name, client->node_id, + (server_going_down ? " (server is going down)" : "")); +} + +void +qnetd_log_debug_ask_for_vote_received(struct qnetd_client *client, uint32_t msg_seq_num) +{ + + qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "asked for a vote", client->addr_str, client->cluster_name, client->node_id); + qnetd_log(LOG_DEBUG, " msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num); +} + +void +qnetd_log_debug_vote_info_reply_received(struct qnetd_client *client, uint32_t msg_seq_num) +{ + + qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "replied back to vote info message", client->addr_str, client->cluster_name, + client->node_id); + qnetd_log(LOG_DEBUG, " msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num); +} + +void +qnetd_log_debug_send_vote_info(struct qnetd_client *client, uint32_t msg_seq_num, enum tlv_vote vote) +{ + + qnetd_log(LOG_DEBUG, "Sending vote info to client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") ", + client->addr_str, client->cluster_name, client->node_id); + qnetd_log(LOG_DEBUG, " msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num); + qnetd_log(LOG_DEBUG, " vote = %s", tlv_vote_to_str(vote)); +} + +void +qnetd_log_debug_heuristics_change_received(struct qnetd_client *client, uint32_t msg_seq_num, + enum tlv_heuristics heuristics) +{ + + qnetd_log(LOG_DEBUG, "Client %s (cluster %s, node_id "UTILS_PRI_NODE_ID") " + "sent heuristics change", client->addr_str, client->cluster_name, client->node_id); + qnetd_log(LOG_DEBUG, " msg seq num = "UTILS_PRI_MSG_SEQ, msg_seq_num); + qnetd_log(LOG_DEBUG, " heuristics = %s", tlv_heuristics_to_str(heuristics)); +} diff --git a/qdevices/qnetd-log-debug.h b/qdevices/qnetd-log-debug.h new file mode 100644 index 0000000..a6d22e3 --- /dev/null +++ b/qdevices/qnetd-log-debug.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 _QNETD_LOG_DEBUG_H_ +#define _QNETD_LOG_DEBUG_H_ + +#include "qnetd-client.h" +#include "qnetd-cluster-list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern void qnetd_log_debug_dump_cluster(struct qnetd_cluster *cluster); + +extern void qnetd_log_debug_new_client_connected(struct qnetd_client *client); + +extern void qnetd_log_debug_dump_node_list(struct qnetd_client *client, + const struct node_list *nodes); + +extern void qnetd_log_debug_config_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, int config_version_set, uint64_t config_version, + const struct node_list *nodes, int initial); + +extern void qnetd_log_debug_membership_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, const struct tlv_ring_id *ring_id, + enum tlv_heuristics heuristics, const struct node_list *nodes); + +extern void qnetd_log_debug_quorum_node_list_received(struct qnetd_client *client, + uint32_t msg_seq_num, enum tlv_quorate quorate, const struct node_list *nodes); + +extern void qnetd_log_debug_client_disconnect(struct qnetd_client *client, + int server_going_down); + +extern void qnetd_log_debug_ask_for_vote_received(struct qnetd_client *client, + uint32_t msg_seq_num); + +extern void qnetd_log_debug_vote_info_reply_received(struct qnetd_client *client, + uint32_t msg_seq_num); + +extern void qnetd_log_debug_send_vote_info(struct qnetd_client *client, + uint32_t msg_seq_num, enum tlv_vote vote); + +extern void qnetd_log_debug_heuristics_change_received(struct qnetd_client *client, + uint32_t msg_seq_num, enum tlv_heuristics heuristics); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_LOG_DEBUG_H_ */ diff --git a/qdevices/qnetd-log.c b/qdevices/qnetd-log.c new file mode 100644 index 0000000..6f225ae --- /dev/null +++ b/qdevices/qnetd-log.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "qnet-config.h" +#include "qnetd-log.h" + +static int qnetd_log_config_target = 0; +static int qnetd_log_config_debug = 0; +static int qnetd_log_config_priority_bump = 0; + +static const char qnetd_log_month_str[][4] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +struct qnetd_log_syslog_prio_to_str_item { + int priority; + const char *priority_str; +}; + +static struct qnetd_log_syslog_prio_to_str_item qnetd_syslog_prio_to_str_array[] = { + {LOG_EMERG, "emerg"}, + {LOG_ALERT, "alert"}, + {LOG_CRIT, "crit"}, + {LOG_ERR, "error"}, + {LOG_WARNING, "warning"}, + {LOG_NOTICE, "notice"}, + {LOG_INFO, "info"}, + {LOG_DEBUG, "debug"}, + {-1, NULL}}; + +void +qnetd_log_init(int target) +{ + + qnetd_log_config_target = target; + + if (qnetd_log_config_target & QNETD_LOG_TARGET_SYSLOG) { + openlog(QNETD_PROGRAM_NAME, LOG_PID, LOG_DAEMON); + } +} + +static const char * +qnetd_log_syslog_prio_to_str(int priority) +{ + + if (priority >= LOG_EMERG && priority <= LOG_DEBUG) { + return (qnetd_syslog_prio_to_str_array[priority].priority_str); + } else { + return ("none"); + } +} + +void +qnetd_log_vprintf(int priority, const char *format, va_list ap) +{ + time_t current_time; + struct tm tm_res; + int final_priority; + va_list ap_copy; + + if (priority != LOG_DEBUG || (qnetd_log_config_debug)) { + if (qnetd_log_config_target & QNETD_LOG_TARGET_STDERR) { + current_time = time(NULL); + localtime_r(¤t_time, &tm_res); + + fprintf(stderr, "%s %02d %02d:%02d:%02d ", + qnetd_log_month_str[tm_res.tm_mon], tm_res.tm_mday, tm_res.tm_hour, + tm_res.tm_min, tm_res.tm_sec); + + fprintf(stderr, "%-7s ", qnetd_log_syslog_prio_to_str(priority)); + + va_copy(ap_copy, ap); + vfprintf(stderr, format, ap_copy); + va_end(ap_copy); + fprintf(stderr, "\n"); + } + + if (qnetd_log_config_target & QNETD_LOG_TARGET_SYSLOG) { + final_priority = priority; + if (qnetd_log_config_priority_bump && priority > LOG_INFO) { + final_priority = LOG_INFO; + } + + va_copy(ap_copy, ap); + vsyslog(final_priority, format, ap); + va_end(ap_copy); + } + } +} + +void +qnetd_log_printf(int priority, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + qnetd_log_vprintf(priority, format, ap); + + va_end(ap); +} + +void +qnetd_log_close(void) +{ + + if (qnetd_log_config_target & QNETD_LOG_TARGET_SYSLOG) { + closelog(); + } +} + +void +qnetd_log_set_debug(int enabled) +{ + + qnetd_log_config_debug = enabled; +} + +void +qnetd_log_set_priority_bump(int enabled) +{ + + qnetd_log_config_priority_bump = enabled; +} + +void +qnetd_log_msg_decode_error(int ret) +{ + + switch (ret) { + case -1: + qnetd_log(LOG_WARNING, "Received message with option with invalid length"); + break; + case -2: + qnetd_log(LOG_CRIT, "Can't allocate memory"); + break; + case -3: + qnetd_log(LOG_WARNING, "Received inconsistent msg (tlv len > msg size)"); + break; + case -4: + qnetd_log(LOG_WARNING, "Received message with option with invalid value"); + break; + default: + qnetd_log(LOG_ERR, "Unknown error occurred when decoding message"); + break; + } +} diff --git a/qdevices/qnetd-log.h b/qdevices/qnetd-log.h new file mode 100644 index 0000000..6d94775 --- /dev/null +++ b/qdevices/qnetd-log.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QNETD_LOG_H_ +#define _QNETD_LOG_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define QNETD_LOG_TARGET_STDERR 1 +#define QNETD_LOG_TARGET_SYSLOG 2 + +#define qnetd_log(...) qnetd_log_printf(__VA_ARGS__) +#define qnetd_log_nss(priority, str) qnetd_log_printf(priority, "%s (%d): %s", \ + str, PR_GetError(), PR_ErrorToString(PR_GetError(), PR_LANGUAGE_I_DEFAULT)); + +#define qnetd_log_err(priority, str) qnetd_log_printf(priority, "%s (%d): %s", \ + str, errno, strerror(errno)) + +extern void qnetd_log_init(int target); + +extern void qnetd_log_printf(int priority, const char *format, ...) + __attribute__((__format__(__printf__, 2, 3))); + +extern void qnetd_log_vprintf(int priority, const char *format, va_list ap) + __attribute__((__format__(__printf__, 2, 0))); + +extern void qnetd_log_close(void); + +extern void qnetd_log_set_debug(int enabled); + +extern void qnetd_log_set_priority_bump(int enabled); + +extern void qnetd_log_msg_decode_error(int ret); + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_LOG_H_ */ diff --git a/qdevices/qnetd-poll-array-user-data.h b/qdevices/qnetd-poll-array-user-data.h new file mode 100644 index 0000000..336e41f --- /dev/null +++ b/qdevices/qnetd-poll-array-user-data.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 _QNETD_POLL_ARRAY_USER_DATA_H_ +#define _QNETD_POLL_ARRAY_USER_DATA_H_ + +#include "qnetd-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum qnetd_poll_array_user_data_type { + QNETD_POLL_ARRAY_USER_DATA_TYPE_SOCKET, + QNETD_POLL_ARRAY_USER_DATA_TYPE_CLIENT, + QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_SOCKET, + QNETD_POLL_ARRAY_USER_DATA_TYPE_IPC_CLIENT, +}; + +struct qnetd_poll_array_user_data { + enum qnetd_poll_array_user_data_type type; + struct qnetd_client *client; + struct unix_socket_client *ipc_client; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _QNETD_POLL_ARRAY_USER_DATA_H_ */ diff --git a/qdevices/send-buffer-list.c b/qdevices/send-buffer-list.c new file mode 100644 index 0000000..4ff3261 --- /dev/null +++ b/qdevices/send-buffer-list.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "send-buffer-list.h" + +void +send_buffer_list_init(struct send_buffer_list *sblist, size_t max_list_entries, + size_t max_buffer_size) +{ + + memset(sblist, 0, sizeof(*sblist)); + + sblist->max_list_entries = max_list_entries; + sblist->allocated_list_entries = 0; + sblist->max_buffer_size = max_buffer_size; + TAILQ_INIT(&sblist->list); + TAILQ_INIT(&sblist->free_list); +} + +struct send_buffer_list_entry * +send_buffer_list_get_new(struct send_buffer_list *sblist) +{ + struct send_buffer_list_entry *entry; + + if (!TAILQ_EMPTY(&sblist->free_list)) { + /* + * Use free list entry + */ + entry = TAILQ_FIRST(&sblist->free_list); + TAILQ_REMOVE(&sblist->free_list, entry, entries); + + dynar_clean(&entry->buffer); + dynar_set_max_size(&entry->buffer, sblist->max_buffer_size); + } else { + if (sblist->allocated_list_entries + 1 > sblist->max_list_entries) { + return (NULL); + } + + sblist->allocated_list_entries++; + + /* + * Alloc new entry + */ + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + return (NULL); + } + + dynar_init(&entry->buffer, sblist->max_buffer_size); + } + + entry->msg_already_sent_bytes = 0; + + return (entry); +} + +void +send_buffer_list_put(struct send_buffer_list *sblist, struct send_buffer_list_entry *sblist_entry) +{ + + TAILQ_INSERT_TAIL(&sblist->list, sblist_entry, entries); +} + +void +send_buffer_list_discard_new(struct send_buffer_list *sblist, struct send_buffer_list_entry *sblist_entry) +{ + + TAILQ_INSERT_HEAD(&sblist->free_list, sblist_entry, entries); +} + +struct send_buffer_list_entry * +send_buffer_list_get_active(const struct send_buffer_list *sblist) +{ + struct send_buffer_list_entry *entry; + + entry = TAILQ_FIRST(&sblist->list); + + return (entry); +} + +void +send_buffer_list_delete(struct send_buffer_list *sblist, + struct send_buffer_list_entry *sblist_entry) +{ + + /* + * Move item to free list + */ + TAILQ_REMOVE(&sblist->list, sblist_entry, entries); + TAILQ_INSERT_HEAD(&sblist->free_list, sblist_entry, entries); +} + +int +send_buffer_list_empty(const struct send_buffer_list *sblist) +{ + + return (TAILQ_EMPTY(&sblist->list)); +} + +void +send_buffer_list_free(struct send_buffer_list *sblist) +{ + struct send_buffer_list_entry *entry; + struct send_buffer_list_entry *entry_next; + + entry = TAILQ_FIRST(&sblist->list); + + while (entry != NULL) { + entry_next = TAILQ_NEXT(entry, entries); + + dynar_destroy(&entry->buffer); + free(entry); + + entry = entry_next; + } + + entry = TAILQ_FIRST(&sblist->free_list); + + while (entry != NULL) { + entry_next = TAILQ_NEXT(entry, entries); + + dynar_destroy(&entry->buffer); + free(entry); + + entry = entry_next; + } + + sblist->allocated_list_entries = 0; + TAILQ_INIT(&sblist->list); + TAILQ_INIT(&sblist->free_list); +} + +void +send_buffer_list_set_max_buffer_size(struct send_buffer_list *sblist, size_t max_buffer_size) +{ + + sblist->max_buffer_size = max_buffer_size; +} + +void +send_buffer_list_set_max_list_entries(struct send_buffer_list *sblist, size_t max_list_entries) +{ + + sblist->max_list_entries = max_list_entries; +} diff --git a/qdevices/send-buffer-list.h b/qdevices/send-buffer-list.h new file mode 100644 index 0000000..f204b9d --- /dev/null +++ b/qdevices/send-buffer-list.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SEND_BUFFER_LIST_H_ +#define _SEND_BUFFER_LIST_H_ + +#include + +#include "dynar.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct send_buffer_list_entry { + struct dynar buffer; + size_t msg_already_sent_bytes; + + TAILQ_ENTRY(send_buffer_list_entry) entries; +}; + +struct send_buffer_list { + size_t max_list_entries; + size_t allocated_list_entries; + + size_t max_buffer_size; + + TAILQ_HEAD(, send_buffer_list_entry) list; + TAILQ_HEAD(, send_buffer_list_entry) free_list; +}; + +extern void send_buffer_list_init(struct send_buffer_list *sblist, + size_t max_list_entries, size_t max_buffer_size); + +extern struct send_buffer_list_entry *send_buffer_list_get_new(struct send_buffer_list *sblist); + +extern void send_buffer_list_put(struct send_buffer_list *sblist, + struct send_buffer_list_entry *sblist_entry); + +extern void send_buffer_list_discard_new( + struct send_buffer_list *sblist, struct send_buffer_list_entry *sblist_entry); + +extern struct send_buffer_list_entry *send_buffer_list_get_active( + const struct send_buffer_list *sblist); + +extern void send_buffer_list_delete(struct send_buffer_list *sblist, + struct send_buffer_list_entry *sblist_entry); + +extern int send_buffer_list_empty( + const struct send_buffer_list *sblist); + +extern void send_buffer_list_free(struct send_buffer_list *sblist); + +extern void send_buffer_list_set_max_buffer_size( + struct send_buffer_list *sblist, size_t max_buffer_size); + +extern void send_buffer_list_set_max_list_entries( + struct send_buffer_list *sblist, size_t max_list_entries); + +#ifdef __cplusplus +} +#endif + +#endif /* _SEND_BUFFER_LIST_H_ */ diff --git a/qdevices/test-dynar-getopt-lex.c b/qdevices/test-dynar-getopt-lex.c new file mode 100644 index 0000000..9c702da --- /dev/null +++ b/qdevices/test-dynar-getopt-lex.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "dynar.h" +#include "dynar-str.h" +#include "dynar-getopt-lex.h" + +int +main(void) +{ + struct dynar input_str; + struct dynar_getopt_lex lex; + + dynar_init(&input_str, 128); + + assert(dynar_str_catf(&input_str, "option=value") != -1); + dynar_getopt_lex_init(&lex, &input_str); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "option") == 0); + assert(strcmp(dynar_data(&lex.value), "value") == 0); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "") == 0); + assert(strcmp(dynar_data(&lex.value), "") == 0); + dynar_getopt_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "option1=value1,option2=value2") != -1); + dynar_getopt_lex_init(&lex, &input_str); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "option1") == 0); + assert(strcmp(dynar_data(&lex.value), "value1") == 0); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "option2") == 0); + assert(strcmp(dynar_data(&lex.value), "value2") == 0); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "") == 0); + assert(strcmp(dynar_data(&lex.value), "") == 0); + dynar_getopt_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "option1=value1,option2=value2,option3=value3") != -1); + dynar_getopt_lex_init(&lex, &input_str); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "option1") == 0); + assert(strcmp(dynar_data(&lex.value), "value1") == 0); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "option2") == 0); + assert(strcmp(dynar_data(&lex.value), "value2") == 0); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "option3") == 0); + assert(strcmp(dynar_data(&lex.value), "value3") == 0); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "") == 0); + assert(strcmp(dynar_data(&lex.value), "") == 0); + dynar_getopt_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "option1,option2=value2") != -1); + dynar_getopt_lex_init(&lex, &input_str); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "option1") == 0); + assert(strcmp(dynar_data(&lex.value), "") == 0); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "option2") == 0); + assert(strcmp(dynar_data(&lex.value), "value2") == 0); + assert(dynar_getopt_lex_token_next(&lex) == 0); + assert(strcmp(dynar_data(&lex.option), "") == 0); + assert(strcmp(dynar_data(&lex.value), "") == 0); + dynar_getopt_lex_destroy(&lex); + + dynar_destroy(&input_str); + + return (0); +} diff --git a/qdevices/test-dynar-simple-lex.c b/qdevices/test-dynar-simple-lex.c new file mode 100644 index 0000000..bbccf79 --- /dev/null +++ b/qdevices/test-dynar-simple-lex.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "dynar.h" +#include "dynar-str.h" +#include "dynar-simple-lex.h" + +int +main(void) +{ + struct dynar input_str; + struct dynar_simple_lex lex; + struct dynar *output_str_ptr; + struct dynar output_str; + const char *cstr; + + dynar_init(&input_str, 128); + + assert(dynar_str_catf(&input_str, "token1 token2") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "token1") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "token2") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, " token1 token2 ") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "token1") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "token2") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, " token1 token2 \ntoken3") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "token1") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "token2") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "\\ab\\cd e\\fg\\ h i\\") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "\\ab\\cd") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "e\\fg\\") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "h") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "i\\") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, " a b\rc") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_PLAIN); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "a") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "b") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "\\ab\\\\cd e\\fg\\ h i\\") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_BACKSLASH); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "ab\\cd") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "efg h") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "i") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "ab\\\\cd e\\fg\\ h ij\\\na") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_BACKSLASH); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "ab\\cd") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "efg h") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "ij") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, " a b\\\rc") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_BACKSLASH); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "a") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "b") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "abc def \"ghi\" jkl \"m n o\"") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "abc") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "def") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "ghi") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "jkl") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "m n o") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "a\\bc \"d\\e \\\"f\\\\ \\\" \"g hij") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "abc") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "d\\e \"f\\ \" g") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "hij") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "abc \"d e \r\n") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "abc") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "d e ") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + assert(dynar_str_cpy(&input_str, "") == 0); + assert(dynar_str_catf(&input_str, "abc \"d e \\\"\\\r\n") != -1); + dynar_simple_lex_init(&lex, &input_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "abc") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "d e \"") == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + dynar_init(&output_str, 128); + assert(dynar_str_quote_cpy(&output_str, "abcd") == 0); + assert(dynar_size(&output_str) == 6); + assert(memcmp(dynar_data(&output_str), "\"abcd\"", dynar_size(&output_str)) == 0); + assert(dynar_str_cat(&output_str, " ") == 0); + assert(dynar_str_quote_cat(&output_str, "abcd") == 0); + assert(dynar_size(&output_str) == 13); + assert(memcmp(dynar_data(&output_str), "\"abcd\" \"abcd\"", dynar_size(&output_str)) == 0); + + assert(dynar_str_quote_cpy(&output_str, "ab\\cd") == 0); + assert(dynar_size(&output_str) == 8); + assert(memcmp(dynar_data(&output_str), "\"ab\\\\cd\"", dynar_size(&output_str)) == 0); + + assert(dynar_str_quote_cpy(&output_str, "ab\\\\cd") == 0); + assert(dynar_size(&output_str) == 10); + assert(memcmp(dynar_data(&output_str), "\"ab\\\\\\\\cd\"", dynar_size(&output_str)) == 0); + + assert(dynar_str_quote_cpy(&output_str, "ab cd \\\"e") == 0); + assert(dynar_size(&output_str) == 13); + assert(memcmp(dynar_data(&output_str), "\"ab cd \\\\\\\"e\"", dynar_size(&output_str)) == 0); + + cstr = "ab cd \\ ef\\g h\"i"; + assert(dynar_str_quote_cpy(&output_str, cstr) == 0); + dynar_simple_lex_init(&lex, &output_str, DYNAR_SIMPLE_LEX_TYPE_QUOTE); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strlen(cstr) == dynar_size(output_str_ptr) - 1); + assert(memcmp(cstr, dynar_data(output_str_ptr), strlen(cstr)) == 0); + assert((output_str_ptr = dynar_simple_lex_token_next(&lex)) != NULL); + assert(strcmp(dynar_data(output_str_ptr), "") == 0); + dynar_simple_lex_destroy(&lex); + + dynar_destroy(&input_str); + dynar_destroy(&output_str); + + return (0); +} diff --git a/qdevices/test-dynar.c b/qdevices/test-dynar.c new file mode 100644 index 0000000..be11639 --- /dev/null +++ b/qdevices/test-dynar.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "dynar.h" +#include "dynar-str.h" + +int +main(void) +{ + struct dynar str; + + dynar_init(&str, 3); + assert(dynar_cat(&str, "a", 1) == 0); + assert(dynar_size(&str) == 1); + assert(dynar_cat(&str, "b", 1) == 0); + assert(dynar_size(&str) == 2); + assert(dynar_cat(&str, "c", 1) == 0); + assert(dynar_size(&str) == 3); + assert(dynar_cat(&str, "d", 1) != 0); + assert(memcmp(dynar_data(&str), "abc", 3) == 0); + + dynar_clean(&str); + assert(dynar_size(&str) == 0); + dynar_set_max_size(&str, 4); + assert(dynar_cat(&str, "a", 1) == 0); + assert(dynar_size(&str) == 1); + assert(memcmp(dynar_data(&str), "a", 1) == 0); + assert(dynar_cat(&str, "b", 1) == 0); + assert(dynar_size(&str) == 2); + assert(dynar_cat(&str, "c", 1) == 0); + assert(dynar_size(&str) == 3); + assert(dynar_cat(&str, "d", 1) == 0); + assert(dynar_size(&str) == 4); + assert(memcmp(dynar_data(&str), "abcd", 4) == 0); + + assert(dynar_str_cpy(&str, "e") == 0); + assert(dynar_size(&str) == 1); + assert(memcmp(dynar_data(&str), "e", 1) == 0); + + assert(dynar_str_cpy(&str, "fgh") == 0); + assert(dynar_size(&str) == 3); + assert(memcmp(dynar_data(&str), "fgh", 3) == 0); + + assert(dynar_str_cpy(&str, "fghi") == 0); + assert(dynar_size(&str) == 4); + assert(memcmp(dynar_data(&str), "fghi", 4) == 0); + + assert(dynar_str_cpy(&str, "fghij") != 0); + assert(dynar_size(&str) == 4); + assert(dynar_str_cat(&str, "a") != 0); + + assert(dynar_str_cpy(&str, "") == 0); + assert(dynar_size(&str) == 0); + + assert(dynar_str_cat(&str, "a") == 0); + assert(dynar_size(&str) == 1); + assert(memcmp(dynar_data(&str), "a", 1) == 0); + + assert(dynar_str_cat(&str, "b") == 0); + assert(dynar_size(&str) == 2); + assert(memcmp(dynar_data(&str), "ab", 2) == 0); + + assert(dynar_str_cat(&str, "cd") == 0); + assert(dynar_size(&str) == 4); + assert(memcmp(dynar_data(&str), "abcb", 1) == 0); + + assert(dynar_str_cpy(&str, "") == 0); + assert(dynar_str_catf(&str, "%s", "a") == 1); + assert(memcmp(dynar_data(&str), "a", 1) == 0); + assert(dynar_str_catf(&str, "%s", "ab") == 2); + assert(memcmp(dynar_data(&str), "aab", 3) == 0); + assert(dynar_str_cpy(&str, "") == 0); + assert(dynar_str_catf(&str, "%s", "abc") == 3); + assert(dynar_str_cpy(&str, "") == 0); + assert(dynar_str_catf(&str, "%s", "abcd") == -1); + assert(dynar_str_cpy(&str, "a") == 0); + assert(dynar_str_catf(&str, "%s", "") == 0); + assert(memcmp(dynar_data(&str), "a", 1) == 0); + + dynar_destroy(&str); + dynar_init(&str, 5); + assert(dynar_str_catf(&str, "%s", "abcd") == 4); + dynar_destroy(&str); + dynar_init(&str, 5); + assert(dynar_str_catf(&str, "%s", "") == 0); + assert(dynar_str_catf(&str, "%s", "abc") == 3); + assert(dynar_str_catf(&str, "%s", "d") == 1); + assert(memcmp(dynar_data(&str), "abcd", 4) == 0); + dynar_destroy(&str); + + dynar_init(&str, 10); + assert(dynar_str_cat(&str, "abcd") == 0); + assert(memcmp(dynar_data(&str), "abcd", 4) == 0); + assert(dynar_str_prepend(&str, "e") == 0); + assert(dynar_size(&str) == 5); + assert(memcmp(dynar_data(&str), "eabcd", 5) == 0); + assert(dynar_str_prepend(&str, "fgh") == 0); + assert(dynar_size(&str) == 8); + assert(memcmp(dynar_data(&str), "fgheabcd", 8) == 0); + assert(dynar_str_prepend(&str, "ijk") != 0); + assert(dynar_size(&str) == 8); + assert(dynar_str_prepend(&str, "ij") == 0); + assert(dynar_size(&str) == 10); + assert(memcmp(dynar_data(&str), "ijfgheabcd", 10) == 0); + dynar_destroy(&str); + + dynar_init(&str, 10); + assert(dynar_str_cat(&str, "abcd") == 0); + assert(memcmp(dynar_data(&str), "abcd", 4) == 0); + assert(dynar_str_prepend(&str, "ef") == 0); + assert(dynar_size(&str) == 6); + assert(memcmp(dynar_data(&str), "efabcd", 6) == 0); + assert(dynar_str_cat(&str, "ij") == 0); + assert(dynar_size(&str) == 8); + assert(memcmp(dynar_data(&str), "efabcdij", 8) == 0); + assert(dynar_str_prepend(&str, "k") == 0); + assert(dynar_size(&str) == 9); + assert(memcmp(dynar_data(&str), "kefabcdij", 9) == 0); + assert(dynar_str_cat(&str, "l") == 0); + assert(dynar_size(&str) == 10); + assert(memcmp(dynar_data(&str), "kefabcdijl", 10) == 0); + dynar_destroy(&str); + + return (0); +} diff --git a/qdevices/test-process-list.c b/qdevices/test-process-list.c new file mode 100644 index 0000000..57c667f --- /dev/null +++ b/qdevices/test-process-list.c @@ -0,0 +1,517 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "process-list.h" + +static int no_executed; +static int no_finished; + +static void +signal_handlers_register(void) +{ + struct sigaction act; + + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGCHLD, &act, NULL); + + act.sa_handler = SIG_IGN; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + sigaction(SIGPIPE, &act, NULL); +} + +static void +plist_notify(enum process_list_notify_reason reason, const struct process_list_entry *entry, + void *user_data) +{ + + assert(user_data == (void *)0x42); + + switch (reason) { + case PROCESS_LIST_NOTIFY_REASON_EXECUTED: + no_executed++; + break; + case PROCESS_LIST_NOTIFY_REASON_FINISHED: + no_finished++; + break; + } +} + +int +main(void) +{ + struct process_list plist; + struct process_list_entry *plist_entry; + int i; + int timeout; + int no_repeats; + + signal_handlers_register(); + + process_list_init(&plist, 10, 1, plist_notify, (void *)0x42); + plist_entry = process_list_add(&plist, "test name", "command"); + assert(plist_entry != NULL); + assert(strcmp(plist_entry->name, "test name") == 0); + assert(plist_entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED); + assert(plist_entry->exec_argc == 1); + assert(plist_entry->exec_argv[0] != NULL && strcmp(plist_entry->exec_argv[0], "command") == 0); + assert(plist_entry->exec_argv[1] == NULL); + + plist_entry = process_list_add(&plist, "test name", "/bin/ping -c \"host wit\\\"h space\" notaspace"); + assert(plist_entry != NULL); + assert(strcmp(plist_entry->name, "test name") == 0); + assert(plist_entry->state == PROCESS_LIST_ENTRY_STATE_INITIALIZED); + assert(plist_entry->exec_argc == 4); + assert(plist_entry->exec_argv[0] != NULL && strcmp(plist_entry->exec_argv[0], "/bin/ping") == 0); + assert(plist_entry->exec_argv[1] != NULL && strcmp(plist_entry->exec_argv[1], "-c") == 0); + assert(plist_entry->exec_argv[2] != NULL && strcmp(plist_entry->exec_argv[2], "host wit\"h space") == 0); + assert(plist_entry->exec_argv[3] != NULL && strcmp(plist_entry->exec_argv[3], "notaspace") == 0); + assert(plist_entry->exec_argv[4] == NULL); + + process_list_free(&plist); + + /* + * Test no process + */ + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 0); + assert(process_list_get_no_running(&plist) == 0); + + /* + * Wait to exit + */ + no_repeats = 10; + timeout = 1000 / no_repeats; + for (i = 0; i < no_repeats; i++) { + assert(process_list_waitpid(&plist) == 0); + if (process_list_get_no_running(&plist) > 0) { + poll(NULL, 0, timeout); + } + } + + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_no_running(&plist) == 0); + assert(no_finished == 0); + assert(process_list_get_summary_result(&plist) == 0); + assert(process_list_get_summary_result_short(&plist) == 0); + + process_list_free(&plist); + + /* + * Test two processes. /bin/true and /bin/false. Accumulated result should be fail + */ + plist_entry = process_list_add(&plist, "true", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "false", "/bin/false"); + assert(plist_entry != NULL); + + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 2); + assert(process_list_get_no_running(&plist) == 2); + + /* + * Wait to exit + */ + no_repeats = 10; + timeout = 1000 / no_repeats; + for (i = 0; i < no_repeats; i++) { + assert(process_list_waitpid(&plist) == 0); + if (process_list_get_no_running(&plist) > 0) { + poll(NULL, 0, timeout); + } + } + + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_no_running(&plist) == 0); + assert(no_finished == 2); + assert(process_list_get_summary_result(&plist) == 1); + assert(process_list_get_summary_result_short(&plist) == 1); + + process_list_free(&plist); + + /* + * Test two processes. /bin/true and one non-existing. Accumulated result should be fail + */ + plist_entry = process_list_add(&plist, "true", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "false", "/nonexistingdir/nonexistingfile"); + assert(plist_entry != NULL); + + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 2); + assert(process_list_get_no_running(&plist) == 2); + + /* + * Wait to exit + */ + no_repeats = 10; + timeout = 1000 / no_repeats; + for (i = 0; i < no_repeats; i++) { + assert(process_list_waitpid(&plist) == 0); + if (process_list_get_no_running(&plist) > 0) { + poll(NULL, 0, timeout); + } + } + + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_no_running(&plist) == 0); + assert(no_finished == 2); + assert(process_list_get_summary_result(&plist) == 1); + assert(process_list_get_summary_result_short(&plist) == 1); + + process_list_free(&plist); + + /* + * Test three processes /bin/true. Accumulated result should be success. + */ + plist_entry = process_list_add(&plist, "true", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true2", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true3", "/bin/true"); + assert(plist_entry != NULL); + + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 3); + assert(process_list_get_no_running(&plist) == 3); + + /* + * Wait to exit + */ + no_repeats = 10; + timeout = 1000 / no_repeats; + for (i = 0; i < no_repeats; i++) { + assert(process_list_waitpid(&plist) == 0); + if (process_list_get_no_running(&plist) > 0) { + poll(NULL, 0, timeout); + } + } + + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_no_running(&plist) == 0); + assert(no_finished == 3); + assert(process_list_get_summary_result(&plist) == 0); + assert(process_list_get_summary_result_short(&plist) == 0); + + process_list_free(&plist); + + /* + * Test two processes. /bin/true and cat. Waiting for maximum of 2 sec + */ + plist_entry = process_list_add(&plist, "true", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "cat", "/bin/cat /dev/zero"); + assert(plist_entry != NULL); + + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 2); + assert(process_list_get_no_running(&plist) == 2); + + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + + assert(process_list_get_no_running(&plist) == 1); + assert(no_finished == 1); + assert(process_list_get_summary_result(&plist) == -1); + assert(process_list_get_summary_result_short(&plist) == -1); + + process_list_move_active_entries_to_kill_list(&plist); + assert(process_list_process_kill_list(&plist) == 0); + poll(NULL, 0, 500); + assert(process_list_process_kill_list(&plist) == 0); + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + + assert(process_list_get_kill_list_items(&plist) == 0); + + assert(process_list_process_kill_list(&plist) == 0); + + process_list_free(&plist); + + /* + * Test two bash proceses. One ignores INT and second ignores INT and TERM. Waiting for maximum of 2 sec + */ + plist_entry = process_list_add(&plist, "ignoresig1", "bash -c \"trap 'echo trap' SIGINT;while true;do sleep 1;done\""); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "ignoresig2", "bash -c \"trap 'echo trap' SIGINT SIGTERM;while true;do sleep 1;done\""); + assert(plist_entry != NULL); + + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 2); + assert(process_list_get_no_running(&plist) == 2); + + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + + assert(process_list_get_no_running(&plist) == 2); + assert(no_finished == 0); + assert(process_list_get_summary_result(&plist) == -1); + assert(process_list_get_summary_result_short(&plist) == -1); + + process_list_move_active_entries_to_kill_list(&plist); + assert(process_list_process_kill_list(&plist) == 0); + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_kill_list_items(&plist) == 1); + + assert(process_list_process_kill_list(&plist) == 0); + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_kill_list_items(&plist) == 0); + + process_list_free(&plist); + + /* + * Test 3 processes. Test if entries are properly deallocated + */ + process_list_init(&plist, 3, 1, plist_notify, (void *)0x42); + plist_entry = process_list_add(&plist, "true", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true2", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true3", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true4", "/bin/true"); + assert(plist_entry == NULL); + + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 3); + assert(process_list_get_no_running(&plist) == 3); + + /* + * Wait to exit + */ + no_repeats = 10; + timeout = 1000 / no_repeats; + for (i = 0; i < no_repeats; i++) { + assert(process_list_waitpid(&plist) == 0); + if (process_list_get_no_running(&plist) > 0) { + poll(NULL, 0, timeout); + } + } + + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_no_running(&plist) == 0); + assert(no_finished == 3); + assert(process_list_get_summary_result(&plist) == 0); + assert(process_list_get_summary_result_short(&plist) == 0); + + process_list_move_active_entries_to_kill_list(&plist); + + plist_entry = process_list_add(&plist, "true", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "ignoresig1", "bash -c \"trap 'echo trap' SIGINT;while true;do sleep 1;done\""); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "ignoresig2", "bash -c \"trap 'echo trap' SIGINT SIGTERM;while true;do sleep 1;done\""); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true4", "/bin/true"); + assert(plist_entry == NULL); + + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 3); + assert(process_list_get_no_running(&plist) == 3); + + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + + assert(process_list_get_no_running(&plist) == 2); + assert(no_finished == 1); + assert(process_list_get_summary_result(&plist) == -1); + assert(process_list_get_summary_result_short(&plist) == -1); + + plist_entry = process_list_add(&plist, "true4", "/bin/true"); + assert(plist_entry == NULL); + + process_list_move_active_entries_to_kill_list(&plist); + + plist_entry = process_list_add(&plist, "true4", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true5", "/bin/true"); + assert(plist_entry == NULL); + + assert(process_list_process_kill_list(&plist) == 0); + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_kill_list_items(&plist) == 1); + + assert(process_list_process_kill_list(&plist) == 0); + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_kill_list_items(&plist) == 0); + + process_list_move_active_entries_to_kill_list(&plist); + assert(process_list_get_summary_result(&plist) == 0); + assert(process_list_get_summary_result_short(&plist) == 0); + + plist_entry = process_list_add(&plist, "true", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true2", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true3", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true4", "/bin/true"); + assert(plist_entry == NULL); + + process_list_free(&plist); + + /* + * Test 3 processes and difference between summary and short-circuit summary + */ + process_list_init(&plist, 3, 1, plist_notify, (void *)0x42); + plist_entry = process_list_add(&plist, "true", "/bin/true"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "false", "/bin/false"); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "loop", "bash -c \"while true;do sleep 1;done\""); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "true4", "/bin/true"); + assert(plist_entry == NULL); + + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 3); + assert(process_list_get_no_running(&plist) == 3); + + /* + * Wait to exit + */ + no_repeats = 10; + timeout = 1000 / no_repeats; + for (i = 0; i < no_repeats; i++) { + assert(process_list_waitpid(&plist) == 0); + if (process_list_get_no_running(&plist) > 0) { + poll(NULL, 0, timeout); + } + } + + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_no_running(&plist) == 1); + assert(no_finished == 2); + assert(process_list_get_summary_result(&plist) == -1); + assert(process_list_get_summary_result_short(&plist) == 1); + + process_list_move_active_entries_to_kill_list(&plist); + assert(process_list_process_kill_list(&plist) == 0); + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + assert(process_list_get_kill_list_items(&plist) == 0); + + process_list_free(&plist); + + /* + * Test process_list_killall by running two bash proceses. + * One ignores INT and second ignores INT and TERM. Waiting for maximum of 2 sec + */ + plist_entry = process_list_add(&plist, "ignoresig1", "bash -c \"trap 'echo trap' SIGINT;while true;do sleep 1;done\""); + assert(plist_entry != NULL); + + plist_entry = process_list_add(&plist, "ignoresig2", "bash -c \"trap 'echo trap' SIGINT SIGTERM;while true;do sleep 1;done\""); + assert(plist_entry != NULL); + + no_executed = 0; + no_finished = 0; + assert(process_list_exec_initialized(&plist) == 0); + assert(no_executed == 2); + assert(process_list_get_no_running(&plist) == 2); + + poll(NULL, 0, 500); + assert(process_list_waitpid(&plist) == 0); + + assert(process_list_get_no_running(&plist) == 2); + assert(no_finished == 0); + assert(process_list_get_summary_result(&plist) == -1); + assert(process_list_get_summary_result_short(&plist) == -1); + + assert(process_list_killall(&plist, 2000) == 0); + assert(process_list_get_kill_list_items(&plist) == 0); + + process_list_free(&plist); + + /* + * Empty killall exits with sucess result + */ + assert(process_list_killall(&plist, 2000) == 0); + + process_list_free(&plist); + + return (0); +} diff --git a/qdevices/test-qnetd-cluster-list.c b/qdevices/test-qnetd-cluster-list.c new file mode 100644 index 0000000..ebbebcb --- /dev/null +++ b/qdevices/test-qnetd-cluster-list.c @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "qnetd-cluster-list.h" +#include "qnetd-client.h" +#include "qnetd-client-list.h" + +static struct qnetd_client_list clients; +static struct qnetd_cluster_list clusters; + +static void +add_client(const char *cluster_name, size_t cluster_name_len, + struct qnetd_client **client, struct qnetd_cluster **cluster) +{ + PRNetAddr addr; + struct qnetd_client *tmp_client; + struct qnetd_cluster *tmp_cluster; + char *client_addr_str; + + memset(&addr, 0, sizeof(addr)); + + client_addr_str = strdup("addrstr"); + assert(client_addr_str != NULL); + + tmp_client = qnetd_client_list_add(&clients, NULL, &addr, client_addr_str, 1000, 2, 1000, NULL); + assert(tmp_client != NULL); + tmp_client->cluster_name = malloc(cluster_name_len + 1); + assert(tmp_client->cluster_name != NULL); + memcpy(tmp_client->cluster_name, cluster_name, cluster_name_len); + tmp_client->cluster_name_len = cluster_name_len; + + tmp_cluster = qnetd_cluster_list_add_client(&clusters, tmp_client); + assert(cluster != NULL); + tmp_client->cluster = tmp_cluster; + + *client = tmp_client; + *cluster = tmp_cluster; +} + +static int +no_clients_in_cluster(struct qnetd_cluster *cluster) +{ + int i; + struct qnetd_client *client; + + i = 0; + + TAILQ_FOREACH(client, &cluster->client_list, cluster_entries) { + i++; + } + + return (i); +} + +static int +no_clusters(void) +{ + int i; + struct qnetd_cluster *cluster; + + i = 0; + + TAILQ_FOREACH(cluster, &clusters, entries) { + i++; + } + + return (i); +} + +static int +is_client_in_cluster(struct qnetd_cluster *cluster, const struct qnetd_client *client) +{ + struct qnetd_client *tmp_client; + + TAILQ_FOREACH(tmp_client, &cluster->client_list, cluster_entries) { + if (tmp_client == client) { + return (1); + } + } + + return (0); +} + +static void +del_client(struct qnetd_client *client) +{ + + qnetd_cluster_list_del_client(&clusters, client->cluster, client); + qnetd_client_list_del(&clients, client); +} + +int +main(void) +{ + struct qnetd_client *client[4]; + struct qnetd_cluster *cluster[4]; + const char *cl_name; + + qnetd_client_list_init(&clients); + qnetd_cluster_list_init(&clusters); + + assert(no_clusters() == 0); + + cl_name = "test_cluster"; + add_client(cl_name, strlen(cl_name), &client[0], &cluster[0]); + assert(no_clusters() == 1); + add_client(cl_name, strlen(cl_name), &client[1], &cluster[1]); + assert(no_clusters() == 1); + + cl_name = "cluster2"; + add_client(cl_name, strlen(cl_name), &client[2], &cluster[2]); + assert(no_clusters() == 2); + add_client(cl_name, strlen(cl_name), &client[3], &cluster[3]); + assert(no_clusters() == 2); + + assert(cluster[0] == cluster[1]); + assert(cluster[2] == cluster[3]); + assert(cluster[0] != cluster[2]); + + assert(no_clients_in_cluster(cluster[0]) == 2); + assert(no_clients_in_cluster(cluster[2]) == 2); + + assert(is_client_in_cluster(cluster[0], client[0])); + assert(is_client_in_cluster(client[0]->cluster, client[0])); + assert(is_client_in_cluster(cluster[0], client[1])); + assert(!is_client_in_cluster(cluster[0], client[2])); + assert(!is_client_in_cluster(cluster[0], client[3])); + + assert(!is_client_in_cluster(cluster[2], client[0])); + assert(!is_client_in_cluster(cluster[2], client[1])); + assert(is_client_in_cluster(cluster[2], client[2])); + assert(is_client_in_cluster(cluster[2], client[3])); + assert(is_client_in_cluster(client[2]->cluster, client[2])); + + del_client(client[0]); + assert(no_clusters() == 2); + assert(no_clients_in_cluster(cluster[0]) == 1); + assert(no_clients_in_cluster(cluster[2]) == 2); + + assert(!is_client_in_cluster(cluster[0], client[0])); + assert(is_client_in_cluster(cluster[0], client[1])); + assert(!is_client_in_cluster(cluster[0], client[2])); + assert(!is_client_in_cluster(cluster[0], client[3])); + + add_client(cl_name, strlen(cl_name), &client[0], &cluster[0]); + assert(no_clients_in_cluster(cluster[1]) == 1); + assert(no_clients_in_cluster(cluster[2]) == 3); + + assert(!is_client_in_cluster(cluster[1], client[0])); + assert(is_client_in_cluster(cluster[1], client[1])); + assert(!is_client_in_cluster(cluster[1], client[2])); + assert(!is_client_in_cluster(cluster[1], client[3])); + + assert(is_client_in_cluster(cluster[2], client[0])); + assert(!is_client_in_cluster(cluster[2], client[1])); + assert(is_client_in_cluster(cluster[2], client[2])); + assert(is_client_in_cluster(cluster[2], client[3])); + + del_client(client[1]); + assert(no_clusters() == 1); + + del_client(client[2]); + assert(no_clusters() == 1); + + del_client(client[3]); + assert(no_clusters() == 1); + + del_client(client[0]); + assert(no_clusters() == 0); + + return (0); +} diff --git a/qdevices/timer-list.c b/qdevices/timer-list.c new file mode 100644 index 0000000..b9b83ba --- /dev/null +++ b/qdevices/timer-list.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "timer-list.h" + +void +timer_list_init(struct timer_list *tlist) +{ + + memset(tlist, 0, sizeof(*tlist)); + + TAILQ_INIT(&tlist->list); + TAILQ_INIT(&tlist->free_list); +} + +static PRIntervalTime +timer_list_entry_time_to_expire(const struct timer_list_entry *entry, PRIntervalTime current_time) +{ + PRIntervalTime diff, half_interval; + + diff = entry->expire_time - current_time; + half_interval = ~0; + half_interval /= 2; + + if (diff > half_interval) { + return (0); + } + + return (diff); +} + +static int +timer_list_entry_cmp(const struct timer_list_entry *entry1, + const struct timer_list_entry *entry2, PRIntervalTime current_time) +{ + PRIntervalTime diff1, diff2; + int res; + + diff1 = timer_list_entry_time_to_expire(entry1, current_time); + diff2 = timer_list_entry_time_to_expire(entry2, current_time); + + res = 0; + + if (diff1 < diff2) res = -1; + if (diff1 > diff2) res = 1; + + return (res); +} + +static void +timer_list_insert_into_list(struct timer_list *tlist, struct timer_list_entry *new_entry) +{ + struct timer_list_entry *entry; + + /* + * This can overflow and it's not a problem + */ + new_entry->expire_time = new_entry->epoch + PR_MillisecondsToInterval(new_entry->interval); + + entry = TAILQ_FIRST(&tlist->list); + while (entry != NULL) { + if (timer_list_entry_cmp(entry, new_entry, new_entry->epoch) > 0) { + /* + * Insert new entry right before current entry + */ + TAILQ_INSERT_BEFORE(entry, new_entry, entries); + + break; + } + + entry = TAILQ_NEXT(entry, entries); + } + + if (entry == NULL) { + TAILQ_INSERT_TAIL(&tlist->list, new_entry, entries); + } +} + +struct timer_list_entry * +timer_list_add(struct timer_list *tlist, PRUint32 interval, timer_list_cb_fn func, void *data1, + void *data2) +{ + struct timer_list_entry *new_entry; + + if (interval < 1 || interval > TIMER_LIST_MAX_INTERVAL) { + return (NULL); + } + + if (!TAILQ_EMPTY(&tlist->free_list)) { + /* + * Use free list entry + */ + new_entry = TAILQ_FIRST(&tlist->free_list); + TAILQ_REMOVE(&tlist->free_list, new_entry, entries); + } else { + /* + * Alloc new entry + */ + new_entry = malloc(sizeof(*new_entry)); + if (new_entry == NULL) { + return (NULL); + } + } + + memset(new_entry, 0, sizeof(*new_entry)); + new_entry->epoch = PR_IntervalNow(); + new_entry->interval = interval; + new_entry->func = func; + new_entry->user_data1 = data1; + new_entry->user_data2 = data2; + new_entry->is_active = 1; + + timer_list_insert_into_list(tlist, new_entry); + + return (new_entry); +} + +void +timer_list_reschedule(struct timer_list *tlist, struct timer_list_entry *entry) +{ + + if (entry->is_active) { + entry->epoch = PR_IntervalNow(); + TAILQ_REMOVE(&tlist->list, entry, entries); + timer_list_insert_into_list(tlist, entry); + } +} + +void +timer_list_expire(struct timer_list *tlist) +{ + PRIntervalTime now; + struct timer_list_entry *entry; + int res; + + now = PR_IntervalNow(); + + while ((entry = TAILQ_FIRST(&tlist->list)) != NULL && + timer_list_entry_time_to_expire(entry, now) == 0) { + /* + * Expired + */ + res = entry->func(entry->user_data1, entry->user_data2); + if (res == 0) { + /* + * Move item to free list + */ + timer_list_delete(tlist, entry); + } else if (entry->is_active) { + /* + * Schedule again + */ + entry->epoch = now; + TAILQ_REMOVE(&tlist->list, entry, entries); + timer_list_insert_into_list(tlist, entry); + } + } +} + +PRIntervalTime +timer_list_time_to_expire(struct timer_list *tlist) +{ + struct timer_list_entry *entry; + + entry = TAILQ_FIRST(&tlist->list); + if (entry == NULL) { + return (PR_INTERVAL_NO_TIMEOUT); + } + + return (timer_list_entry_time_to_expire(entry, PR_IntervalNow())); +} + +uint32_t +timer_list_time_to_expire_ms(struct timer_list *tlist) +{ + struct timer_list_entry *entry; + uint32_t u32; + + entry = TAILQ_FIRST(&tlist->list); + if (entry == NULL) { + u32 = ~((uint32_t)0); + return (u32); + } + + return (PR_IntervalToMilliseconds(timer_list_entry_time_to_expire(entry, PR_IntervalNow()))); +} + +void +timer_list_delete(struct timer_list *tlist, struct timer_list_entry *entry) +{ + + if (entry->is_active) { + /* + * Move item to free list + */ + TAILQ_REMOVE(&tlist->list, entry, entries); + TAILQ_INSERT_HEAD(&tlist->free_list, entry, entries); + entry->is_active = 0; + } +} + +void +timer_list_free(struct timer_list *tlist) +{ + struct timer_list_entry *entry; + struct timer_list_entry *entry_next; + + + entry = TAILQ_FIRST(&tlist->list); + + while (entry != NULL) { + entry_next = TAILQ_NEXT(entry, entries); + + free(entry); + + entry = entry_next; + } + + entry = TAILQ_FIRST(&tlist->free_list); + + while (entry != NULL) { + entry_next = TAILQ_NEXT(entry, entries); + + free(entry); + + entry = entry_next; + } + + timer_list_init(tlist); +} diff --git a/qdevices/timer-list.h b/qdevices/timer-list.h new file mode 100644 index 0000000..86313ff --- /dev/null +++ b/qdevices/timer-list.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TIMER_LIST_H_ +#define _TIMER_LIST_H_ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * PR Interval is 32-bit integer which overflows. Maximum useable interval is around + * 6 hours (less). So define max interval as 5 hours + */ +#define TIMER_LIST_MAX_INTERVAL 18000000 + +typedef int (*timer_list_cb_fn)(void *data1, void *data2); + +struct timer_list_entry { + /* Time when timer was planned */ + PRIntervalTime epoch; + /* Number of miliseconds to expire */ + PRUint32 interval; + /* Time when timer expires (epoch + interval) */ + PRIntervalTime expire_time; + timer_list_cb_fn func; + void *user_data1; + void *user_data2; + int is_active; + TAILQ_ENTRY(timer_list_entry) entries; +}; + +struct timer_list { + TAILQ_HEAD(, timer_list_entry) list; + TAILQ_HEAD(, timer_list_entry) free_list; +}; + +extern void timer_list_init(struct timer_list *tlist); + +extern struct timer_list_entry *timer_list_add(struct timer_list *tlist, + PRUint32 interval, timer_list_cb_fn func, void *data1, void *data2); + +extern void timer_list_reschedule(struct timer_list *tlist, + struct timer_list_entry *entry); + +extern void timer_list_delete(struct timer_list *tlist, + struct timer_list_entry *entry); + +extern void timer_list_expire(struct timer_list *tlist); + +extern PRIntervalTime timer_list_time_to_expire(struct timer_list *tlist); + +extern uint32_t timer_list_time_to_expire_ms(struct timer_list *tlist); + +extern void timer_list_free(struct timer_list *tlist); + +#ifdef __cplusplus +} +#endif + +#endif /* _TIMER_LIST_H_ */ diff --git a/qdevices/tlv.c b/qdevices/tlv.c new file mode 100644 index 0000000..43bf97f --- /dev/null +++ b/qdevices/tlv.c @@ -0,0 +1,1121 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include + +/* + * 64-bit variant of ntoh is not exactly standard... + */ +#if defined(__linux__) +#include +#elif defined(__FreeBSD__) || defined(__NetBSD__) +#include +#elif defined(__OpenBSD__) +#define be64toh(x) betoh64(x) +#endif + +#include "tlv.h" + +#define TLV_TYPE_LENGTH 2 +#define TLV_LENGTH_LENGTH 2 + +#define TLV_STATIC_SUPPORTED_OPTIONS_SIZE 23 + +enum tlv_opt_type tlv_static_supported_options[TLV_STATIC_SUPPORTED_OPTIONS_SIZE] = { + TLV_OPT_MSG_SEQ_NUMBER, + TLV_OPT_CLUSTER_NAME, + TLV_OPT_TLS_SUPPORTED, + TLV_OPT_TLS_CLIENT_CERT_REQUIRED, + TLV_OPT_SUPPORTED_MESSAGES, + TLV_OPT_SUPPORTED_OPTIONS, + TLV_OPT_REPLY_ERROR_CODE, + TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE, + TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE, + TLV_OPT_NODE_ID, + TLV_OPT_SUPPORTED_DECISION_ALGORITHMS, + TLV_OPT_DECISION_ALGORITHM, + TLV_OPT_HEARTBEAT_INTERVAL, + TLV_OPT_RING_ID, + TLV_OPT_CONFIG_VERSION, + TLV_OPT_DATA_CENTER_ID, + TLV_OPT_NODE_STATE, + TLV_OPT_NODE_INFO, + TLV_OPT_NODE_LIST_TYPE, + TLV_OPT_VOTE, + TLV_OPT_QUORATE, + TLV_OPT_TIE_BREAKER, + TLV_OPT_HEURISTICS, +}; + +int +tlv_add(struct dynar *msg, enum tlv_opt_type opt_type, uint16_t opt_len, const void *value) +{ + uint16_t nlen; + uint16_t nopt_type; + + if (dynar_size(msg) + sizeof(nopt_type) + sizeof(nlen) + opt_len > dynar_max_size(msg)) { + return (-1); + } + + nopt_type = htons((uint16_t)opt_type); + nlen = htons(opt_len); + + dynar_cat(msg, &nopt_type, sizeof(nopt_type)); + dynar_cat(msg, &nlen, sizeof(nlen)); + dynar_cat(msg, value, opt_len); + + return (0); +} + +int +tlv_add_u32(struct dynar *msg, enum tlv_opt_type opt_type, uint32_t u32) +{ + uint32_t nu32; + + nu32 = htonl(u32); + + return (tlv_add(msg, opt_type, sizeof(nu32), &nu32)); +} + +int +tlv_add_u8(struct dynar *msg, enum tlv_opt_type opt_type, uint8_t u8) +{ + + return (tlv_add(msg, opt_type, sizeof(u8), &u8)); +} + +int +tlv_add_u16(struct dynar *msg, enum tlv_opt_type opt_type, uint16_t u16) +{ + uint16_t nu16; + + nu16 = htons(u16); + + return (tlv_add(msg, opt_type, sizeof(nu16), &nu16)); +} + +int +tlv_add_u64(struct dynar *msg, enum tlv_opt_type opt_type, uint64_t u64) +{ + uint64_t nu64; + + nu64 = htobe64(u64); + + return (tlv_add(msg, opt_type, sizeof(nu64), &nu64)); +} + +int +tlv_add_string(struct dynar *msg, enum tlv_opt_type opt_type, const char *str) +{ + + return (tlv_add(msg, opt_type, strlen(str), str)); +} + +int +tlv_add_msg_seq_number(struct dynar *msg, uint32_t msg_seq_number) +{ + + return (tlv_add_u32(msg, TLV_OPT_MSG_SEQ_NUMBER, msg_seq_number)); +} + +int +tlv_add_cluster_name(struct dynar *msg, const char *cluster_name) +{ + + return (tlv_add_string(msg, TLV_OPT_CLUSTER_NAME, cluster_name)); +} + +int +tlv_add_tls_supported(struct dynar *msg, enum tlv_tls_supported tls_supported) +{ + + return (tlv_add_u8(msg, TLV_OPT_TLS_SUPPORTED, tls_supported)); +} + +int +tlv_add_tls_client_cert_required(struct dynar *msg, int tls_client_cert_required) +{ + + return (tlv_add_u8(msg, TLV_OPT_TLS_CLIENT_CERT_REQUIRED, tls_client_cert_required)); +} + +int +tlv_add_u16_array(struct dynar *msg, enum tlv_opt_type opt_type, const uint16_t *array, + size_t array_size) +{ + size_t i; + uint16_t *nu16a; + uint16_t opt_len; + int res; + + nu16a = malloc(sizeof(uint16_t) * array_size); + if (nu16a == NULL) { + return (-1); + } + + for (i = 0; i < array_size; i++) { + nu16a[i] = htons(array[i]); + } + + opt_len = sizeof(uint16_t) * array_size; + + res = tlv_add(msg, opt_type, opt_len, nu16a); + + free(nu16a); + + return (res); +} + +int +tlv_add_supported_options(struct dynar *msg, const enum tlv_opt_type *supported_options, + size_t no_supported_options) +{ + uint16_t *u16a; + size_t i; + int res; + + u16a = malloc(sizeof(*u16a) * no_supported_options); + if (u16a == NULL) { + return (-1); + } + + for (i = 0; i < no_supported_options; i++) { + u16a[i] = (uint16_t)supported_options[i]; + } + + res = (tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_OPTIONS, u16a, no_supported_options)); + + free(u16a); + + return (res); +} + +int +tlv_add_supported_decision_algorithms(struct dynar *msg, + const enum tlv_decision_algorithm_type *supported_algorithms, size_t no_supported_algorithms) +{ + uint16_t *u16a; + size_t i; + int res; + + u16a = malloc(sizeof(*u16a) * no_supported_algorithms); + if (u16a == NULL) { + return (-1); + } + + for (i = 0; i < no_supported_algorithms; i++) { + u16a[i] = (uint16_t)supported_algorithms[i]; + } + + res = (tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_DECISION_ALGORITHMS, u16a, + no_supported_algorithms)); + + free(u16a); + + return (res); +} + +int +tlv_add_reply_error_code(struct dynar *msg, enum tlv_reply_error_code error_code) +{ + + return (tlv_add_u16(msg, TLV_OPT_REPLY_ERROR_CODE, (uint16_t)error_code)); +} + +int +tlv_add_server_maximum_request_size(struct dynar *msg, size_t server_maximum_request_size) +{ + + return (tlv_add_u32(msg, TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE, server_maximum_request_size)); +} + +int +tlv_add_server_maximum_reply_size(struct dynar *msg, size_t server_maximum_reply_size) +{ + + return (tlv_add_u32(msg, TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE, server_maximum_reply_size)); +} + +int +tlv_add_node_id(struct dynar *msg, uint32_t node_id) +{ + + return (tlv_add_u32(msg, TLV_OPT_NODE_ID, node_id)); +} + +int +tlv_add_decision_algorithm(struct dynar *msg, enum tlv_decision_algorithm_type decision_algorithm) +{ + + return (tlv_add_u16(msg, TLV_OPT_DECISION_ALGORITHM, (uint16_t)decision_algorithm)); +} + +int +tlv_add_heartbeat_interval(struct dynar *msg, uint32_t heartbeat_interval) +{ + + return (tlv_add_u32(msg, TLV_OPT_HEARTBEAT_INTERVAL, heartbeat_interval)); +} + +int +tlv_add_ring_id(struct dynar *msg, const struct tlv_ring_id *ring_id) +{ + uint64_t nu64; + uint32_t nu32; + char tmp_buf[12]; + + nu32 = htonl(ring_id->node_id); + nu64 = htobe64(ring_id->seq); + + memcpy(tmp_buf, &nu32, sizeof(nu32)); + memcpy(tmp_buf + sizeof(nu32), &nu64, sizeof(nu64)); + + return (tlv_add(msg, TLV_OPT_RING_ID, sizeof(tmp_buf), tmp_buf)); +} + +int +tlv_add_tie_breaker(struct dynar *msg, const struct tlv_tie_breaker *tie_breaker) +{ + uint32_t nu32; + uint8_t u8; + char tmp_buf[5]; + + u8 = tie_breaker->mode; + nu32 = (tie_breaker->mode == TLV_TIE_BREAKER_MODE_NODE_ID ? + htonl(tie_breaker->node_id) : 0); + + memcpy(tmp_buf, &u8, sizeof(u8)); + memcpy(tmp_buf + sizeof(u8), &nu32, sizeof(nu32)); + + return (tlv_add(msg, TLV_OPT_TIE_BREAKER, sizeof(tmp_buf), tmp_buf)); +} + +int +tlv_add_config_version(struct dynar *msg, uint64_t config_version) +{ + + return (tlv_add_u64(msg, TLV_OPT_CONFIG_VERSION, config_version)); +} + +int +tlv_add_data_center_id(struct dynar *msg, uint32_t data_center_id) +{ + + return (tlv_add_u32(msg, TLV_OPT_DATA_CENTER_ID, data_center_id)); +} + +int +tlv_add_node_state(struct dynar *msg, enum tlv_node_state node_state) +{ + + return (tlv_add_u8(msg, TLV_OPT_NODE_STATE, node_state)); +} + +int +tlv_add_node_info(struct dynar *msg, const struct tlv_node_info *node_info) +{ + struct dynar opt_value; + int res; + + res = 0; + /* + * Create sub message, + */ + dynar_init(&opt_value, 1024); + if ((res = tlv_add_node_id(&opt_value, node_info->node_id)) != 0) { + goto exit_dynar_destroy; + } + + if (node_info->data_center_id != 0) { + if ((res = tlv_add_data_center_id(&opt_value, node_info->data_center_id)) != 0) { + goto exit_dynar_destroy; + } + } + + if (node_info->node_state != TLV_NODE_STATE_NOT_SET) { + if ((res = tlv_add_node_state(&opt_value, node_info->node_state)) != 0) { + goto exit_dynar_destroy; + } + } + + res = tlv_add(msg, TLV_OPT_NODE_INFO, dynar_size(&opt_value), dynar_data(&opt_value)); + if (res != 0) { + goto exit_dynar_destroy; + } + + +exit_dynar_destroy: + dynar_destroy(&opt_value); + + return (res); +} + +int +tlv_add_node_list_type(struct dynar *msg, enum tlv_node_list_type node_list_type) +{ + + return (tlv_add_u8(msg, TLV_OPT_NODE_LIST_TYPE, node_list_type)); +} + +int +tlv_add_vote(struct dynar *msg, enum tlv_vote vote) +{ + + return (tlv_add_u8(msg, TLV_OPT_VOTE, vote)); +} + +int +tlv_add_quorate(struct dynar *msg, enum tlv_quorate quorate) +{ + + return (tlv_add_u8(msg, TLV_OPT_QUORATE, quorate)); +} + +int +tlv_add_heuristics(struct dynar *msg, enum tlv_heuristics heuristics) +{ + + if (heuristics == TLV_HEURISTICS_UNDEFINED) { + return (-1); + } + + return (tlv_add_u8(msg, TLV_OPT_HEURISTICS, heuristics)); +} + +void +tlv_iter_init_str(const char *msg, size_t msg_len, size_t msg_header_len, + struct tlv_iterator *tlv_iter) +{ + + tlv_iter->msg = msg; + tlv_iter->msg_len = msg_len; + tlv_iter->current_pos = 0; + tlv_iter->msg_header_len = msg_header_len; + tlv_iter->iter_next_called = 0; +} + +void +tlv_iter_init(const struct dynar *msg, size_t msg_header_len, struct tlv_iterator *tlv_iter) +{ + + tlv_iter_init_str(dynar_data(msg), dynar_size(msg), msg_header_len, tlv_iter); +} + +enum tlv_opt_type +tlv_iter_get_type(const struct tlv_iterator *tlv_iter) +{ + uint16_t ntype; + uint16_t type; + + memcpy(&ntype, tlv_iter->msg + tlv_iter->current_pos, sizeof(ntype)); + type = ntohs(ntype); + + return (type); +} + +uint16_t +tlv_iter_get_len(const struct tlv_iterator *tlv_iter) +{ + uint16_t nlen; + uint16_t len; + + memcpy(&nlen, tlv_iter->msg + tlv_iter->current_pos + TLV_TYPE_LENGTH, sizeof(nlen)); + len = ntohs(nlen); + + return (len); +} + +const char * +tlv_iter_get_data(const struct tlv_iterator *tlv_iter) +{ + + return (tlv_iter->msg + tlv_iter->current_pos + TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH); +} + +int +tlv_iter_next(struct tlv_iterator *tlv_iter) +{ + uint16_t len; + + if (tlv_iter->iter_next_called == 0) { + tlv_iter->iter_next_called = 1; + tlv_iter->current_pos = tlv_iter->msg_header_len; + + goto check_tlv_validity; + } + + len = tlv_iter_get_len(tlv_iter); + + if (tlv_iter->current_pos + TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH + len >= + tlv_iter->msg_len) { + return (0); + } + + tlv_iter->current_pos += TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH + len; + +check_tlv_validity: + /* + * Check if tlv is valid = is not larger than whole message + */ + len = tlv_iter_get_len(tlv_iter); + + if (tlv_iter->current_pos + TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH + len > tlv_iter->msg_len) { + return (-1); + } + + return (1); +} + +int +tlv_iter_decode_u32(struct tlv_iterator *tlv_iter, uint32_t *res) +{ + const char *opt_data; + uint16_t opt_len; + uint32_t nu32; + + opt_len = tlv_iter_get_len(tlv_iter); + opt_data = tlv_iter_get_data(tlv_iter); + + if (opt_len != sizeof(nu32)) { + return (-1); + } + + memcpy(&nu32, opt_data, sizeof(nu32)); + *res = ntohl(nu32); + + return (0); +} + +int +tlv_iter_decode_u8(struct tlv_iterator *tlv_iter, uint8_t *res) +{ + const char *opt_data; + uint16_t opt_len; + + opt_len = tlv_iter_get_len(tlv_iter); + opt_data = tlv_iter_get_data(tlv_iter); + + if (opt_len != sizeof(*res)) { + return (-1); + } + + memcpy(res, opt_data, sizeof(*res)); + + return (0); +} + +int +tlv_iter_decode_client_cert_required(struct tlv_iterator *tlv_iter, uint8_t *client_cert_required) +{ + + return (tlv_iter_decode_u8(tlv_iter, client_cert_required)); +} + +int +tlv_iter_decode_str(struct tlv_iterator *tlv_iter, char **str, size_t *str_len) +{ + const char *opt_data; + uint16_t opt_len; + char *tmp_str; + + opt_len = tlv_iter_get_len(tlv_iter); + opt_data = tlv_iter_get_data(tlv_iter); + + tmp_str = malloc(opt_len + 1); + if (tmp_str == NULL) { + return (-1); + } + + memcpy(tmp_str, opt_data, opt_len); + tmp_str[opt_len] = '\0'; + + *str = tmp_str; + *str_len = opt_len; + + return (0); +} + +int +tlv_iter_decode_u16_array(struct tlv_iterator *tlv_iter, uint16_t **u16a, size_t *no_items) +{ + uint16_t opt_len; + uint16_t *u16a_res; + size_t i; + + opt_len = tlv_iter_get_len(tlv_iter); + + if (opt_len % sizeof(uint16_t) != 0) { + return (-1); + } + + *no_items = opt_len / sizeof(uint16_t); + + u16a_res = malloc(sizeof(uint16_t) * *no_items); + if (u16a_res == NULL) { + return (-2); + } + + memcpy(u16a_res, tlv_iter_get_data(tlv_iter), opt_len); + + for (i = 0; i < *no_items; i++) { + u16a_res[i] = ntohs(u16a_res[i]); + } + + *u16a = u16a_res; + + return (0); +} + +int +tlv_iter_decode_supported_options(struct tlv_iterator *tlv_iter, + enum tlv_opt_type **supported_options, size_t *no_supported_options) +{ + uint16_t *u16a; + enum tlv_opt_type *tlv_opt_array; + size_t i; + int res; + + res = tlv_iter_decode_u16_array(tlv_iter, &u16a, no_supported_options); + if (res != 0) { + return (res); + } + + tlv_opt_array = malloc(sizeof(enum tlv_opt_type) * *no_supported_options); + if (tlv_opt_array == NULL) { + free(u16a); + return (-2); + } + + for (i = 0; i < *no_supported_options; i++) { + tlv_opt_array[i] = (enum tlv_opt_type)u16a[i]; + } + + free(u16a); + + *supported_options = tlv_opt_array; + + return (0); +} + +int +tlv_iter_decode_supported_decision_algorithms(struct tlv_iterator *tlv_iter, + enum tlv_decision_algorithm_type **supported_decision_algorithms, + size_t *no_supported_decision_algorithms) +{ + uint16_t *u16a; + enum tlv_decision_algorithm_type *tlv_decision_algorithm_type_array; + size_t i; + int res; + + res = tlv_iter_decode_u16_array(tlv_iter, &u16a, no_supported_decision_algorithms); + if (res != 0) { + return (res); + } + + tlv_decision_algorithm_type_array = malloc( + sizeof(enum tlv_decision_algorithm_type) * *no_supported_decision_algorithms); + + if (tlv_decision_algorithm_type_array == NULL) { + free(u16a); + return (-2); + } + + for (i = 0; i < *no_supported_decision_algorithms; i++) { + tlv_decision_algorithm_type_array[i] = (enum tlv_decision_algorithm_type)u16a[i]; + } + + free(u16a); + + *supported_decision_algorithms = tlv_decision_algorithm_type_array; + + return (0); +} + +int +tlv_iter_decode_u16(struct tlv_iterator *tlv_iter, uint16_t *u16) +{ + const char *opt_data; + uint16_t opt_len; + uint16_t nu16; + + opt_len = tlv_iter_get_len(tlv_iter); + opt_data = tlv_iter_get_data(tlv_iter); + + if (opt_len != sizeof(nu16)) { + return (-1); + } + + memcpy(&nu16, opt_data, sizeof(nu16)); + *u16 = ntohs(nu16); + + return (0); +} + +int +tlv_iter_decode_u64(struct tlv_iterator *tlv_iter, uint64_t *u64) +{ + const char *opt_data; + uint64_t opt_len; + uint64_t nu64; + + opt_len = tlv_iter_get_len(tlv_iter); + opt_data = tlv_iter_get_data(tlv_iter); + + if (opt_len != sizeof(nu64)) { + return (-1); + } + + memcpy(&nu64, opt_data, sizeof(nu64)); + *u64 = be64toh(nu64); + + return (0); +} + +int +tlv_iter_decode_reply_error_code(struct tlv_iterator *tlv_iter, + enum tlv_reply_error_code *reply_error_code) +{ + + return (tlv_iter_decode_u16(tlv_iter, (uint16_t *)reply_error_code)); +} + +int +tlv_iter_decode_tls_supported(struct tlv_iterator *tlv_iter, enum tlv_tls_supported *tls_supported) +{ + uint8_t u8; + enum tlv_tls_supported tmp_tls_supported; + + if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) { + return (-1); + } + + tmp_tls_supported = u8; + + if (tmp_tls_supported != TLV_TLS_UNSUPPORTED && + tmp_tls_supported != TLV_TLS_SUPPORTED && + tmp_tls_supported != TLV_TLS_REQUIRED) { + return (-4); + } + + *tls_supported = tmp_tls_supported; + + return (0); +} + +int +tlv_iter_decode_decision_algorithm(struct tlv_iterator *tlv_iter, + enum tlv_decision_algorithm_type *decision_algorithm) +{ + uint16_t u16; + + if (tlv_iter_decode_u16(tlv_iter, &u16) != 0) { + return (-1); + } + + *decision_algorithm = (enum tlv_decision_algorithm_type)u16; + + return (0); +} + +int +tlv_iter_decode_ring_id(struct tlv_iterator *tlv_iter, struct tlv_ring_id *ring_id) +{ + const char *opt_data; + uint16_t opt_len; + uint32_t nu32; + uint64_t nu64; + char tmp_buf[12]; + + opt_len = tlv_iter_get_len(tlv_iter); + opt_data = tlv_iter_get_data(tlv_iter); + + if (opt_len != sizeof(tmp_buf)) { + return (-1); + } + + memcpy(&nu32, opt_data, sizeof(nu32)); + memcpy(&nu64, opt_data + sizeof(nu32), sizeof(nu64)); + + ring_id->node_id = ntohl(nu32); + ring_id->seq = be64toh(nu64); + + return (0); +} + +int +tlv_iter_decode_tie_breaker(struct tlv_iterator *tlv_iter, struct tlv_tie_breaker *tie_breaker) +{ + const char *opt_data; + uint16_t opt_len; + uint32_t nu32; + uint8_t u8; + enum tlv_tie_breaker_mode tie_breaker_mode; + char tmp_buf[5]; + + opt_len = tlv_iter_get_len(tlv_iter); + opt_data = tlv_iter_get_data(tlv_iter); + + if (opt_len != sizeof(tmp_buf)) { + return (-1); + } + + memcpy(&u8, opt_data, sizeof(u8)); + tie_breaker_mode = u8; + + if (tie_breaker_mode != TLV_TIE_BREAKER_MODE_LOWEST && + tie_breaker_mode != TLV_TIE_BREAKER_MODE_HIGHEST && + tie_breaker_mode != TLV_TIE_BREAKER_MODE_NODE_ID) { + return (-4); + } + + memcpy(&nu32, opt_data + sizeof(u8), sizeof(nu32)); + + tie_breaker->mode = tie_breaker_mode; + tie_breaker->node_id = (tie_breaker->mode == TLV_TIE_BREAKER_MODE_NODE_ID ? + ntohl(nu32) : 0); + + return (0); +} + +int +tlv_iter_decode_node_state(struct tlv_iterator *tlv_iter, enum tlv_node_state *node_state) +{ + uint8_t u8; + enum tlv_node_state tmp_node_state; + + if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) { + return (-1); + } + + tmp_node_state = u8; + + if (tmp_node_state != TLV_NODE_STATE_MEMBER && + tmp_node_state != TLV_NODE_STATE_DEAD && + tmp_node_state != TLV_NODE_STATE_LEAVING) { + return (-4); + } + + *node_state = tmp_node_state; + + return (0); +} + +int +tlv_iter_decode_node_info(struct tlv_iterator *tlv_iter, struct tlv_node_info *node_info) +{ + struct tlv_iterator data_tlv_iter; + int iter_res; + int res; + enum tlv_opt_type opt_type; + struct tlv_node_info tmp_node_info; + + memset(&tmp_node_info, 0, sizeof(tmp_node_info)); + + tlv_iter_init_str(tlv_iter_get_data(tlv_iter), tlv_iter_get_len(tlv_iter), 0, + &data_tlv_iter); + + while ((iter_res = tlv_iter_next(&data_tlv_iter)) > 0) { + opt_type = tlv_iter_get_type(&data_tlv_iter); + + switch (opt_type) { + case TLV_OPT_NODE_ID: + if ((res = tlv_iter_decode_u32(&data_tlv_iter, + &tmp_node_info.node_id)) != 0) { + return (res); + } + break; + case TLV_OPT_DATA_CENTER_ID: + if ((res = tlv_iter_decode_u32(&data_tlv_iter, + &tmp_node_info.data_center_id)) != 0) { + return (res); + } + break; + case TLV_OPT_NODE_STATE: + if ((res = tlv_iter_decode_node_state(&data_tlv_iter, + &tmp_node_info.node_state)) != 0) { + return (res); + } + break; + default: + /* + * Other options are not processed + */ + break; + } + } + + if (iter_res != 0) { + return (-3); + } + + if (tmp_node_info.node_id == 0) { + return (-4); + } + + memcpy(node_info, &tmp_node_info, sizeof(tmp_node_info)); + + return (0); +} + +int +tlv_iter_decode_node_list_type(struct tlv_iterator *tlv_iter, + enum tlv_node_list_type *node_list_type) +{ + uint8_t u8; + enum tlv_node_list_type tmp_node_list_type; + + if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) { + return (-1); + } + + tmp_node_list_type = u8; + + if (tmp_node_list_type != TLV_NODE_LIST_TYPE_INITIAL_CONFIG && + tmp_node_list_type != TLV_NODE_LIST_TYPE_CHANGED_CONFIG && + tmp_node_list_type != TLV_NODE_LIST_TYPE_MEMBERSHIP && + tmp_node_list_type != TLV_NODE_LIST_TYPE_QUORUM) { + return (-4); + } + + *node_list_type = tmp_node_list_type; + + return (0); +} + +int +tlv_iter_decode_vote(struct tlv_iterator *tlv_iter, enum tlv_vote *vote) +{ + uint8_t u8; + enum tlv_vote tmp_vote; + + if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) { + return (-1); + } + + tmp_vote = u8; + + if (tmp_vote != TLV_VOTE_ACK && + tmp_vote != TLV_VOTE_NACK && + tmp_vote != TLV_VOTE_ASK_LATER && + tmp_vote != TLV_VOTE_WAIT_FOR_REPLY && + tmp_vote != TLV_VOTE_NO_CHANGE) { + return (-4); + } + + *vote = tmp_vote; + + return (0); +} + +int +tlv_iter_decode_quorate(struct tlv_iterator *tlv_iter, enum tlv_quorate *quorate) +{ + uint8_t u8; + enum tlv_quorate tmp_quorate; + + if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) { + return (-1); + } + + tmp_quorate = u8; + + if (tmp_quorate != TLV_QUORATE_QUORATE && + tmp_quorate != TLV_QUORATE_INQUORATE) { + return (-4); + } + + *quorate = tmp_quorate; + + return (0); +} + +int +tlv_iter_decode_heuristics(struct tlv_iterator *tlv_iter, enum tlv_heuristics *heuristics) +{ + uint8_t u8; + enum tlv_heuristics tmp_heuristics; + + if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) { + return (-1); + } + + tmp_heuristics = u8; + + if (tmp_heuristics != TLV_HEURISTICS_PASS && + tmp_heuristics != TLV_HEURISTICS_FAIL) { + return (-4); + } + + *heuristics = tmp_heuristics; + + return (0); +} + +void +tlv_get_supported_options(enum tlv_opt_type **supported_options, size_t *no_supported_options) +{ + + *supported_options = tlv_static_supported_options; + *no_supported_options = TLV_STATIC_SUPPORTED_OPTIONS_SIZE; +} + +int +tlv_ring_id_eq(const struct tlv_ring_id *rid1, const struct tlv_ring_id *rid2) +{ + + return (rid1->node_id == rid2->node_id && rid1->seq == rid2->seq); +} + +int +tlv_tie_breaker_eq(const struct tlv_tie_breaker *tb1, const struct tlv_tie_breaker *tb2) +{ + + if (tb1->mode == tb2->mode && tb1->mode == TLV_TIE_BREAKER_MODE_NODE_ID) { + return (tb1->node_id == tb2->node_id); + } + + return (tb1->mode == tb2->mode); +} + +const char * +tlv_vote_to_str(enum tlv_vote vote) +{ + + switch (vote) { + case TLV_VOTE_UNDEFINED: break; + case TLV_VOTE_ACK: return ("ACK"); break; + case TLV_VOTE_NACK: return ("NACK"); break; + case TLV_VOTE_ASK_LATER: return ("Ask later"); break; + case TLV_VOTE_WAIT_FOR_REPLY: return ("Wait for reply"); break; + case TLV_VOTE_NO_CHANGE: return ("No change"); break; + } + + return ("Unknown vote value"); +} + +const char * +tlv_node_state_to_str(enum tlv_node_state state) +{ + + switch (state) { + case TLV_NODE_STATE_NOT_SET: return ("not set"); break; + case TLV_NODE_STATE_MEMBER: return ("member"); break; + case TLV_NODE_STATE_DEAD: return ("dead"); break; + case TLV_NODE_STATE_LEAVING: return ("leaving"); break; + } + + return ("Unhandled node state"); +} + +const char * +tlv_tls_supported_to_str(enum tlv_tls_supported tls_supported) +{ + + switch (tls_supported) { + case TLV_TLS_UNSUPPORTED: return ("Unsupported"); break; + case TLV_TLS_SUPPORTED: return ("Supported"); break; + case TLV_TLS_REQUIRED: return ("Required"); break; + } + + return ("Unhandled tls supported state"); +} + +const char * +tlv_decision_algorithm_type_to_str(enum tlv_decision_algorithm_type algorithm) +{ + + switch (algorithm) { + case TLV_DECISION_ALGORITHM_TYPE_TEST: return ("Test"); break; + case TLV_DECISION_ALGORITHM_TYPE_FFSPLIT: return ("Fifty-Fifty split"); break; + case TLV_DECISION_ALGORITHM_TYPE_2NODELMS: return ("2 Node LMS"); break; + case TLV_DECISION_ALGORITHM_TYPE_LMS: return ("LMS"); break; + } + + return ("Unknown algorithm"); +} + +const char * +tlv_heuristics_to_str(enum tlv_heuristics heuristics) +{ + + switch (heuristics) { + case TLV_HEURISTICS_UNDEFINED: return ("Undefined"); break; + case TLV_HEURISTICS_PASS: return ("Pass"); break; + case TLV_HEURISTICS_FAIL: return ("Fail"); break; + } + + return ("Unknown heuristics type"); +} + +int +tlv_heuristics_cmp(enum tlv_heuristics h1, enum tlv_heuristics h2) +{ + int res; + + res = -2; + + switch (h1) { + case TLV_HEURISTICS_UNDEFINED: + switch (h2) { + case TLV_HEURISTICS_UNDEFINED: res = 0; break; + case TLV_HEURISTICS_PASS: res = -1; break; + case TLV_HEURISTICS_FAIL: res = 1; break; + } + break; + case TLV_HEURISTICS_PASS: + switch (h2) { + case TLV_HEURISTICS_UNDEFINED: res = 1; break; + case TLV_HEURISTICS_PASS: res = 0; break; + case TLV_HEURISTICS_FAIL: res = 1; break; + } + break; + case TLV_HEURISTICS_FAIL: + switch (h2) { + case TLV_HEURISTICS_UNDEFINED: res = -1; break; + case TLV_HEURISTICS_PASS: res = -1; break; + case TLV_HEURISTICS_FAIL: res = 0; break; + } + break; + } + + assert(res == -1 || res == 0 || res == 1); + + return (res); +} diff --git a/qdevices/tlv.h b/qdevices/tlv.h new file mode 100644 index 0000000..586421c --- /dev/null +++ b/qdevices/tlv.h @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TLV_H_ +#define _TLV_H_ + +#include +#include + +#include "dynar.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum tlv_opt_type { + TLV_OPT_MSG_SEQ_NUMBER = 0, + TLV_OPT_CLUSTER_NAME = 1, + TLV_OPT_TLS_SUPPORTED = 2, + TLV_OPT_TLS_CLIENT_CERT_REQUIRED = 3, + TLV_OPT_SUPPORTED_MESSAGES = 4, + TLV_OPT_SUPPORTED_OPTIONS = 5, + TLV_OPT_REPLY_ERROR_CODE = 6, + TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE = 7, + TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE = 8, + TLV_OPT_NODE_ID = 9, + TLV_OPT_SUPPORTED_DECISION_ALGORITHMS = 10, + TLV_OPT_DECISION_ALGORITHM = 11, + TLV_OPT_HEARTBEAT_INTERVAL = 12, + TLV_OPT_RING_ID = 13, + TLV_OPT_CONFIG_VERSION = 14, + TLV_OPT_DATA_CENTER_ID = 15, + TLV_OPT_NODE_STATE = 16, + TLV_OPT_NODE_INFO = 17, + TLV_OPT_NODE_LIST_TYPE = 18, + TLV_OPT_VOTE = 19, + TLV_OPT_QUORATE = 20, + TLV_OPT_TIE_BREAKER = 21, + TLV_OPT_HEURISTICS = 22, +}; + +enum tlv_tls_supported { + TLV_TLS_UNSUPPORTED = 0, + TLV_TLS_SUPPORTED = 1, + TLV_TLS_REQUIRED = 2, +}; + +enum tlv_reply_error_code { + TLV_REPLY_ERROR_CODE_NO_ERROR = 0, + TLV_REPLY_ERROR_CODE_UNSUPPORTED_NEEDED_MESSAGE = 1, + TLV_REPLY_ERROR_CODE_UNSUPPORTED_NEEDED_OPTION = 2, + TLV_REPLY_ERROR_CODE_TLS_REQUIRED = 3, + TLV_REPLY_ERROR_CODE_UNSUPPORTED_MESSAGE = 4, + TLV_REPLY_ERROR_CODE_MESSAGE_TOO_LONG = 5, + TLV_REPLY_ERROR_CODE_PREINIT_REQUIRED = 6, + TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION = 7, + TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE = 8, + TLV_REPLY_ERROR_CODE_ERROR_DECODING_MSG = 9, + TLV_REPLY_ERROR_CODE_INTERNAL_ERROR = 10, + TLV_REPLY_ERROR_CODE_INIT_REQUIRED = 11, + TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM = 12, + TLV_REPLY_ERROR_CODE_INVALID_HEARTBEAT_INTERVAL = 13, + TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM_MESSAGE = 14, + TLV_REPLY_ERROR_CODE_TIE_BREAKER_DIFFERS_FROM_OTHER_NODES = 15, + TLV_REPLY_ERROR_CODE_ALGORITHM_DIFFERS_FROM_OTHER_NODES = 16, + TLV_REPLY_ERROR_CODE_DUPLICATE_NODE_ID = 17, + TLV_REPLY_ERROR_CODE_INVALID_CONFIG_NODE_LIST = 18, + TLV_REPLY_ERROR_CODE_INVALID_MEMBERSHIP_NODE_LIST = 19, +}; + +enum tlv_decision_algorithm_type { + TLV_DECISION_ALGORITHM_TYPE_TEST = 0, + TLV_DECISION_ALGORITHM_TYPE_FFSPLIT = 1, + TLV_DECISION_ALGORITHM_TYPE_2NODELMS = 2, + TLV_DECISION_ALGORITHM_TYPE_LMS = 3, +}; + +struct tlv_ring_id { + uint32_t node_id; + uint64_t seq; +}; + +enum tlv_node_state { + TLV_NODE_STATE_NOT_SET = 0, + TLV_NODE_STATE_MEMBER = 1, + TLV_NODE_STATE_DEAD = 2, + TLV_NODE_STATE_LEAVING = 3, +}; + +enum tlv_node_list_type { + TLV_NODE_LIST_TYPE_INITIAL_CONFIG = 0, + TLV_NODE_LIST_TYPE_CHANGED_CONFIG = 1, + TLV_NODE_LIST_TYPE_MEMBERSHIP = 2, + TLV_NODE_LIST_TYPE_QUORUM = 3, +}; + +enum tlv_vote { + TLV_VOTE_UNDEFINED = 0, + TLV_VOTE_ACK = 1, + TLV_VOTE_NACK = 2, + TLV_VOTE_ASK_LATER = 3, + TLV_VOTE_WAIT_FOR_REPLY = 4, + TLV_VOTE_NO_CHANGE = 5, +}; + +enum tlv_quorate { + TLV_QUORATE_INQUORATE = 0, + TLV_QUORATE_QUORATE = 1, +}; + +enum tlv_tie_breaker_mode { + TLV_TIE_BREAKER_MODE_LOWEST = 1, + TLV_TIE_BREAKER_MODE_HIGHEST = 2, + TLV_TIE_BREAKER_MODE_NODE_ID = 3, +}; + +struct tlv_tie_breaker { + enum tlv_tie_breaker_mode mode; + uint32_t node_id; +}; + +struct tlv_node_info { + uint32_t node_id; + uint32_t data_center_id; /* 0 - data center id was not set */ + enum tlv_node_state node_state; /* TLV_NODE_STATE_NOT_SET - state was not set */ +}; + +enum tlv_heuristics { + TLV_HEURISTICS_UNDEFINED = 0, + TLV_HEURISTICS_PASS = 1, + TLV_HEURISTICS_FAIL = 2, +}; + +struct tlv_iterator { + const char *msg; + size_t msg_len; + size_t current_pos; + size_t msg_header_len; + int iter_next_called; +}; + +extern int tlv_add(struct dynar *msg, enum tlv_opt_type opt_type, + uint16_t opt_len, const void *value); + +extern int tlv_add_u32(struct dynar *msg, enum tlv_opt_type opt_type, + uint32_t u32); + +extern int tlv_add_u8(struct dynar *msg, enum tlv_opt_type opt_type, + uint8_t u8); + +extern int tlv_add_u16(struct dynar *msg, enum tlv_opt_type opt_type, + uint16_t u16); + +extern int tlv_add_u64(struct dynar *msg, enum tlv_opt_type opt_type, + uint64_t u64); + +extern int tlv_add_string(struct dynar *msg, enum tlv_opt_type opt_type, + const char *str); + +extern int tlv_add_u16_array(struct dynar *msg, enum tlv_opt_type opt_type, + const uint16_t *array, size_t array_size); + +extern int tlv_add_supported_options(struct dynar *msg, + const enum tlv_opt_type *supported_options, size_t no_supported_options); + +extern int tlv_add_msg_seq_number(struct dynar *msg, + uint32_t msg_seq_number); + +extern int tlv_add_cluster_name(struct dynar *msg, const char *cluster_name); + +extern int tlv_add_tls_supported(struct dynar *msg, + enum tlv_tls_supported tls_supported); + +extern int tlv_add_tls_client_cert_required(struct dynar *msg, + int tls_client_cert_required); + +extern int tlv_add_reply_error_code(struct dynar *msg, + enum tlv_reply_error_code error_code); + +extern int tlv_add_node_id(struct dynar *msg, uint32_t node_id); + +extern int tlv_add_server_maximum_request_size(struct dynar *msg, + size_t server_maximum_request_size); + +extern int tlv_add_server_maximum_reply_size(struct dynar *msg, + size_t server_maximum_reply_size); + +extern int tlv_add_supported_decision_algorithms(struct dynar *msg, + const enum tlv_decision_algorithm_type *supported_algorithms, size_t no_supported_algorithms); + +extern int tlv_add_decision_algorithm(struct dynar *msg, + enum tlv_decision_algorithm_type decision_algorithm); + +extern int tlv_add_heartbeat_interval(struct dynar *msg, + uint32_t heartbeat_interval); + +extern int tlv_add_ring_id(struct dynar *msg, + const struct tlv_ring_id *ring_id); + +extern int tlv_add_tie_breaker(struct dynar *msg, + const struct tlv_tie_breaker *tie_breaker); + +extern int tlv_add_config_version(struct dynar *msg, + uint64_t config_version); + +extern int tlv_add_data_center_id(struct dynar *msg, + uint32_t data_center_id); + +extern int tlv_add_node_state(struct dynar *msg, + enum tlv_node_state node_state); + +extern int tlv_add_node_info(struct dynar *msg, + const struct tlv_node_info *node_info); + +extern int tlv_add_node_list_type(struct dynar *msg, + enum tlv_node_list_type node_list_type); + +extern int tlv_add_vote(struct dynar *msg, enum tlv_vote vote); + +extern int tlv_add_quorate(struct dynar *msg, enum tlv_quorate quorate); + +extern int tlv_add_heuristics(struct dynar *msg, + enum tlv_heuristics heuristics); + +extern void tlv_iter_init_str(const char *msg, size_t msg_len, + size_t msg_header_len, struct tlv_iterator *tlv_iter); + +extern void tlv_iter_init(const struct dynar *msg, size_t msg_header_len, + struct tlv_iterator *tlv_iter); + +extern enum tlv_opt_type tlv_iter_get_type(const struct tlv_iterator *tlv_iter); + +extern uint16_t tlv_iter_get_len(const struct tlv_iterator *tlv_iter); + +extern const char *tlv_iter_get_data(const struct tlv_iterator *tlv_iter); + +extern int tlv_iter_next(struct tlv_iterator *tlv_iter); + +extern int tlv_iter_decode_u8(struct tlv_iterator *tlv_iter, uint8_t *res); + +extern int tlv_iter_decode_tls_supported(struct tlv_iterator *tlv_iter, + enum tlv_tls_supported *tls_supported); + +extern int tlv_iter_decode_u32(struct tlv_iterator *tlv_iter, + uint32_t *res); + +extern int tlv_iter_decode_str(struct tlv_iterator *tlv_iter, char **str, + size_t *str_len); + +extern int tlv_iter_decode_client_cert_required( + struct tlv_iterator *tlv_iter, uint8_t *client_cert_required); + +extern int tlv_iter_decode_u16_array(struct tlv_iterator *tlv_iter, + uint16_t **u16a, size_t *no_items); + +extern int tlv_iter_decode_supported_options(struct tlv_iterator *tlv_iter, + enum tlv_opt_type **supported_options, size_t *no_supported_options); + +extern int tlv_iter_decode_supported_decision_algorithms( + struct tlv_iterator *tlv_iter, + enum tlv_decision_algorithm_type **supported_decision_algorithms, + size_t *no_supported_decision_algorithms); + +extern int tlv_iter_decode_u16(struct tlv_iterator *tlv_iter, + uint16_t *u16); + +extern int tlv_iter_decode_u64(struct tlv_iterator *tlv_iter, + uint64_t *u64); + +extern int tlv_iter_decode_reply_error_code(struct tlv_iterator *tlv_iter, + enum tlv_reply_error_code *reply_error_code); + +extern int tlv_iter_decode_decision_algorithm(struct tlv_iterator *tlv_iter, + enum tlv_decision_algorithm_type *decision_algorithm); + +extern int tlv_iter_decode_ring_id(struct tlv_iterator *tlv_iter, + struct tlv_ring_id *ring_id); + +extern int tlv_iter_decode_tie_breaker(struct tlv_iterator *tlv_iter, + struct tlv_tie_breaker *tie_breaker); + +extern int tlv_iter_decode_node_state(struct tlv_iterator *tlv_iter, + enum tlv_node_state *node_state); + +extern int tlv_iter_decode_node_info(struct tlv_iterator *tlv_iter, + struct tlv_node_info *node_info); + +extern int tlv_iter_decode_node_list_type(struct tlv_iterator *tlv_iter, + enum tlv_node_list_type *node_list_type); + +extern int tlv_iter_decode_vote(struct tlv_iterator *tlv_iter, + enum tlv_vote *vote); + +extern int tlv_iter_decode_quorate(struct tlv_iterator *tlv_iter, + enum tlv_quorate *quorate); + +extern int tlv_iter_decode_heuristics(struct tlv_iterator *tlv_iter, + enum tlv_heuristics *heuristics); + +extern void tlv_get_supported_options(enum tlv_opt_type **supported_options, + size_t *no_supported_options); + +extern int tlv_ring_id_eq(const struct tlv_ring_id *rid1, + const struct tlv_ring_id *rid2); + +extern int tlv_tie_breaker_eq(const struct tlv_tie_breaker *tb1, + const struct tlv_tie_breaker *tb2); + +extern const char *tlv_vote_to_str(enum tlv_vote vote); + +extern const char *tlv_node_state_to_str(enum tlv_node_state state); + +extern const char *tlv_tls_supported_to_str(enum tlv_tls_supported tls_supported); + +extern const char *tlv_decision_algorithm_type_to_str( + enum tlv_decision_algorithm_type algorithm); + +extern const char *tlv_heuristics_to_str(enum tlv_heuristics heuristics); + +/* + * Compare h1 and h2. Return -1 if h1 < h2, 0 if h1 == h2 and 1 if h1 > h2 + */ +extern int tlv_heuristics_cmp(enum tlv_heuristics h1, enum tlv_heuristics h2); + +#ifdef __cplusplus +} +#endif + +#endif /* _TLV_H_ */ diff --git a/qdevices/unix-socket-client-list.c b/qdevices/unix-socket-client-list.c new file mode 100644 index 0000000..3c31089 --- /dev/null +++ b/qdevices/unix-socket-client-list.c @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "unix-socket-client-list.h" + +void +unix_socket_client_list_init(struct unix_socket_client_list *client_list) +{ + + TAILQ_INIT(client_list); +} + +struct unix_socket_client * +unix_socket_client_list_add(struct unix_socket_client_list *client_list, + int sock, size_t max_receive_size, size_t max_send_size, void *user_data) +{ + struct unix_socket_client *client; + + client = (struct unix_socket_client *)malloc(sizeof(*client)); + if (client == NULL) { + return (NULL); + } + + unix_socket_client_init(client, sock, max_receive_size, max_send_size, user_data); + + TAILQ_INSERT_TAIL(client_list, client, entries); + + return (client); +} + +void +unix_socket_client_list_free(struct unix_socket_client_list *client_list) +{ + struct unix_socket_client *client; + struct unix_socket_client *client_next; + + client = TAILQ_FIRST(client_list); + + while (client != NULL) { + client_next = TAILQ_NEXT(client, entries); + + unix_socket_client_destroy(client); + free(client); + + client = client_next; + } + + TAILQ_INIT(client_list); +} + +void +unix_socket_client_list_del(struct unix_socket_client_list *client_list, + struct unix_socket_client *client) +{ + + TAILQ_REMOVE(client_list, client, entries); + unix_socket_client_destroy(client); + free(client); +} + +size_t +unix_socket_client_list_no_clients(struct unix_socket_client_list *client_list) +{ + size_t res; + struct unix_socket_client *client; + + res = 0; + + TAILQ_FOREACH(client, client_list, entries) { + res++; + } + + return (res); +} diff --git a/qdevices/unix-socket-client-list.h b/qdevices/unix-socket-client-list.h new file mode 100644 index 0000000..c5a482d --- /dev/null +++ b/qdevices/unix-socket-client-list.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _UNIX_SOCKET_CLIENT_LIST_H_ +#define _UNIX_SOCKET_CLIENT_LIST_H_ + +#include +#include + +#include "unix-socket-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +TAILQ_HEAD(unix_socket_client_list, unix_socket_client); + +extern void unix_socket_client_list_init( + struct unix_socket_client_list *client_list); + +extern struct unix_socket_client *unix_socket_client_list_add( + struct unix_socket_client_list *client_list, + int sock, size_t max_receive_size, size_t max_send_size, void *user_data); + +extern void unix_socket_client_list_free( + struct unix_socket_client_list *client_list); + +extern void unix_socket_client_list_del( + struct unix_socket_client_list *client_list, struct unix_socket_client *client); + +extern size_t unix_socket_client_list_no_clients( + struct unix_socket_client_list *client_list); + +#ifdef __cplusplus +} +#endif + +#endif /* _UNIX_SOCKET_CLIENT_LIST_H_ */ diff --git a/qdevices/unix-socket-client.c b/qdevices/unix-socket-client.c new file mode 100644 index 0000000..10235de --- /dev/null +++ b/qdevices/unix-socket-client.c @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "unix-socket-client.h" +#include "unix-socket.h" + +#define UNIX_SOCKET_CLIENT_BUFFER 256 + +void +unix_socket_client_init(struct unix_socket_client *client, int sock, size_t max_receive_size, + size_t max_send_size, void *user_data) +{ + + memset(client, 0, sizeof(*client)); + client->socket = sock; + client->user_data = user_data; + dynar_init(&client->receive_buffer, max_receive_size); + dynar_init(&client->send_buffer, max_send_size); +} + +void +unix_socket_client_destroy(struct unix_socket_client *client) +{ + + dynar_destroy(&client->send_buffer); + dynar_destroy(&client->receive_buffer); +} + +void +unix_socket_client_read_line(struct unix_socket_client *client, int enabled) +{ + + client->reading_line = enabled; +} + +void +unix_socket_client_write_buffer(struct unix_socket_client *client, int enabled) +{ + + client->writing_buffer = enabled; +} + +/* + * 1 Full line readed + * 0 Partial read (no error) + * -1 End of connection + * -2 Buffer too long + * -3 Unhandled error + */ +int +unix_socket_client_io_read(struct unix_socket_client *client) +{ + char buf[UNIX_SOCKET_CLIENT_BUFFER]; + ssize_t readed; + int res; + size_t zi; + + res = 0; + readed = unix_socket_read(client->socket, buf, sizeof(buf)); + if (readed > 0) { + client->msg_already_received_bytes += readed; + if (dynar_cat(&client->receive_buffer, buf, readed) == -1) { + res = -2; + goto exit_err; + } + + for (zi = 0; zi < (size_t)readed; zi++) { + if (buf[zi] == '\n') { + res = 1; + } + } + } + + if (readed == 0) { + res = -1; + } + + if (readed < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { + res = -3; + } + +exit_err: + return (res); +} + +/* + * 1 All data succesfully sent + * 0 Partial send (no error) + * -1 End of connection + * -2 Unhandled error + */ +int +unix_socket_client_io_write(struct unix_socket_client *client) +{ + ssize_t sent; + size_t to_send; + int res; + + res = 0; + + to_send = dynar_size(&client->send_buffer) - client->msg_already_sent_bytes; + if (to_send > UNIX_SOCKET_CLIENT_BUFFER) { + to_send = UNIX_SOCKET_CLIENT_BUFFER; + } + + sent = unix_socket_write(client->socket, + dynar_data(&client->send_buffer) + client->msg_already_sent_bytes, + to_send); + + if (sent > 0) { + client->msg_already_sent_bytes += sent; + + if (client->msg_already_sent_bytes == dynar_size(&client->send_buffer)) { + return (1); + } + } + + if (sent == 0) { + res = -1; + } + + if (sent < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { + res = -2; + } + + return (res); +} diff --git a/qdevices/unix-socket-client.h b/qdevices/unix-socket-client.h new file mode 100644 index 0000000..6a758ff --- /dev/null +++ b/qdevices/unix-socket-client.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _UNIX_SOCKET_CLIENT_H_ +#define _UNIX_SOCKET_CLIENT_H_ + +#include + +#include +#include + +#include "dynar.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct unix_socket_client { + int socket; + struct dynar receive_buffer; + struct dynar send_buffer; + size_t msg_already_received_bytes; + size_t msg_already_sent_bytes; + int reading_line; + int writing_buffer; + int schedule_disconnect; + void *user_data; + TAILQ_ENTRY(unix_socket_client) entries; +}; + +extern void unix_socket_client_init(struct unix_socket_client *client, int sock, + size_t max_receive_size, size_t max_send_size, void *user_data); + +extern void unix_socket_client_destroy(struct unix_socket_client *client); + +extern void unix_socket_client_read_line(struct unix_socket_client *client, int enabled); + +extern void unix_socket_client_write_buffer(struct unix_socket_client *client, int enabled); + +extern int unix_socket_client_io_read(struct unix_socket_client *client); + +extern int unix_socket_client_io_write(struct unix_socket_client *client); + +#ifdef __cplusplus +} +#endif + +#endif /* _UNIX_SOCKET_CLIENT_H_ */ diff --git a/qdevices/unix-socket-ipc.c b/qdevices/unix-socket-ipc.c new file mode 100644 index 0000000..a06dd10 --- /dev/null +++ b/qdevices/unix-socket-ipc.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "unix-socket.h" +#include "unix-socket-ipc.h" + +int +unix_socket_ipc_init(struct unix_socket_ipc *ipc, const char *socket_file_name, int backlog, + size_t max_clients, size_t max_receive_size, size_t max_send_size) +{ + + memset(ipc, 0, sizeof(*ipc)); + + ipc->socket_file_name = strdup(socket_file_name); + if (ipc->socket_file_name == NULL) { + return (-1); + } + + unix_socket_client_list_init(&ipc->clients); + + ipc->backlog = backlog; + ipc->socket = unix_socket_server_create(ipc->socket_file_name, 1, + backlog); + if (ipc->socket < 0) { + free(ipc->socket_file_name); + return (-1); + } + + ipc->max_clients = max_clients; + ipc->max_receive_size = max_receive_size; + ipc->max_send_size = max_send_size; + + return (0); +} + +int +unix_socket_ipc_close(struct unix_socket_ipc *ipc) +{ + int res; + + res = 0; + + if (ipc->socket < 0) { + return (0); + } + + if (unix_socket_server_destroy(ipc->socket, ipc->socket_file_name) != 0) { + res = -1; + } + + free(ipc->socket_file_name); + ipc->socket_file_name = NULL; + + ipc->socket = -1; + + return (res); +} + +int +unix_socket_ipc_is_closed(struct unix_socket_ipc *ipc) +{ + + return (ipc->socket < 0); +} + +int +unix_socket_ipc_destroy(struct unix_socket_ipc *ipc) +{ + + if (unix_socket_ipc_close(ipc) != 0) { + return (-1); + } + + unix_socket_client_list_free(&ipc->clients); + + return (0); +} + +/* + * 0 = No error + * -1 = Can't accept connection (errno set) + * -2 = Too much clients + * -3 = Can't add client to list + */ +int +unix_socket_ipc_accept(struct unix_socket_ipc *ipc, struct unix_socket_client **res_client) +{ + int client_sock; + struct unix_socket_client *client; + + if ((client_sock = unix_socket_server_accept(ipc->socket, 1)) < 0) { + return (-1); + } + + if (ipc->max_clients != 0 && + unix_socket_client_list_no_clients(&ipc->clients) >= ipc->max_clients) { + unix_socket_close(client_sock); + + return (-2); + } + + client = unix_socket_client_list_add(&ipc->clients, client_sock, ipc->max_receive_size, + ipc->max_send_size, NULL); + if (client == NULL) { + unix_socket_close(client_sock); + + return (-3); + } + + *res_client = client; + + return (0); +} + +void +unix_socket_ipc_client_disconnect(struct unix_socket_ipc *ipc, struct unix_socket_client *client) +{ + + unix_socket_close(client->socket); + unix_socket_client_list_del(&ipc->clients, client); +} diff --git a/qdevices/unix-socket-ipc.h b/qdevices/unix-socket-ipc.h new file mode 100644 index 0000000..540cc3a --- /dev/null +++ b/qdevices/unix-socket-ipc.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _UNIX_SOCKET_IPC_H_ +#define _UNIX_SOCKET_IPC_H_ + +#include "unix-socket-client.h" +#include "unix-socket-client-list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct unix_socket_ipc { + int socket; + int backlog; + char *socket_file_name; + struct unix_socket_client_list clients; + size_t max_clients; + size_t max_receive_size; + size_t max_send_size; +}; + +extern int unix_socket_ipc_init(struct unix_socket_ipc *ipc, + const char *socket_file_name, int backlog, size_t max_clients, size_t max_receive_size, + size_t max_send_size); + +extern int unix_socket_ipc_destroy(struct unix_socket_ipc *ipc); + +extern int unix_socket_ipc_accept(struct unix_socket_ipc *ipc, + struct unix_socket_client **res_client); + +void unix_socket_ipc_client_disconnect(struct unix_socket_ipc *ipc, + struct unix_socket_client *client); + +extern int unix_socket_ipc_close(struct unix_socket_ipc *ipc); + +extern int unix_socket_ipc_is_closed(struct unix_socket_ipc *ipc); + +#ifdef __cplusplus +} +#endif + +#endif /* _UNIX_SOCKET_IPC_H_ */ diff --git a/qdevices/unix-socket.c b/qdevices/unix-socket.c new file mode 100644 index 0000000..464eae7 --- /dev/null +++ b/qdevices/unix-socket.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include + +#include "unix-socket.h" +#include "utils.h" + +int +unix_socket_server_create(const char *path, int non_blocking, int backlog) +{ + int s; + struct sockaddr_un sun; + + if (strlen(path) >= sizeof(sun.sun_path)) { + errno = ENAMETOOLONG; + return (-1); + } + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + return (-1); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + + strncpy(sun.sun_path, path, strlen(path)); + unlink(path); + if (bind(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) != 0) { + close(s); + + return (-1); + } + + if (non_blocking) { + if (utils_fd_set_non_blocking(s) != 0) { + close(s); + + return (-1); + } + } + + if (listen(s, backlog) != 0) { + close(s); + + return (-1); + } + + return (s); +} + +int +unix_socket_client_create(const char *path, int non_blocking) +{ + int s; + struct sockaddr_un sun; + + if (strlen(path) >= sizeof(sun.sun_path)) { + errno = ENAMETOOLONG; + return (-1); + } + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + return (-1); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + + strncpy(sun.sun_path, path, strlen(path)); + + if (non_blocking) { + if (utils_fd_set_non_blocking(s) != 0) { + close(s); + + return (-1); + } + } + + if (connect(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) != 0) { + close(s); + + return (-1); + } + + return (s); +} + + + +int +unix_socket_server_destroy(int sock, const char *path) +{ + int res; + + res = 0; + + if (close(sock) != 0) { + res = -1; + } + + if (unlink(path) != 0) { + res = -1; + } + + return (res); +} + +int +unix_socket_server_accept(int sock, int non_blocking) +{ + struct sockaddr_un sun; + socklen_t sun_len; + int client_sock; + + sun_len = sizeof(sun); + if ((client_sock = accept(sock, (struct sockaddr *)&sun, &sun_len)) < 0) { + return (-1); + } + + if (non_blocking) { + if (utils_fd_set_non_blocking(client_sock) != 0) { + close(client_sock); + + return (-1); + } + } + + return (client_sock); +} + +int +unix_socket_close(int sock) +{ + + return (close(sock)); +} + +ssize_t +unix_socket_read(int sock, void *buf, size_t len) +{ + + return (recv(sock, buf, len, 0)); +} + +ssize_t +unix_socket_write(int sock, void *buf, size_t len) +{ + + return (send(sock, buf, len, 0)); +} diff --git a/qdevices/unix-socket.h b/qdevices/unix-socket.h new file mode 100644 index 0000000..8583feb --- /dev/null +++ b/qdevices/unix-socket.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _UNIX_SOCKET_H_ +#define _UNIX_SOCKET_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern int unix_socket_server_create(const char *path, int non_blocking, int backlog); + +extern int unix_socket_client_create(const char *path, int non_blocking); + +extern int unix_socket_server_destroy(int sock, const char *path); + +extern int unix_socket_server_accept(int sock, int non_blocking); + +extern int unix_socket_close(int sock); + +extern ssize_t unix_socket_read(int sock, void *buf, size_t len); + +extern ssize_t unix_socket_write(int sock, void *buf, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* _UNIX_SOCKET_H_ */ diff --git a/qdevices/utils.c b/qdevices/utils.c new file mode 100644 index 0000000..9574c96 --- /dev/null +++ b/qdevices/utils.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +/* + * Check string to value on, off, yes, no, 0, 1. Return 1 if value is on, yes or 1, 0 if + * value is off, no or 0 and -1 otherwise. + */ +int +utils_parse_bool_str(const char *str) +{ + + if (strcasecmp(str, "yes") == 0 || + strcasecmp(str, "on") == 0 || + strcasecmp(str, "1") == 0) { + return (1); + } else if (strcasecmp(str, "no") == 0 || + strcasecmp(str, "off") == 0 || + strcasecmp(str, "0") == 0) { + return (0); + } + + return (-1); +} + +int +utils_flock(const char *lockfile, pid_t pid, int *another_instance_running) +{ + struct flock lock; + char pid_s[17]; + int fd_flag; + int lf; + char *dname; + + *another_instance_running = 0; + + /* + * lockfile directory may not exists. Creation of directory should + * be handled by initscript/tmpfiles.d. But as a last chance it + * make sense to try to create it here. + */ + dname = strdup(lockfile); + if (dname != NULL) { + (void)mkdir(dirname(dname), 0770); + free(dname); + } + + lf = open(lockfile, O_WRONLY | O_CREAT, 0640); + if (lf == -1) { + return (-1); + } + +retry_fcntl: + lock.l_type = F_WRLCK; + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + if (fcntl(lf, F_SETLK, &lock) == -1) { + switch (errno) { + case EINTR: + goto retry_fcntl; + break; + case EAGAIN: + case EACCES: + *another_instance_running = 1; + goto error_close; + break; + default: + goto error_close; + break; + } + } + + if (ftruncate(lf, 0) == -1) { + goto error_close_unlink; + } + + memset(pid_s, 0, sizeof(pid_s)); + snprintf(pid_s, sizeof(pid_s) - 1, "%u\n", pid); + +retry_write: + if (write(lf, pid_s, strlen(pid_s)) != (ssize_t)strlen(pid_s)) { + if (errno == EINTR) { + goto retry_write; + } else { + goto error_close_unlink; + } + } + + if ((fd_flag = fcntl(lf, F_GETFD, 0)) == -1) { + goto error_close_unlink; + } + fd_flag |= FD_CLOEXEC; + if (fcntl(lf, F_SETFD, fd_flag) == -1) { + goto error_close_unlink; + } + + return (lf); + +error_close_unlink: + unlink(lockfile); +error_close: + close(lf); + + return (-1); +} + +void +utils_tty_detach(void) +{ + int devnull; + + switch (fork()) { + case -1: + err(1, "Can't create child process"); + break; + case 0: + break; + default: + exit(0); + break; + } + + /* Create new session */ + (void)setsid(); + + /* + * Map stdin/out/err to /dev/null. + */ + devnull = open("/dev/null", O_RDWR); + if (devnull == -1) { + err(1, "Can't open /dev/null"); + } + + if (dup2(devnull, 0) < 0 || dup2(devnull, 1) < 0 + || dup2(devnull, 2) < 0) { + close(devnull); + err(1, "Can't dup2 stdin/out/err to /dev/null"); + } + close(devnull); +} + +int +utils_fd_set_non_blocking(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL, NULL); + + if (flags < 0) { + return (-1); + } + + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) { + return (-1); + } + + return (0); +} diff --git a/qdevices/utils.h b/qdevices/utils.h new file mode 100644 index 0000000..fe9b5b4 --- /dev/null +++ b/qdevices/utils.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015-2016 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse (jfriesse@redhat.com) + * + * This software licensed under BSD license, the text of which follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Red Hat, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include +#include + +#define UTILS_PRI_NODE_ID "%" PRIu32 +#define UTILS_PRI_DATACENTER_ID "%" PRIu32 +/* +#define UTILS_PRI_NODE_ID "0x%" PRIx32 +#define UTILS_PRI_DATACENTER_ID "0x%" PRIx32 +*/ +#define UTILS_PRI_MSG_SEQ "%" PRIu32 +#define UTILS_PRI_RING_ID "%" PRIx32 ".%" PRIx64 +#define UTILS_PRI_CONFIG_VERSION "%" PRIu64 +#define UTILS_PRI_EXPECTED_VOTES "%" PRIu32 + +#ifdef __cplusplus +extern "C" { +#endif + +extern int utils_parse_bool_str(const char *str); + +extern int utils_flock(const char *lockfile, pid_t pid, + int *another_instance_running); + +extern void utils_tty_detach(void); + +extern int utils_fd_set_non_blocking(int fd); + +#ifdef __cplusplus +} +#endif + +#endif /* _UTILS_H_ */