summaryrefslogtreecommitdiffstats
path: root/bindings/rust
diff options
context:
space:
mode:
Diffstat (limited to 'bindings/rust')
-rw-r--r--bindings/rust/Cargo.toml.in16
-rw-r--r--bindings/rust/Makefile.am60
-rw-r--r--bindings/rust/Makefile.in836
-rw-r--r--bindings/rust/README.md20
-rw-r--r--bindings/rust/build.rs.in7
-rw-r--r--bindings/rust/src/cfg.rs348
-rw-r--r--bindings/rust/src/cmap.rs894
-rw-r--r--bindings/rust/src/cpg.rs628
-rw-r--r--bindings/rust/src/lib.rs296
-rw-r--r--bindings/rust/src/quorum.rs298
-rw-r--r--bindings/rust/src/sys/mod.rs7
-rw-r--r--bindings/rust/src/votequorum.rs501
-rw-r--r--bindings/rust/tests/Cargo.toml.in36
-rw-r--r--bindings/rust/tests/Makefile.am26
-rw-r--r--bindings/rust/tests/Makefile.in625
-rw-r--r--bindings/rust/tests/build.rs.in22
-rw-r--r--bindings/rust/tests/src/bin/cfg-test.rs135
-rw-r--r--bindings/rust/tests/src/bin/cmap-test.rs195
-rw-r--r--bindings/rust/tests/src/bin/cpg-test.rs142
-rw-r--r--bindings/rust/tests/src/bin/quorum-test.rs83
-rw-r--r--bindings/rust/tests/src/bin/votequorum-test.rs117
21 files changed, 5292 insertions, 0 deletions
diff --git a/bindings/rust/Cargo.toml.in b/bindings/rust/Cargo.toml.in
new file mode 100644
index 0000000..d357605
--- /dev/null
+++ b/bindings/rust/Cargo.toml.in
@@ -0,0 +1,16 @@
+[package]
+name = "rust-corosync"
+version = "@corosyncrustver@"
+authors = ["Christine Caulfield <ccaulfie@redhat.com>"]
+edition = "2021"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/corosync/corosync/"
+description = "Rust bindings for corosync libraries"
+categories = ["api-bindings"]
+keywords = ["cluster", "high-availability"]
+
+[dependencies]
+lazy_static = "1.4.0"
+num_enum = "0.5.4"
+bitflags = "1.3.2"
diff --git a/bindings/rust/Makefile.am b/bindings/rust/Makefile.am
new file mode 100644
index 0000000..c22b84b
--- /dev/null
+++ b/bindings/rust/Makefile.am
@@ -0,0 +1,60 @@
+#
+# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+#
+# Author: Christine Caulfield <ccaulfie@redhat.com>
+#
+# This software licensed under GPL-2.0+
+#
+
+MAINTAINERCLEANFILES = Makefile.in
+
+include $(top_srcdir)/build-aux/rust.mk
+
+# required for make check
+localver = $(corosyncrustver)
+
+SUBDIRS = . tests
+
+EXTRA_DIST = \
+ $(RUST_COMMON) \
+ $(RUST_SHIP_SRCS) \
+ README.md
+
+RUST_SHIP_SRCS = \
+ src/cpg.rs \
+ src/cfg.rs \
+ src/quorum.rs \
+ src/votequorum.rs \
+ src/cmap.rs \
+ src/lib.rs \
+ src/sys/mod.rs
+
+RUST_BUILT_SRCS = \
+ src/sys/cpg.rs \
+ src/sys/cfg.rs \
+ src/sys/quorum.rs \
+ src/sys/votequorum.rs \
+ src/sys/cmap.rs
+
+src/sys/cpg.rs: cargo-tree-prep ../../include/corosync/cpg.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/cpg.h $@ CPG --blocklist-function=inet6.* --blocklist-function==.*etsourcefilter -- -I$(top_srcdir)/include
+
+src/sys/cfg.rs: cargo-tree-prep ../../include/corosync/cfg.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/cfg.h $@ CFG --blocklist-function=inet6.* --blocklist-function=.*etsourcefilter -- -I$(top_srcdir)/include
+
+src/sys/quorum.rs: cargo-tree-prep ../../include/corosync/quorum.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/quorum.h $@ QUORUM -- -I$(top_srcdir)/include
+
+src/sys/votequorum.rs: cargo-tree-prep ../../include/corosync/votequorum.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/votequorum.h $@ VOTEQUORUM -- -I$(top_srcdir)/include
+
+src/sys/cmap.rs: cargo-tree-prep ../../include/corosync/cmap.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/cmap.h $@ CMAP -- -I$(top_srcdir)/include $(LIBQB_CFLAGS)
+
+all-local: target/$(RUST_TARGET_DIR)/cpg.rlib \
+ target/$(RUST_TARGET_DIR)/cfg.rlib \
+ target/$(RUST_TARGET_DIR)/quorum.rlib \
+ target/$(RUST_TARGET_DIR)/votequorum.rlib \
+ target/$(RUST_TARGET_DIR)/cmap.rlib
+
+clean-local: cargo-clean
diff --git a/bindings/rust/Makefile.in b/bindings/rust/Makefile.in
new file mode 100644
index 0000000..a2a6169
--- /dev/null
+++ b/bindings/rust/Makefile.in
@@ -0,0 +1,836 @@
+# Makefile.in generated by automake 1.13.4 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+#
+# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+#
+# Author: Christine Caulfield <ccaulfie@redhat.com>
+#
+# This software licensed under GPL-2.0+
+#
+
+#
+# Copyright (C) 2021-2022 Red Hat, Inc. All rights reserved.
+#
+# Author: Fabio M. Di Nitto <fabbione@kronosnet.org>
+#
+# This software licensed under GPL-2.0+
+#
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+DIST_COMMON = $(top_srcdir)/build-aux/rust.mk $(srcdir)/Makefile.in \
+ $(srcdir)/Makefile.am $(srcdir)/Cargo.toml.in
+subdir = bindings/rust
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/lib/libcfg.verso $(top_srcdir)/lib/libcpg.verso \
+ $(top_srcdir)/lib/libquorum.verso \
+ $(top_srcdir)/lib/libsam.verso \
+ $(top_srcdir)/lib/libvotequorum.verso \
+ $(top_srcdir)/lib/libcmap.verso $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/include/corosync/config.h
+CONFIG_CLEAN_FILES = Cargo.toml
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUGTOOL = @AUGTOOL@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BASHPATH = @BASHPATH@
+BINDGEN = @BINDGEN@
+CARGO = @CARGO@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFG_SONAME = @CFG_SONAME@
+CFLAGS = @CFLAGS@
+CLIPPY = @CLIPPY@
+CMAP_SONAME = @CMAP_SONAME@
+COROSYSCONFDIR = @COROSYSCONFDIR@
+CPG_SONAME = @CPG_SONAME@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DBUS_CFLAGS = @DBUS_CFLAGS@
+DBUS_LIBS = @DBUS_LIBS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOT = @DOT@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+GROFF = @GROFF@
+INITCONFIGDIR = @INITCONFIGDIR@
+INITDDIR = @INITDDIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBQB_CFLAGS = @LIBQB_CFLAGS@
+LIBQB_LIBS = @LIBQB_LIBS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LINT_FLAGS = @LINT_FLAGS@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOGDIR = @LOGDIR@
+LOGROTATEDIR = @LOGROTATEDIR@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUORUM_SONAME = @QUORUM_SONAME@
+RANLIB = @RANLIB@
+RUSTC = @RUSTC@
+RUSTDOC = @RUSTDOC@
+RUSTFMT = @RUSTFMT@
+RUST_FLAGS = @RUST_FLAGS@
+RUST_TARGET_DIR = @RUST_TARGET_DIR@
+SAM_SONAME = @SAM_SONAME@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SNMPCONFIG = @SNMPCONFIG@
+SNMP_LIBS = @SNMP_LIBS@
+SOMAJOR = @SOMAJOR@
+SOMICRO = @SOMICRO@
+SOMINOR = @SOMINOR@
+SONAME = @SONAME@
+STRIP = @STRIP@
+SYSTEMDDIR = @SYSTEMDDIR@
+VERSCRIPT_LDFLAGS = @VERSCRIPT_LDFLAGS@
+VERSION = @VERSION@
+VOTEQUORUM_SONAME = @VOTEQUORUM_SONAME@
+WITH_LIST = @WITH_LIST@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+corosyncrustver = @corosyncrustver@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+knet_CFLAGS = @knet_CFLAGS@
+knet_LIBS = @knet_LIBS@
+libdir = @libdir@
+libexecdir = @libexecdir@
+libsystemd_CFLAGS = @libsystemd_CFLAGS@
+libsystemd_LIBS = @libsystemd_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+nozzle_CFLAGS = @nozzle_CFLAGS@
+nozzle_LIBS = @nozzle_LIBS@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+statgrab_CFLAGS = @statgrab_CFLAGS@
+statgrab_LIBS = @statgrab_LIBS@
+statgrabge090_CFLAGS = @statgrabge090_CFLAGS@
+statgrabge090_LIBS = @statgrabge090_LIBS@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+MAINTAINERCLEANFILES = Makefile.in
+RUST_COMMON = \
+ build.rs.in
+
+RUST_SRCS = $(RUST_SHIP_SRCS) $(RUST_BUILT_SRCS)
+
+# required for make check
+localver = $(corosyncrustver)
+SUBDIRS = . tests
+EXTRA_DIST = \
+ $(RUST_COMMON) \
+ $(RUST_SHIP_SRCS) \
+ README.md
+
+RUST_SHIP_SRCS = \
+ src/cpg.rs \
+ src/cfg.rs \
+ src/quorum.rs \
+ src/votequorum.rs \
+ src/cmap.rs \
+ src/lib.rs \
+ src/sys/mod.rs
+
+RUST_BUILT_SRCS = \
+ src/sys/cpg.rs \
+ src/sys/cfg.rs \
+ src/sys/quorum.rs \
+ src/sys/votequorum.rs \
+ src/sys/cmap.rs
+
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/build-aux/rust.mk $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign bindings/rust/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign bindings/rust/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+$(top_srcdir)/build-aux/rust.mk:
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+Cargo.toml: $(top_builddir)/config.status $(srcdir)/Cargo.toml.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-recursive
+all-am: Makefile all-local
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES)
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-local mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am all-local \
+ check check-am check-local clean clean-generic clean-libtool \
+ clean-local cscopelist-am ctags ctags-am distclean \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+
+%.rlib: $(RUST_SRCS) Cargo.toml build.rs
+ PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(CARGO) build $(RUST_FLAGS)
+
+%-test: $(RUST_SRCS) Cargo.toml build.rs
+ PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(CARGO) build $(RUST_FLAGS)
+
+build.rs: build.rs.in
+ rm -f $@ $@-t
+ cat $^ | sed \
+ -e 's#@ABSTOPLEVELSRC@#$(abs_top_srcdir)#g' \
+ -e 's#@ABSTOPLEVELBUILD@#$(abs_top_builddir)#g' \
+ -e 's#@LIBQBLIBS@#$(LIBQB_LIBS)#g' \
+ > $@-t
+ chmod a-w $@-t
+ mv $@-t $@
+ rm -f $@-t
+
+cargo-tree-prep:
+ if [ "${abs_builddir}" != "${abs_srcdir}" ]; then \
+ echo "Generating builddir out-of-tree rust symlinks"; \
+ src_realpath=$(shell realpath ${abs_srcdir}); \
+ for i in `find "$$src_realpath/" -type d | \
+ grep -v "${abs_builddir}" | \
+ sed -e 's#^'$$src_realpath'/##g'`; do \
+ $(MKDIR_P) ${abs_builddir}/$${i}; \
+ done; \
+ find "$$src_realpath/" -type f | { while read src; do \
+ process=no; \
+ copy=no; \
+ case $$src in \
+ ${abs_builddir}*) \
+ ;; \
+ *Makefile.*|*.in) \
+ ;; \
+ *) \
+ process=yes; \
+ ;; \
+ esac ; \
+ dst=`echo $$src | sed -e 's#^'$$src_realpath'/##g'`; \
+ if [ $${process} == yes ]; then \
+ rm -f ${abs_builddir}/$$dst; \
+ $(LN_S) $$src ${abs_builddir}/$$dst; \
+ fi; \
+ if [ $${copy} == yes ]; then \
+ rm -f ${abs_builddir}/$$dst; \
+ cp $$src ${abs_builddir}/$$dst; \
+ chmod u+w ${abs_builddir}/$$dst; \
+ fi; \
+ done; }; \
+ fi
+
+cargo-clean:
+ -$(CARGO) clean
+ rm -rf Cargo.lock $(RUST_BUILT_SRCS) build.rs target/
+ if [ "${abs_builddir}" != "${abs_srcdir}" ]; then \
+ echo "Cleaning out-of-tree rust symlinks" ; \
+ find "${abs_builddir}/" -type l -delete; \
+ find "${abs_builddir}/" -type d -empty -delete; \
+ fi
+
+clippy-check:
+ $(CARGO) clippy --verbose --all-features -- -D warnings
+
+format-check:
+ if [ "${abs_builddir}" = "${abs_srcdir}" ]; then \
+ $(CARGO) fmt --all --check; \
+ else \
+ echo "!!!!! WARNING: skipping format check !!!!!"; \
+ fi
+
+doc-check:
+ $(CARGO) doc --verbose --all-features
+
+publish-check:
+ if [ -f "${abs_srcdir}/README.md" ]; then \
+ $(CARGO) publish --dry-run; \
+ fi
+
+crates-publish:
+ if [ -f "${abs_srcdir}/README.md" ]; then \
+ bindingname=`cat Cargo.toml | grep ^name | sed -e 's#.*= ##g' -e 's#"##g'` && \
+ cratesver=`cargo search $$bindingname | grep "^$$bindingname " | sed -e 's#.*= ##g' -e 's#"##g' -e 's/\+.*//g'` && \
+ testver=`echo $(localver) | sed -e 's/\+.*//g'` && \
+ if [ "$$cratesver" != "$$testver" ]; then \
+ $(CARGO) publish; \
+ fi; \
+ fi
+
+crates-check:
+ if [ -f "${abs_srcdir}/README.md" ]; then \
+ bindingname=`cat Cargo.toml | grep ^name | sed -e 's#.*= ##g' -e 's#"##g'` && \
+ cratesver=`cargo search $$bindingname | grep "^$$bindingname " | sed -e 's#.*= ##g' -e 's#"##g' -e 's/\+.*//g'` && \
+ testver=`echo $(localver) | sed -e 's/\+.*//g'` && \
+ if [ "$$cratesver" != "$$testver" ]; then \
+ echo "!!!!! WARNING !!!!!"; \
+ echo "!!!!! WARNING: $$bindingname local version ($$testver) is higher than the current published one on crates.io ($$cratesver)"; \
+ echo "!!!!! WARNING !!!!!"; \
+ fi; \
+ fi
+
+check-local: clippy-check format-check doc-check crates-check publish-check
+
+src/sys/cpg.rs: cargo-tree-prep ../../include/corosync/cpg.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/cpg.h $@ CPG --blocklist-function=inet6.* --blocklist-function==.*etsourcefilter -- -I$(top_srcdir)/include
+
+src/sys/cfg.rs: cargo-tree-prep ../../include/corosync/cfg.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/cfg.h $@ CFG --blocklist-function=inet6.* --blocklist-function=.*etsourcefilter -- -I$(top_srcdir)/include
+
+src/sys/quorum.rs: cargo-tree-prep ../../include/corosync/quorum.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/quorum.h $@ QUORUM -- -I$(top_srcdir)/include
+
+src/sys/votequorum.rs: cargo-tree-prep ../../include/corosync/votequorum.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/votequorum.h $@ VOTEQUORUM -- -I$(top_srcdir)/include
+
+src/sys/cmap.rs: cargo-tree-prep ../../include/corosync/cmap.h
+ $(top_srcdir)/build-aux/rust-regen.sh $(top_srcdir)/include/corosync/cmap.h $@ CMAP -- -I$(top_srcdir)/include $(LIBQB_CFLAGS)
+
+all-local: target/$(RUST_TARGET_DIR)/cpg.rlib \
+ target/$(RUST_TARGET_DIR)/cfg.rlib \
+ target/$(RUST_TARGET_DIR)/quorum.rlib \
+ target/$(RUST_TARGET_DIR)/votequorum.rlib \
+ target/$(RUST_TARGET_DIR)/cmap.rlib
+
+clean-local: cargo-clean
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/bindings/rust/README.md b/bindings/rust/README.md
new file mode 100644
index 0000000..cb2f9cc
--- /dev/null
+++ b/bindings/rust/README.md
@@ -0,0 +1,20 @@
+# rust-corosync
+Rust bindings for corosync
+
+Rust bindings for cfg, cmap, cpg, quorum, votequorum are part of this
+source tree, but are included here mainly to keep all of the
+corosync APIs in one place and to ensure that everything is kept
+up-to-date and properly tested in our CI system.
+
+The correct place to get the Rust crates for corosync
+is still crates.io as it would be for other crates. These will be
+updated when we issue a new release of corosync.
+
+https://crates.io/crates/rust-corosync
+
+Of course, if you want to try any new features in the APIs that
+may have not yet been released then you can try these sources, but
+please keep in touch with us via email or IRC if you do so.
+
+#clusterlabs or #kronosnet on libera IRC
+users@clusterlabs.org
diff --git a/bindings/rust/build.rs.in b/bindings/rust/build.rs.in
new file mode 100644
index 0000000..e7de170
--- /dev/null
+++ b/bindings/rust/build.rs.in
@@ -0,0 +1,7 @@
+fn main() {
+ println!("cargo:rustc-link-lib=cpg");
+ println!("cargo:rustc-link-lib=cfg");
+ println!("cargo:rustc-link-lib=cmap");
+ println!("cargo:rustc-link-lib=quorum");
+ println!("cargo:rustc-link-lib=votequorum");
+}
diff --git a/bindings/rust/src/cfg.rs b/bindings/rust/src/cfg.rs
new file mode 100644
index 0000000..b4eecac
--- /dev/null
+++ b/bindings/rust/src/cfg.rs
@@ -0,0 +1,348 @@
+// libcfg interface for Rust
+// Copyright (c) 2021 Red Hat, Inc.
+//
+// All rights reserved.
+//
+// Author: Christine Caulfield (ccaulfi@redhat.com)
+//
+
+// For the code generated by bindgen
+use crate::sys::cfg as ffi;
+
+use std::collections::HashMap;
+use std::ffi::CString;
+use std::os::raw::{c_int, c_void};
+use std::sync::Mutex;
+
+use crate::string_from_bytes;
+use crate::{CsError, DispatchFlags, NodeId, Result};
+
+// Used to convert a CFG handle into one of ours
+lazy_static! {
+ static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
+}
+
+/// Callback from [track_start]. Will be called if another process
+/// requests to shut down corosync. [reply_to_shutdown] should be called
+/// with a [ShutdownReply] of either Yes or No.
+#[derive(Copy, Clone)]
+pub struct Callbacks {
+ pub corosync_cfg_shutdown_callback_fn: Option<fn(handle: &Handle, flags: u32)>,
+}
+
+/// A handle into the cfg library. returned from [initialize] and needed for all other calls
+#[derive(Copy, Clone)]
+pub struct Handle {
+ cfg_handle: u64,
+ callbacks: Callbacks,
+}
+
+/// Flags for [try_shutdown]
+pub enum ShutdownFlags {
+ /// Request shutdown (other daemons will be consulted)
+ Request,
+ /// Tells other daemons but ignore their opinions
+ Regardless,
+ /// Go down straight away (but still tell other nodes)
+ Immediate,
+}
+
+/// Responses for [reply_to_shutdown]
+pub enum ShutdownReply {
+ Yes = 1,
+ No = 0,
+}
+
+/// Trackflags for [track_start]. None currently supported
+pub enum TrackFlags {
+ None,
+}
+
+/// Version of the [NodeStatus] structure returned from [node_status_get]
+#[derive(Debug, Copy, Clone)]
+pub enum NodeStatusVersion {
+ V1,
+}
+
+/// Status of a link inside [NodeStatus] struct
+#[derive(Debug)]
+pub struct LinkStatus {
+ pub enabled: bool,
+ pub connected: bool,
+ pub dynconnected: bool,
+ pub mtu: u32,
+ pub src_ipaddr: String,
+ pub dst_ipaddr: String,
+}
+
+/// Structure returned from [node_status_get], shows all the details of a node
+/// that is known to corosync, including all configured links
+#[derive(Debug)]
+pub struct NodeStatus {
+ pub version: NodeStatusVersion,
+ pub nodeid: NodeId,
+ pub reachable: bool,
+ pub remote: bool,
+ pub external: bool,
+ pub onwire_min: u8,
+ pub onwire_max: u8,
+ pub onwire_ver: u8,
+ pub link_status: Vec<LinkStatus>,
+}
+
+extern "C" fn rust_shutdown_notification_fn(handle: ffi::corosync_cfg_handle_t, flags: u32) {
+ if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
+ if let Some(cb) = h.callbacks.corosync_cfg_shutdown_callback_fn {
+ (cb)(h, flags);
+ }
+ }
+}
+
+/// Initialize a connection to the cfg library. You must call this before doing anything
+/// else and use the passed back [Handle].
+/// Remember to free the handle using [finalize] when finished.
+pub fn initialize(callbacks: &Callbacks) -> Result<Handle> {
+ let mut handle: ffi::corosync_cfg_handle_t = 0;
+
+ let c_callbacks = ffi::corosync_cfg_callbacks_t {
+ corosync_cfg_shutdown_callback: Some(rust_shutdown_notification_fn),
+ };
+
+ unsafe {
+ let res = ffi::corosync_cfg_initialize(&mut handle, &c_callbacks);
+ if res == ffi::CS_OK {
+ let rhandle = Handle {
+ cfg_handle: handle,
+ callbacks: *callbacks,
+ };
+ HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
+ Ok(rhandle)
+ } else {
+ Err(CsError::from_c(res))
+ }
+ }
+}
+
+/// Finish with a connection to corosync, after calling this the [Handle] is invalid
+pub fn finalize(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::corosync_cfg_finalize(handle.cfg_handle) };
+ if res == ffi::CS_OK {
+ HANDLE_HASH.lock().unwrap().remove(&handle.cfg_handle);
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+// not sure if an fd is the right thing to return here, but it will do for now.
+/// Returns a file descriptor to use for poll/select on the CFG handle
+pub fn fd_get(handle: Handle) -> Result<i32> {
+ let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
+ let res = unsafe { ffi::corosync_cfg_fd_get(handle.cfg_handle, c_fd) };
+ if res == ffi::CS_OK {
+ Ok(c_fd as i32)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Get the local [NodeId]
+pub fn local_get(handle: Handle) -> Result<NodeId> {
+ let mut nodeid: u32 = 0;
+ let res = unsafe { ffi::corosync_cfg_local_get(handle.cfg_handle, &mut nodeid) };
+ if res == ffi::CS_OK {
+ Ok(NodeId::from(nodeid))
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Reload the cluster configuration on all nodes
+pub fn reload_cnfig(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::corosync_cfg_reload_config(handle.cfg_handle) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Re-open the cluster log files, on this node only
+pub fn reopen_log_files(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::corosync_cfg_reopen_log_files(handle.cfg_handle) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Tell another cluster node to shutdown. reason is a string that
+/// will be written to the system log files.
+pub fn kill_node(handle: Handle, nodeid: NodeId, reason: &str) -> Result<()> {
+ let c_string = {
+ match CString::new(reason) {
+ Ok(cs) => cs,
+ Err(_) => return Err(CsError::CsErrInvalidParam),
+ }
+ };
+
+ let res = unsafe {
+ ffi::corosync_cfg_kill_node(handle.cfg_handle, u32::from(nodeid), c_string.as_ptr())
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Ask this cluster node to shutdown. If [ShutdownFlags] is set to Request then
+///it may be refused by other applications
+/// that have registered for shutdown callbacks.
+pub fn try_shutdown(handle: Handle, flags: ShutdownFlags) -> Result<()> {
+ let c_flags = match flags {
+ ShutdownFlags::Request => 0,
+ ShutdownFlags::Regardless => 1,
+ ShutdownFlags::Immediate => 2,
+ };
+ let res = unsafe { ffi::corosync_cfg_try_shutdown(handle.cfg_handle, c_flags) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Reply to a shutdown request with Yes or No [ShutdownReply]
+pub fn reply_to_shutdown(handle: Handle, flags: ShutdownReply) -> Result<()> {
+ let c_flags = match flags {
+ ShutdownReply::No => 0,
+ ShutdownReply::Yes => 1,
+ };
+ let res = unsafe { ffi::corosync_cfg_replyto_shutdown(handle.cfg_handle, c_flags) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Call any/all active CFG callbacks for this [Handle] see [DispatchFlags] for details
+pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
+ let res = unsafe { ffi::corosync_cfg_dispatch(handle.cfg_handle, flags as u32) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+// Quick & dirty u8 to boolean
+fn u8_to_bool(val: u8) -> bool {
+ val != 0
+}
+
+const CFG_MAX_LINKS: usize = 8;
+const CFG_MAX_HOST_LEN: usize = 256;
+fn unpack_nodestatus(c_nodestatus: ffi::corosync_cfg_node_status_v1) -> Result<NodeStatus> {
+ let mut ns = NodeStatus {
+ version: NodeStatusVersion::V1,
+ nodeid: NodeId::from(c_nodestatus.nodeid),
+ reachable: u8_to_bool(c_nodestatus.reachable),
+ remote: u8_to_bool(c_nodestatus.remote),
+ external: u8_to_bool(c_nodestatus.external),
+ onwire_min: c_nodestatus.onwire_min,
+ onwire_max: c_nodestatus.onwire_max,
+ onwire_ver: c_nodestatus.onwire_min,
+ link_status: Vec::<LinkStatus>::new(),
+ };
+ for i in 0..CFG_MAX_LINKS {
+ let ls = LinkStatus {
+ enabled: u8_to_bool(c_nodestatus.link_status[i].enabled),
+ connected: u8_to_bool(c_nodestatus.link_status[i].connected),
+ dynconnected: u8_to_bool(c_nodestatus.link_status[i].dynconnected),
+ mtu: c_nodestatus.link_status[i].mtu,
+ src_ipaddr: string_from_bytes(
+ &c_nodestatus.link_status[i].src_ipaddr[0],
+ CFG_MAX_HOST_LEN,
+ )?,
+ dst_ipaddr: string_from_bytes(
+ &c_nodestatus.link_status[i].dst_ipaddr[0],
+ CFG_MAX_HOST_LEN,
+ )?,
+ };
+ ns.link_status.push(ls);
+ }
+
+ Ok(ns)
+}
+
+// Constructor for link status to make c_ndostatus initialization tidier.
+fn new_ls() -> ffi::corosync_knet_link_status_v1 {
+ ffi::corosync_knet_link_status_v1 {
+ enabled: 0,
+ connected: 0,
+ dynconnected: 0,
+ mtu: 0,
+ src_ipaddr: [0; 256],
+ dst_ipaddr: [0; 256],
+ }
+}
+
+/// Get the extended status of a node in the cluster (including active links) from its [NodeId].
+/// Returns a filled in [NodeStatus] struct
+pub fn node_status_get(
+ handle: Handle,
+ nodeid: NodeId,
+ _version: NodeStatusVersion,
+) -> Result<NodeStatus> {
+ // Currently only supports V1 struct
+ unsafe {
+ // We need to initialize this even though it's all going to be overwritten.
+ let mut c_nodestatus = ffi::corosync_cfg_node_status_v1 {
+ version: 1,
+ nodeid: 0,
+ reachable: 0,
+ remote: 0,
+ external: 0,
+ onwire_min: 0,
+ onwire_max: 0,
+ onwire_ver: 0,
+ link_status: [new_ls(); 8],
+ };
+
+ let res = ffi::corosync_cfg_node_status_get(
+ handle.cfg_handle,
+ u32::from(nodeid),
+ 1,
+ &mut c_nodestatus as *mut _ as *mut c_void,
+ );
+
+ if res == ffi::CS_OK {
+ unpack_nodestatus(c_nodestatus)
+ } else {
+ Err(CsError::from_c(res))
+ }
+ }
+}
+
+/// Start tracking for shutdown notifications
+pub fn track_start(handle: Handle, _flags: TrackFlags) -> Result<()> {
+ let res = unsafe { ffi::corosync_cfg_trackstart(handle.cfg_handle, 0) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Stop tracking for shutdown notifications
+pub fn track_stop(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::corosync_cfg_trackstop(handle.cfg_handle) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
diff --git a/bindings/rust/src/cmap.rs b/bindings/rust/src/cmap.rs
new file mode 100644
index 0000000..454fbee
--- /dev/null
+++ b/bindings/rust/src/cmap.rs
@@ -0,0 +1,894 @@
+// libcmap interface for Rust
+// Copyright (c) 2021 Red Hat, Inc.
+//
+// All rights reserved.
+//
+// Author: Christine Caulfield (ccaulfi@redhat.com)
+//
+
+#![allow(clippy::type_complexity)]
+
+// For the code generated by bindgen
+use crate::sys::cmap as ffi;
+
+use num_enum::TryFromPrimitive;
+use std::any::type_name;
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::ffi::CString;
+use std::fmt;
+use std::os::raw::{c_char, c_int, c_void};
+use std::ptr::copy_nonoverlapping;
+use std::sync::Mutex;
+
+use crate::string_from_bytes;
+use crate::{CsError, DispatchFlags, Result};
+
+// Maps:
+/// "Maps" available to [initialize]
+pub enum Map {
+ Icmap,
+ Stats,
+}
+
+bitflags! {
+/// Tracker types for cmap, both passed into [track_add]
+/// and returned from its callback.
+ pub struct TrackType: i32
+ {
+ const DELETE = 1;
+ const MODIFY = 2;
+ const ADD = 4;
+ const PREFIX = 8;
+ }
+}
+
+impl fmt::Display for TrackType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.contains(TrackType::DELETE) {
+ write!(f, "DELETE ")?
+ }
+ if self.contains(TrackType::MODIFY) {
+ write!(f, "MODIFY ")?
+ }
+ if self.contains(TrackType::ADD) {
+ write!(f, "ADD ")?
+ }
+ if self.contains(TrackType::PREFIX) {
+ write!(f, "PREFIX ")
+ } else {
+ Ok(())
+ }
+ }
+}
+
+#[derive(Copy, Clone)]
+/// A handle returned from [initialize], needs to be passed to all other cmap API calls
+pub struct Handle {
+ cmap_handle: u64,
+}
+
+#[derive(Copy, Clone)]
+/// A handle for a specific CMAP tracker. returned from [track_add].
+/// There may be multiple TrackHandles per [Handle]
+pub struct TrackHandle {
+ track_handle: u64,
+ notify_callback: NotifyCallback,
+}
+
+// Used to convert CMAP handles into one of ours, for callbacks
+lazy_static! {
+ static ref TRACKHANDLE_HASH: Mutex<HashMap<u64, TrackHandle>> = Mutex::new(HashMap::new());
+ static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
+}
+
+/// Initialize a connection to the cmap subsystem.
+/// map specifies which cmap "map" to use.
+/// Returns a [Handle] into the cmap library
+pub fn initialize(map: Map) -> Result<Handle> {
+ let mut handle: ffi::cmap_handle_t = 0;
+ let c_map = match map {
+ Map::Icmap => ffi::CMAP_MAP_ICMAP,
+ Map::Stats => ffi::CMAP_MAP_STATS,
+ };
+
+ unsafe {
+ let res = ffi::cmap_initialize_map(&mut handle, c_map);
+ if res == ffi::CS_OK {
+ let rhandle = Handle {
+ cmap_handle: handle,
+ };
+ HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
+ Ok(rhandle)
+ } else {
+ Err(CsError::from_c(res))
+ }
+ }
+}
+
+/// Finish with a connection to corosync.
+/// Takes a [Handle] as returned from [initialize]
+pub fn finalize(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::cmap_finalize(handle.cmap_handle) };
+ if res == ffi::CS_OK {
+ HANDLE_HASH.lock().unwrap().remove(&handle.cmap_handle);
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Return a file descriptor to use for poll/select on the CMAP handle.
+/// Takes a [Handle] as returned from [initialize],
+/// returns a C file descriptor as i32
+pub fn fd_get(handle: Handle) -> Result<i32> {
+ let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
+ let res = unsafe { ffi::cmap_fd_get(handle.cmap_handle, c_fd) };
+ if res == ffi::CS_OK {
+ Ok(c_fd as i32)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Dispatch any/all active CMAP callbacks.
+/// Takes a [Handle] as returned from [initialize],
+/// flags [DispatchFlags] tells it how many items to dispatch before returning
+pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
+ let res = unsafe { ffi::cmap_dispatch(handle.cmap_handle, flags as u32) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Get the current 'context' value for this handle
+/// The context value is an arbitrary value that is always passed
+/// back to callbacks to help identify the source
+pub fn context_get(handle: Handle) -> Result<u64> {
+ let (res, context) = unsafe {
+ let mut context: u64 = 0;
+ let c_context: *mut c_void = &mut context as *mut _ as *mut c_void;
+ let r = ffi::cmap_context_get(handle.cmap_handle, c_context as *mut *const c_void);
+ (r, context)
+ };
+ if res == ffi::CS_OK {
+ Ok(context)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Set the current 'context' value for this handle
+/// The context value is an arbitrary value that is always passed
+/// back to callbacks to help identify the source.
+/// Normally this is set in [initialize], but this allows it to be changed
+pub fn context_set(handle: Handle, context: u64) -> Result<()> {
+ let res = unsafe {
+ let c_context = context as *mut c_void;
+ ffi::cmap_context_set(handle.cmap_handle, c_context)
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// The type of data returned from [get] or in a
+/// tracker callback or iterator, part of the [Data] struct
+#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
+#[repr(u32)]
+pub enum DataType {
+ Int8 = ffi::CMAP_VALUETYPE_INT8,
+ UInt8 = ffi::CMAP_VALUETYPE_UINT8,
+ Int16 = ffi::CMAP_VALUETYPE_INT16,
+ UInt16 = ffi::CMAP_VALUETYPE_UINT16,
+ Int32 = ffi::CMAP_VALUETYPE_INT32,
+ UInt32 = ffi::CMAP_VALUETYPE_UINT32,
+ Int64 = ffi::CMAP_VALUETYPE_INT64,
+ UInt64 = ffi::CMAP_VALUETYPE_UINT64,
+ Float = ffi::CMAP_VALUETYPE_FLOAT,
+ Double = ffi::CMAP_VALUETYPE_DOUBLE,
+ String = ffi::CMAP_VALUETYPE_STRING,
+ Binary = ffi::CMAP_VALUETYPE_BINARY,
+ Unknown = 999,
+}
+
+fn cmap_to_enum(cmap_type: u32) -> DataType {
+ match DataType::try_from(cmap_type) {
+ Ok(e) => e,
+ Err(_) => DataType::Unknown,
+ }
+}
+
+/// Data returned from the cmap::get() call and tracker & iterators.
+/// Contains the data itself and the type of that data.
+pub enum Data {
+ Int8(i8),
+ UInt8(u8),
+ Int16(i16),
+ UInt16(u16),
+ Int32(i32),
+ UInt32(u32),
+ Int64(i64),
+ UInt64(u64),
+ Float(f32),
+ Double(f64),
+ String(String),
+ Binary(Vec<u8>),
+ Unknown,
+}
+
+impl fmt::Display for DataType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ DataType::Int8 => write!(f, "Int8"),
+ DataType::UInt8 => write!(f, "UInt8"),
+ DataType::Int16 => write!(f, "Int16"),
+ DataType::UInt16 => write!(f, "UInt16"),
+ DataType::Int32 => write!(f, "Int32"),
+ DataType::UInt32 => write!(f, "UInt32"),
+ DataType::Int64 => write!(f, "Int64"),
+ DataType::UInt64 => write!(f, "UInt64"),
+ DataType::Float => write!(f, "Float"),
+ DataType::Double => write!(f, "Double"),
+ DataType::String => write!(f, "String"),
+ DataType::Binary => write!(f, "Binary"),
+ DataType::Unknown => write!(f, "Unknown"),
+ }
+ }
+}
+
+impl fmt::Display for Data {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Data::Int8(v) => write!(f, "{v} (Int8)"),
+ Data::UInt8(v) => write!(f, "{v} (UInt8)"),
+ Data::Int16(v) => write!(f, "{v} (Int16)"),
+ Data::UInt16(v) => write!(f, "{v} (UInt16)"),
+ Data::Int32(v) => write!(f, "{v} (Int32)"),
+ Data::UInt32(v) => write!(f, "{v} (UInt32)"),
+ Data::Int64(v) => write!(f, "{v} (Int64)"),
+ Data::UInt64(v) => write!(f, "{v} (UInt64)"),
+ Data::Float(v) => write!(f, "{v} (Float)"),
+ Data::Double(v) => write!(f, "{v} (Double)"),
+ Data::String(v) => write!(f, "{v} (String)"),
+ Data::Binary(v) => write!(f, "{v:?} (Binary)"),
+ Data::Unknown => write!(f, "Unknown)"),
+ }
+ }
+}
+
+const CMAP_KEYNAME_MAXLENGTH: usize = 255;
+fn string_to_cstring_validated(key: &str, maxlen: usize) -> Result<CString> {
+ if maxlen > 0 && key.chars().count() >= maxlen {
+ return Err(CsError::CsErrInvalidParam);
+ }
+
+ match CString::new(key) {
+ Ok(n) => Ok(n),
+ Err(_) => Err(CsError::CsErrLibrary),
+ }
+}
+
+fn set_value(
+ handle: Handle,
+ key_name: &str,
+ datatype: DataType,
+ value: *mut c_void,
+ length: usize,
+) -> Result<()> {
+ let csname = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?;
+ let res = unsafe {
+ ffi::cmap_set(
+ handle.cmap_handle,
+ csname.as_ptr(),
+ value,
+ length,
+ datatype as u32,
+ )
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+// Returns type and size
+fn generic_to_cmap<T>(_value: T) -> (DataType, usize) {
+ match type_name::<T>() {
+ "u8" => (DataType::UInt8, 1),
+ "i8" => (DataType::Int8, 1),
+ "u16" => (DataType::UInt16, 2),
+ "i16" => (DataType::Int16, 2),
+ "u32" => (DataType::UInt32, 4),
+ "i32" => (DataType::Int32, 4),
+ "u64" => (DataType::UInt64, 4),
+ "f32" => (DataType::Float, 4),
+ "f64" => (DataType::Double, 8),
+ "&str" => (DataType::String, 0),
+ // Binary not currently supported here
+ _ => (DataType::Unknown, 0),
+ }
+}
+
+fn is_numeric_type(dtype: DataType) -> bool {
+ matches!(
+ dtype,
+ DataType::UInt8
+ | DataType::Int8
+ | DataType::UInt16
+ | DataType::Int16
+ | DataType::UInt32
+ | DataType::Int32
+ | DataType::UInt64
+ | DataType::Int64
+ | DataType::Float
+ | DataType::Double
+ )
+}
+
+/// Function to set a generic numeric value
+/// This doesn't work for strings or binaries
+pub fn set_number<T: Copy>(handle: Handle, key_name: &str, value: T) -> Result<()> {
+ let (c_type, c_size) = generic_to_cmap(value);
+
+ if is_numeric_type(c_type) {
+ let mut tmp = value;
+ let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ set_value(handle, key_name, c_type, c_value as *mut c_void, c_size)
+ } else {
+ Err(CsError::CsErrNotSupported)
+ }
+}
+
+pub fn set_u8(handle: Handle, key_name: &str, value: u8) -> Result<()> {
+ let mut tmp = value;
+ let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ set_value(handle, key_name, DataType::UInt8, c_value as *mut c_void, 1)
+}
+
+/// Sets an i8 value into cmap
+pub fn set_i8(handle: Handle, key_name: &str, value: i8) -> Result<()> {
+ let mut tmp = value;
+ let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ set_value(handle, key_name, DataType::Int8, c_value as *mut c_void, 1)
+}
+
+/// Sets a u16 value into cmap
+pub fn set_u16(handle: Handle, key_name: &str, value: u16) -> Result<()> {
+ let mut tmp = value;
+ let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ set_value(
+ handle,
+ key_name,
+ DataType::UInt16,
+ c_value as *mut c_void,
+ 2,
+ )
+}
+
+/// Sets an i16 value into cmap
+pub fn set_i16(handle: Handle, key_name: &str, value: i16) -> Result<()> {
+ let mut tmp = value;
+ let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ set_value(handle, key_name, DataType::Int16, c_value as *mut c_void, 2)
+}
+
+/// Sets a u32 value into cmap
+pub fn set_u32(handle: Handle, key_name: &str, value: u32) -> Result<()> {
+ let mut tmp = value;
+ let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ set_value(handle, key_name, DataType::UInt32, c_value, 4)
+}
+
+/// Sets an i32 value into cmap
+pub fn set_i132(handle: Handle, key_name: &str, value: i32) -> Result<()> {
+ let mut tmp = value;
+ let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ set_value(handle, key_name, DataType::Int32, c_value as *mut c_void, 4)
+}
+
+/// Sets a u64 value into cmap
+pub fn set_u64(handle: Handle, key_name: &str, value: u64) -> Result<()> {
+ let mut tmp = value;
+ let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ set_value(
+ handle,
+ key_name,
+ DataType::UInt64,
+ c_value as *mut c_void,
+ 8,
+ )
+}
+
+/// Sets an i64 value into cmap
+pub fn set_i164(handle: Handle, key_name: &str, value: i64) -> Result<()> {
+ let mut tmp = value;
+ let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ set_value(handle, key_name, DataType::Int64, c_value as *mut c_void, 8)
+}
+
+/// Sets a string value into cmap
+pub fn set_string(handle: Handle, key_name: &str, value: &str) -> Result<()> {
+ let v_string = string_to_cstring_validated(value, 0)?;
+ set_value(
+ handle,
+ key_name,
+ DataType::String,
+ v_string.as_ptr() as *mut c_void,
+ value.chars().count(),
+ )
+}
+
+/// Sets a binary value into cmap
+pub fn set_binary(handle: Handle, key_name: &str, value: &[u8]) -> Result<()> {
+ set_value(
+ handle,
+ key_name,
+ DataType::Binary,
+ value.as_ptr() as *mut c_void,
+ value.len(),
+ )
+}
+
+/// Sets a [Data] type into cmap
+pub fn set(handle: Handle, key_name: &str, data: &Data) -> Result<()> {
+ let (datatype, datalen, c_value) = match data {
+ Data::Int8(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::Int8, 1, cv)
+ }
+ Data::UInt8(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::UInt8, 1, cv)
+ }
+ Data::Int16(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::Int16, 2, cv)
+ }
+ Data::UInt16(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::UInt8, 2, cv)
+ }
+ Data::Int32(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::Int32, 4, cv)
+ }
+ Data::UInt32(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::UInt32, 4, cv)
+ }
+ Data::Int64(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::Int64, 8, cv)
+ }
+ Data::UInt64(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::UInt64, 8, cv)
+ }
+ Data::Float(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::Float, 4, cv)
+ }
+ Data::Double(v) => {
+ let mut tmp = *v;
+ let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
+ (DataType::Double, 8, cv)
+ }
+ Data::String(v) => {
+ let cv = string_to_cstring_validated(v, 0)?;
+ // Can't let cv go out of scope
+ return set_value(
+ handle,
+ key_name,
+ DataType::String,
+ cv.as_ptr() as *mut c_void,
+ v.chars().count(),
+ );
+ }
+ Data::Binary(v) => {
+ // Vec doesn't return quite the right types.
+ return set_value(
+ handle,
+ key_name,
+ DataType::Binary,
+ v.as_ptr() as *mut c_void,
+ v.len(),
+ );
+ }
+ Data::Unknown => return Err(CsError::CsErrInvalidParam),
+ };
+
+ set_value(handle, key_name, datatype, c_value, datalen)
+}
+
+// Local function to parse out values from the C mess
+// Assumes the c_value is complete. So cmap::get() will need to check the size
+// and re-get before calling us with a resized buffer
+fn c_to_data(value_size: usize, c_key_type: u32, c_value: *const u8) -> Result<Data> {
+ unsafe {
+ match cmap_to_enum(c_key_type) {
+ DataType::UInt8 => {
+ let mut ints = [0u8; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr(), value_size);
+ Ok(Data::UInt8(ints[0]))
+ }
+ DataType::Int8 => {
+ let mut ints = [0i8; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size);
+ Ok(Data::Int8(ints[0]))
+ }
+ DataType::UInt16 => {
+ let mut ints = [0u16; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size);
+ Ok(Data::UInt16(ints[0]))
+ }
+ DataType::Int16 => {
+ let mut ints = [0i16; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size);
+ Ok(Data::Int16(ints[0]))
+ }
+ DataType::UInt32 => {
+ let mut ints = [0u32; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size);
+ Ok(Data::UInt32(ints[0]))
+ }
+ DataType::Int32 => {
+ let mut ints = [0i32; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size);
+ Ok(Data::Int32(ints[0]))
+ }
+ DataType::UInt64 => {
+ let mut ints = [0u64; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size);
+ Ok(Data::UInt64(ints[0]))
+ }
+ DataType::Int64 => {
+ let mut ints = [0i64; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size);
+ Ok(Data::Int64(ints[0]))
+ }
+ DataType::Float => {
+ let mut ints = [0f32; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size);
+ Ok(Data::Float(ints[0]))
+ }
+ DataType::Double => {
+ let mut ints = [0f64; 1];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size);
+ Ok(Data::Double(ints[0]))
+ }
+ DataType::String => {
+ let mut ints = vec![0u8; value_size];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr(), value_size);
+ // -1 here so CString doesn't see the NUL
+ let cs = match CString::new(&ints[0..value_size - 1_usize]) {
+ Ok(c1) => c1,
+ Err(_) => return Err(CsError::CsErrLibrary),
+ };
+ match cs.into_string() {
+ Ok(s) => Ok(Data::String(s)),
+ Err(_) => Err(CsError::CsErrLibrary),
+ }
+ }
+ DataType::Binary => {
+ let mut ints = vec![0u8; value_size];
+ copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr(), value_size);
+ Ok(Data::Binary(ints))
+ }
+ DataType::Unknown => Ok(Data::Unknown),
+ }
+ }
+}
+
+const INITIAL_SIZE: usize = 256;
+
+/// Get a value from cmap, returned as a [Data] struct, so could be anything
+pub fn get(handle: Handle, key_name: &str) -> Result<Data> {
+ let csname = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?;
+ let mut value_size: usize = 16;
+ let mut c_key_type: u32 = 0;
+
+ // First guess at a size for Strings and Binaries. Expand if needed
+ let mut c_value = vec![0u8; INITIAL_SIZE];
+
+ unsafe {
+ let res = ffi::cmap_get(
+ handle.cmap_handle,
+ csname.as_ptr(),
+ c_value.as_mut_ptr() as *mut c_void,
+ &mut value_size,
+ &mut c_key_type,
+ );
+ if res == ffi::CS_OK {
+ if value_size > INITIAL_SIZE {
+ // Need to try again with a bigger buffer
+ c_value.resize(value_size, 0u8);
+ let res2 = ffi::cmap_get(
+ handle.cmap_handle,
+ csname.as_ptr(),
+ c_value.as_mut_ptr() as *mut c_void,
+ &mut value_size,
+ &mut c_key_type,
+ );
+ if res2 != ffi::CS_OK {
+ return Err(CsError::from_c(res2));
+ }
+ }
+
+ // Convert to Rust type and return as a Data enum
+ c_to_data(value_size, c_key_type, c_value.as_ptr())
+ } else {
+ Err(CsError::from_c(res))
+ }
+ }
+}
+
+/// increment the value in a cmap key (must be a numeric type)
+pub fn inc(handle: Handle, key_name: &str) -> Result<()> {
+ let csname = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?;
+ let res = unsafe { ffi::cmap_inc(handle.cmap_handle, csname.as_ptr()) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// decrement the value in a cmap key (must be a numeric type)
+pub fn dec(handle: Handle, key_name: &str) -> Result<()> {
+ let csname = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?;
+ let res = unsafe { ffi::cmap_dec(handle.cmap_handle, csname.as_ptr()) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+// Callback for CMAP notify events from corosync, convert params to Rust and pass on.
+extern "C" fn rust_notify_fn(
+ cmap_handle: ffi::cmap_handle_t,
+ cmap_track_handle: ffi::cmap_track_handle_t,
+ event: i32,
+ key_name: *const ::std::os::raw::c_char,
+ new_value: ffi::cmap_notify_value,
+ old_value: ffi::cmap_notify_value,
+ user_data: *mut ::std::os::raw::c_void,
+) {
+ // If cmap_handle doesn't match then throw away the callback.
+ if let Some(r_cmap_handle) = HANDLE_HASH.lock().unwrap().get(&cmap_handle) {
+ if let Some(h) = TRACKHANDLE_HASH.lock().unwrap().get(&cmap_track_handle) {
+ let r_keyname = match string_from_bytes(key_name, CMAP_KEYNAME_MAXLENGTH) {
+ Ok(s) => s,
+ Err(_) => return,
+ };
+
+ let r_old = match c_to_data(old_value.len, old_value.type_, old_value.data as *const u8)
+ {
+ Ok(v) => v,
+ Err(_) => return,
+ };
+ let r_new = match c_to_data(new_value.len, new_value.type_, new_value.data as *const u8)
+ {
+ Ok(v) => v,
+ Err(_) => return,
+ };
+
+ if let Some(cb) = h.notify_callback.notify_fn {
+ (cb)(
+ r_cmap_handle,
+ h,
+ TrackType { bits: event },
+ &r_keyname,
+ &r_old,
+ &r_new,
+ user_data as u64,
+ );
+ }
+ }
+ }
+}
+
+/// Callback function called every time a tracker reports a change in a tracked value
+#[derive(Copy, Clone)]
+pub struct NotifyCallback {
+ pub notify_fn: Option<
+ fn(
+ handle: &Handle,
+ track_handle: &TrackHandle,
+ event: TrackType,
+ key_name: &str,
+ new_value: &Data,
+ old_value: &Data,
+ user_data: u64,
+ ),
+ >,
+}
+
+/// Track changes in cmap values, multiple [TrackHandle]s per [Handle] are allowed
+pub fn track_add(
+ handle: Handle,
+ key_name: &str,
+ track_type: TrackType,
+ notify_callback: &NotifyCallback,
+ user_data: u64,
+) -> Result<TrackHandle> {
+ let c_name = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?;
+ let mut c_trackhandle = 0u64;
+ let res = unsafe {
+ ffi::cmap_track_add(
+ handle.cmap_handle,
+ c_name.as_ptr(),
+ track_type.bits,
+ Some(rust_notify_fn),
+ user_data as *mut c_void,
+ &mut c_trackhandle,
+ )
+ };
+ if res == ffi::CS_OK {
+ let rhandle = TrackHandle {
+ track_handle: c_trackhandle,
+ notify_callback: *notify_callback,
+ };
+ TRACKHANDLE_HASH
+ .lock()
+ .unwrap()
+ .insert(c_trackhandle, rhandle);
+ Ok(rhandle)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Remove a tracker frm this [Handle]
+pub fn track_delete(handle: Handle, track_handle: TrackHandle) -> Result<()> {
+ let res = unsafe { ffi::cmap_track_delete(handle.cmap_handle, track_handle.track_handle) };
+ if res == ffi::CS_OK {
+ TRACKHANDLE_HASH
+ .lock()
+ .unwrap()
+ .remove(&track_handle.track_handle);
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Create one of these to start iterating over cmap values.
+pub struct CmapIterStart {
+ iter_handle: u64,
+ cmap_handle: u64,
+}
+
+pub struct CmapIntoIter {
+ cmap_handle: u64,
+ iter_handle: u64,
+}
+
+/// Value returned from the iterator. contains the key name and the [Data]
+pub struct CmapIter {
+ key_name: String,
+ data: Data,
+}
+
+impl CmapIter {
+ pub fn key_name(&self) -> &str {
+ &self.key_name
+ }
+ pub fn data(&self) -> &Data {
+ &self.data
+ }
+}
+
+impl fmt::Debug for CmapIter {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}: {}", self.key_name, self.data)
+ }
+}
+
+impl Iterator for CmapIntoIter {
+ type Item = CmapIter;
+
+ fn next(&mut self) -> Option<CmapIter> {
+ let mut c_key_name = [0u8; CMAP_KEYNAME_MAXLENGTH + 1];
+ let mut c_value_len = 0usize;
+ let mut c_value_type = 0u32;
+ let res = unsafe {
+ ffi::cmap_iter_next(
+ self.cmap_handle,
+ self.iter_handle,
+ c_key_name.as_mut_ptr() as *mut c_char,
+ &mut c_value_len,
+ &mut c_value_type,
+ )
+ };
+ if res == ffi::CS_OK {
+ // Return the Data for this iteration
+ let mut c_value = vec![0u8; c_value_len];
+ let res = unsafe {
+ ffi::cmap_get(
+ self.cmap_handle,
+ c_key_name.as_ptr() as *mut c_char,
+ c_value.as_mut_ptr() as *mut c_void,
+ &mut c_value_len,
+ &mut c_value_type,
+ )
+ };
+ if res == ffi::CS_OK {
+ match c_to_data(c_value_len, c_value_type, c_value.as_ptr()) {
+ Ok(d) => {
+ let r_keyname = match string_from_bytes(
+ c_key_name.as_ptr() as *mut c_char,
+ CMAP_KEYNAME_MAXLENGTH,
+ ) {
+ Ok(s) => s,
+ Err(_) => return None,
+ };
+ Some(CmapIter {
+ key_name: r_keyname,
+ data: d,
+ })
+ }
+ Err(_) => None,
+ }
+ } else {
+ // cmap_get returned error
+ None
+ }
+ } else if res == ffi::CS_ERR_NO_SECTIONS {
+ // End of list
+ unsafe {
+ // Yeah, we don't check this return code. There's nowhere to report it.
+ ffi::cmap_iter_finalize(self.cmap_handle, self.iter_handle)
+ };
+ None
+ } else {
+ None
+ }
+ }
+}
+
+impl CmapIterStart {
+ /// Create a new [CmapIterStart] object for iterating over a list of cmap keys
+ pub fn new(cmap_handle: Handle, prefix: &str) -> Result<CmapIterStart> {
+ let mut iter_handle: u64 = 0;
+ let res = unsafe {
+ let c_prefix = string_to_cstring_validated(prefix, CMAP_KEYNAME_MAXLENGTH)?;
+ ffi::cmap_iter_init(cmap_handle.cmap_handle, c_prefix.as_ptr(), &mut iter_handle)
+ };
+ if res == ffi::CS_OK {
+ Ok(CmapIterStart {
+ cmap_handle: cmap_handle.cmap_handle,
+ iter_handle,
+ })
+ } else {
+ Err(CsError::from_c(res))
+ }
+ }
+}
+
+impl IntoIterator for CmapIterStart {
+ type Item = CmapIter;
+ type IntoIter = CmapIntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ CmapIntoIter {
+ iter_handle: self.iter_handle,
+ cmap_handle: self.cmap_handle,
+ }
+ }
+}
diff --git a/bindings/rust/src/cpg.rs b/bindings/rust/src/cpg.rs
new file mode 100644
index 0000000..1246497
--- /dev/null
+++ b/bindings/rust/src/cpg.rs
@@ -0,0 +1,628 @@
+// libcpg interface for Rust
+// Copyright (c) 2020 Red Hat, Inc.
+//
+// All rights reserved.
+//
+// Author: Christine Caulfield (ccaulfi@redhat.com)
+//
+
+#![allow(clippy::single_match)]
+#![allow(clippy::needless_range_loop)]
+#![allow(clippy::type_complexity)]
+
+// For the code generated by bindgen
+use crate::sys::cpg as ffi;
+
+use std::collections::HashMap;
+use std::ffi::{CStr, CString};
+use std::fmt;
+use std::os::raw::{c_int, c_void};
+use std::ptr::copy_nonoverlapping;
+use std::slice;
+use std::string::String;
+use std::sync::Mutex;
+
+// General corosync things
+use crate::string_from_bytes;
+use crate::{CsError, DispatchFlags, NodeId, Result};
+
+const CPG_NAMELEN_MAX: usize = 128;
+const CPG_MEMBERS_MAX: usize = 128;
+
+/// RingId returned by totem_confchg_fn
+#[derive(Copy, Clone)]
+pub struct RingId {
+ pub nodeid: NodeId,
+ pub seq: u64,
+}
+
+/// Totem delivery guarantee options for [mcast_joined]
+// The C enum doesn't have numbers in the code
+// so don't assume we can match them
+#[derive(Copy, Clone)]
+pub enum Guarantee {
+ TypeUnordered,
+ TypeFifo,
+ TypeAgreed,
+ TypeSafe,
+}
+
+// Convert internal to cpg.h values.
+impl Guarantee {
+ pub fn to_c(&self) -> u32 {
+ match self {
+ Guarantee::TypeUnordered => ffi::CPG_TYPE_UNORDERED,
+ Guarantee::TypeFifo => ffi::CPG_TYPE_FIFO,
+ Guarantee::TypeAgreed => ffi::CPG_TYPE_AGREED,
+ Guarantee::TypeSafe => ffi::CPG_TYPE_SAFE,
+ }
+ }
+}
+
+/// Flow control state returned from [flow_control_state_get]
+#[derive(Copy, Clone)]
+pub enum FlowControlState {
+ Disabled,
+ Enabled,
+}
+
+/// No flags current specified for model1 so leave this at None
+#[derive(Copy, Clone)]
+pub enum Model1Flags {
+ None,
+}
+
+/// Reason for cpg item callback
+#[derive(Copy, Clone)]
+pub enum Reason {
+ Undefined = 0,
+ Join = 1,
+ Leave = 2,
+ NodeDown = 3,
+ NodeUp = 4,
+ ProcDown = 5,
+}
+
+// Convert to cpg.h values
+impl Reason {
+ pub fn new(r: u32) -> Reason {
+ match r {
+ 0 => Reason::Undefined,
+ 1 => Reason::Join,
+ 2 => Reason::Leave,
+ 3 => Reason::NodeDown,
+ 4 => Reason::NodeUp,
+ 5 => Reason::ProcDown,
+ _ => Reason::Undefined,
+ }
+ }
+}
+impl fmt::Display for Reason {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Reason::Undefined => write!(f, "Undefined"),
+ Reason::Join => write!(f, "Join"),
+ Reason::Leave => write!(f, "Leave"),
+ Reason::NodeDown => write!(f, "NodeDown"),
+ Reason::NodeUp => write!(f, "NodeUp"),
+ Reason::ProcDown => write!(f, "ProcDown"),
+ }
+ }
+}
+
+/// A CPG address entry returned in the callbacks
+pub struct Address {
+ pub nodeid: NodeId,
+ pub pid: u32,
+ pub reason: Reason,
+}
+impl fmt::Debug for Address {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "[nodeid: {}, pid: {}, reason: {}]",
+ self.nodeid, self.pid, self.reason
+ )
+ }
+}
+
+/// Data for model1 [initialize]
+#[derive(Copy, Clone)]
+pub struct Model1Data {
+ pub flags: Model1Flags,
+ pub deliver_fn: Option<
+ fn(
+ handle: &Handle,
+ group_name: String,
+ nodeid: NodeId,
+ pid: u32,
+ msg: &[u8],
+ msg_len: usize,
+ ),
+ >,
+ pub confchg_fn: Option<
+ fn(
+ handle: &Handle,
+ group_name: &str,
+ member_list: Vec<Address>,
+ left_list: Vec<Address>,
+ joined_list: Vec<Address>,
+ ),
+ >,
+ pub totem_confchg_fn: Option<fn(handle: &Handle, ring_id: RingId, member_list: Vec<NodeId>)>,
+}
+
+/// Modeldata for [initialize], only v1 supported at the moment
+#[derive(Copy, Clone)]
+pub enum ModelData {
+ ModelNone,
+ ModelV1(Model1Data),
+}
+
+/// A handle into the cpg library. Returned from [initialize] and needed for all other calls
+#[derive(Copy, Clone)]
+pub struct Handle {
+ cpg_handle: u64, // Corosync library handle
+ model_data: ModelData,
+}
+
+// Used to convert a CPG handle into one of ours
+lazy_static! {
+ static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
+}
+
+// Convert a Rust String into a cpg_name struct for libcpg
+fn string_to_cpg_name(group: &str) -> Result<ffi::cpg_name> {
+ if group.len() > CPG_NAMELEN_MAX - 1 {
+ return Err(CsError::CsErrInvalidParam);
+ }
+
+ let c_name = match CString::new(group) {
+ Ok(n) => n,
+ Err(_) => return Err(CsError::CsErrLibrary),
+ };
+ let mut c_group = ffi::cpg_name {
+ length: group.len() as u32,
+ value: [0; CPG_NAMELEN_MAX],
+ };
+
+ unsafe {
+ // NOTE param order is 'wrong-way round' from C
+ copy_nonoverlapping(c_name.as_ptr(), c_group.value.as_mut_ptr(), group.len());
+ }
+
+ Ok(c_group)
+}
+
+// Convert an array of cpg_addresses to a Vec<cpg::Address> - used in callbacks
+fn cpg_array_to_vec(list: *const ffi::cpg_address, list_entries: usize) -> Vec<Address> {
+ let temp: &[ffi::cpg_address] = unsafe { slice::from_raw_parts(list, list_entries) };
+ let mut r_vec = Vec::<Address>::new();
+
+ for i in 0..list_entries {
+ let a: Address = Address {
+ nodeid: NodeId::from(temp[i].nodeid),
+ pid: temp[i].pid,
+ reason: Reason::new(temp[i].reason),
+ };
+ r_vec.push(a);
+ }
+ r_vec
+}
+
+// Called from CPG callback function - munge params back to Rust from C
+extern "C" fn rust_deliver_fn(
+ handle: ffi::cpg_handle_t,
+ group_name: *const ffi::cpg_name,
+ nodeid: u32,
+ pid: u32,
+ msg: *mut ::std::os::raw::c_void,
+ msg_len: usize,
+) {
+ if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
+ // Convert group_name into a Rust str.
+ let r_group_name = unsafe {
+ CStr::from_ptr(&(*group_name).value[0])
+ .to_string_lossy()
+ .into_owned()
+ };
+
+ let data: &[u8] = unsafe { std::slice::from_raw_parts(msg as *const u8, msg_len) };
+
+ match h.model_data {
+ ModelData::ModelV1(md) => {
+ if let Some(cb) = md.deliver_fn {
+ (cb)(h, r_group_name, NodeId::from(nodeid), pid, data, msg_len);
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+// Called from CPG callback function - munge params back to Rust from C
+extern "C" fn rust_confchg_fn(
+ handle: ffi::cpg_handle_t,
+ group_name: *const ffi::cpg_name,
+ member_list: *const ffi::cpg_address,
+ member_list_entries: usize,
+ left_list: *const ffi::cpg_address,
+ left_list_entries: usize,
+ joined_list: *const ffi::cpg_address,
+ joined_list_entries: usize,
+) {
+ if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
+ let r_group_name = unsafe {
+ CStr::from_ptr(&(*group_name).value[0])
+ .to_string_lossy()
+ .into_owned()
+ };
+ let r_member_list = cpg_array_to_vec(member_list, member_list_entries);
+ let r_left_list = cpg_array_to_vec(left_list, left_list_entries);
+ let r_joined_list = cpg_array_to_vec(joined_list, joined_list_entries);
+
+ match h.model_data {
+ ModelData::ModelV1(md) => {
+ if let Some(cb) = md.confchg_fn {
+ (cb)(h, &r_group_name, r_member_list, r_left_list, r_joined_list);
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+// Called from CPG callback function - munge params back to Rust from C
+extern "C" fn rust_totem_confchg_fn(
+ handle: ffi::cpg_handle_t,
+ ring_id: ffi::cpg_ring_id,
+ member_list_entries: u32,
+ member_list: *const u32,
+) {
+ if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
+ let r_ring_id = RingId {
+ nodeid: NodeId::from(ring_id.nodeid),
+ seq: ring_id.seq,
+ };
+ let mut r_member_list = Vec::<NodeId>::new();
+ let temp_members: &[u32] =
+ unsafe { slice::from_raw_parts(member_list, member_list_entries as usize) };
+ for i in 0..member_list_entries as usize {
+ r_member_list.push(NodeId::from(temp_members[i]));
+ }
+
+ match h.model_data {
+ ModelData::ModelV1(md) => {
+ if let Some(cb) = md.totem_confchg_fn {
+ (cb)(h, r_ring_id, r_member_list);
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+/// Initialize a connection to the cpg library. You must call this before doing anything
+/// else and use the passed back [Handle].
+/// Remember to free the handle using [finalize] when finished.
+pub fn initialize(model_data: &ModelData, context: u64) -> Result<Handle> {
+ let mut handle: ffi::cpg_handle_t = 0;
+ let mut m = match model_data {
+ ModelData::ModelV1(_v1) => {
+ ffi::cpg_model_v1_data_t {
+ model: ffi::CPG_MODEL_V1,
+ cpg_deliver_fn: Some(rust_deliver_fn),
+ cpg_confchg_fn: Some(rust_confchg_fn),
+ cpg_totem_confchg_fn: Some(rust_totem_confchg_fn),
+ flags: 0, // No supported flags (yet)
+ }
+ }
+ _ => return Err(CsError::CsErrInvalidParam),
+ };
+
+ unsafe {
+ let c_context: *mut c_void = &mut &context as *mut _ as *mut c_void;
+ let c_model: *mut ffi::cpg_model_data_t = &mut m as *mut _ as *mut ffi::cpg_model_data_t;
+ let res = ffi::cpg_model_initialize(&mut handle, m.model, c_model, c_context);
+
+ if res == ffi::CS_OK {
+ let rhandle = Handle {
+ cpg_handle: handle,
+ model_data: *model_data,
+ };
+ HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
+ Ok(rhandle)
+ } else {
+ Err(CsError::from_c(res))
+ }
+ }
+}
+
+/// Finish with a connection to corosync
+pub fn finalize(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::cpg_finalize(handle.cpg_handle) };
+ if res == ffi::CS_OK {
+ HANDLE_HASH.lock().unwrap().remove(&handle.cpg_handle);
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+// Not sure if an FD is the right thing to return here, but it will do for now.
+/// Returns a file descriptor to use for poll/select on the CPG handle
+pub fn fd_get(handle: Handle) -> Result<i32> {
+ let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
+ let res = unsafe { ffi::cpg_fd_get(handle.cpg_handle, c_fd) };
+ if res == ffi::CS_OK {
+ Ok(c_fd as i32)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Call any/all active CPG callbacks for this [Handle] see [DispatchFlags] for details
+pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
+ let res = unsafe { ffi::cpg_dispatch(handle.cpg_handle, flags as u32) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Joins a CPG group for sending and receiving messages
+pub fn join(handle: Handle, group: &str) -> Result<()> {
+ let res = unsafe {
+ let c_group = string_to_cpg_name(group)?;
+ ffi::cpg_join(handle.cpg_handle, &c_group)
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Leave the currently joined CPG group, another group can now be joined on
+/// the same [Handle] or [finalize] can be called to finish using CPG
+pub fn leave(handle: Handle, group: &str) -> Result<()> {
+ let res = unsafe {
+ let c_group = string_to_cpg_name(group)?;
+ ffi::cpg_leave(handle.cpg_handle, &c_group)
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Get the local node ID
+pub fn local_get(handle: Handle) -> Result<NodeId> {
+ let mut nodeid: u32 = 0;
+ let res = unsafe { ffi::cpg_local_get(handle.cpg_handle, &mut nodeid) };
+ if res == ffi::CS_OK {
+ Ok(NodeId::from(nodeid))
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Get a list of members of a CPG group as a vector of [Address] structs
+pub fn membership_get(handle: Handle, group: &str) -> Result<Vec<Address>> {
+ let mut member_list_entries: i32 = 0;
+ let member_list = [ffi::cpg_address {
+ nodeid: 0,
+ pid: 0,
+ reason: 0,
+ }; CPG_MEMBERS_MAX];
+ let res = unsafe {
+ let mut c_group = string_to_cpg_name(group)?;
+ let c_memlist = member_list.as_ptr() as *mut ffi::cpg_address;
+ ffi::cpg_membership_get(
+ handle.cpg_handle,
+ &mut c_group,
+ &mut *c_memlist,
+ &mut member_list_entries,
+ )
+ };
+ if res == ffi::CS_OK {
+ Ok(cpg_array_to_vec(
+ member_list.as_ptr(),
+ member_list_entries as usize,
+ ))
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Get the maximum size that CPG can send in one corosync message,
+/// any messages sent via [mcast_joined] that are larger than this
+/// will be fragmented
+pub fn max_atomic_msgsize_get(handle: Handle) -> Result<u32> {
+ let mut asize: u32 = 0;
+ let res = unsafe { ffi::cpg_max_atomic_msgsize_get(handle.cpg_handle, &mut asize) };
+ if res == ffi::CS_OK {
+ Ok(asize)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Get the current 'context' value for this handle.
+/// The context value is an arbitrary value that is always passed
+/// back to callbacks to help identify the source
+pub fn context_get(handle: Handle) -> Result<u64> {
+ let mut c_context: *mut c_void = &mut 0u64 as *mut _ as *mut c_void;
+ let (res, context) = unsafe {
+ let r = ffi::cpg_context_get(handle.cpg_handle, &mut c_context);
+ let context: u64 = c_context as u64;
+ (r, context)
+ };
+ if res == ffi::CS_OK {
+ Ok(context)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Set the current 'context' value for this handle.
+/// The context value is an arbitrary value that is always passed
+/// back to callbacks to help identify the source.
+/// Normally this is set in [initialize], but this allows it to be changed
+pub fn context_set(handle: Handle, context: u64) -> Result<()> {
+ let res = unsafe {
+ let c_context = context as *mut c_void;
+ ffi::cpg_context_set(handle.cpg_handle, c_context)
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Get the flow control state of corosync CPG
+pub fn flow_control_state_get(handle: Handle) -> Result<bool> {
+ let mut fc_state: u32 = 0;
+ let res = unsafe { ffi::cpg_flow_control_state_get(handle.cpg_handle, &mut fc_state) };
+ if res == ffi::CS_OK {
+ if fc_state == 1 {
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Send a message to the currently joined CPG group
+pub fn mcast_joined(handle: Handle, guarantee: Guarantee, msg: &[u8]) -> Result<()> {
+ let c_iovec = ffi::iovec {
+ iov_base: msg.as_ptr() as *mut c_void,
+ iov_len: msg.len(),
+ };
+ let res = unsafe { ffi::cpg_mcast_joined(handle.cpg_handle, guarantee.to_c(), &c_iovec, 1) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Type of iteration for [CpgIterStart]
+#[derive(Copy, Clone)]
+pub enum CpgIterType {
+ NameOnly = 1,
+ OneGroup = 2,
+ All = 3,
+}
+
+// Iterator based on information on this page. thank you!
+// https://stackoverflow.com/questions/30218886/how-to-implement-iterator-and-intoiterator-for-a-simple-struct
+// Object to iterate over
+/// An object to iterate over a list of CPG groups, create one of these and then use 'for' over it
+pub struct CpgIterStart {
+ iter_handle: u64,
+}
+
+/// struct returned from iterating over a [CpgIterStart]
+pub struct CpgIter {
+ pub group: String,
+ pub nodeid: NodeId,
+ pub pid: u32,
+}
+
+pub struct CpgIntoIter {
+ iter_handle: u64,
+}
+
+impl fmt::Debug for CpgIter {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "[group: {}, nodeid: {}, pid: {}]",
+ self.group, self.nodeid, self.pid
+ )
+ }
+}
+
+impl Iterator for CpgIntoIter {
+ type Item = CpgIter;
+
+ fn next(&mut self) -> Option<CpgIter> {
+ let mut c_iter_description = ffi::cpg_iteration_description_t {
+ nodeid: 0,
+ pid: 0,
+ group: ffi::cpg_name {
+ length: 0_u32,
+ value: [0; CPG_NAMELEN_MAX],
+ },
+ };
+ let res = unsafe { ffi::cpg_iteration_next(self.iter_handle, &mut c_iter_description) };
+
+ if res == ffi::CS_OK {
+ let r_group =
+ match string_from_bytes(c_iter_description.group.value.as_ptr(), CPG_NAMELEN_MAX) {
+ Ok(groupname) => groupname,
+ Err(_) => return None,
+ };
+ Some(CpgIter {
+ group: r_group,
+ nodeid: NodeId::from(c_iter_description.nodeid),
+ pid: c_iter_description.pid,
+ })
+ } else if res == ffi::CS_ERR_NO_SECTIONS {
+ // End of list
+ unsafe {
+ // Yeah, we don't check this return code. There's nowhere to report it.
+ ffi::cpg_iteration_finalize(self.iter_handle)
+ };
+ None
+ } else {
+ None
+ }
+ }
+}
+
+impl CpgIterStart {
+ /// Create a new [CpgIterStart] object for iterating over a list of active CPG groups
+ pub fn new(cpg_handle: Handle, group: &str, iter_type: CpgIterType) -> Result<CpgIterStart> {
+ let mut iter_handle: u64 = 0;
+ let res = unsafe {
+ let mut c_group = string_to_cpg_name(group)?;
+ let c_itertype = iter_type as u32;
+ // IterType 'All' requires that the group pointer is passed in as NULL
+ let c_group_ptr = {
+ match iter_type {
+ CpgIterType::All => std::ptr::null_mut(),
+ _ => &mut c_group,
+ }
+ };
+ ffi::cpg_iteration_initialize(
+ cpg_handle.cpg_handle,
+ c_itertype,
+ c_group_ptr,
+ &mut iter_handle,
+ )
+ };
+ if res == ffi::CS_OK {
+ Ok(CpgIterStart { iter_handle })
+ } else {
+ Err(CsError::from_c(res))
+ }
+ }
+}
+
+impl IntoIterator for CpgIterStart {
+ type Item = CpgIter;
+ type IntoIter = CpgIntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ CpgIntoIter {
+ iter_handle: self.iter_handle,
+ }
+ }
+}
diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs
new file mode 100644
index 0000000..dbf34fc
--- /dev/null
+++ b/bindings/rust/src/lib.rs
@@ -0,0 +1,296 @@
+//! This crate provides access to the corosync libraries cpg, cfg, cmap, quorum & votequorum
+//! from Rust. They are a fairly thin layer around the actual API calls but with Rust data types
+//! and iterators.
+//!
+//! Corosync is a low-level provider of cluster services for high-availability clusters,
+//! for more information about corosync see <https://corosync.github.io/corosync/>
+//!
+//! No more information about corosync itself will be provided here, it is expected that if
+//! you feel you need access to the Corosync API calls, you know what they do :)
+//!
+//! # Example
+//! ```
+//! extern crate rust_corosync as corosync;
+//! use corosync::cmap;
+//!
+//! fn main()
+//! {
+//! // Open connection to corosync libcmap
+//! let handle =
+//! match cmap::initialize(cmap::Map::Icmap) {
+//! Ok(h) => {
+//! println!("cmap initialized.");
+//! h
+//! }
+//! Err(e) => {
+//! println!("Error in CMAP (Icmap) init: {}", e);
+//! return;
+//! }
+//! };
+//!
+//! // Set a numeric value (this is a generic fn)
+//! match cmap::set_number(handle, "test.test_uint32", 456)
+//! {
+//! Ok(_) => {}
+//! Err(e) => {
+//! println!("Error in CMAP set_u32: {}", e);
+//! return;
+//! }
+//! };
+//!
+//! // Get a value - this will be a Data struct
+//! match cmap::get(handle, "test.test_uint32")
+//! {
+//! Ok(v) => {
+//! println!("GOT value {}", v);
+//! }
+//! Err(e) => {
+//! println!("Error in CMAP get: {}", e);
+//! return;
+//! }
+//! };
+//!
+//! // Use an iterator
+//! match cmap::CmapIterStart::new(handle, "totem.") {
+//! Ok(cmap_iter) => {
+//! for i in cmap_iter {
+//! println!("ITER: {:?}", i);
+//! }
+//! println!("");
+//! }
+//! Err(e) => {
+//! println!("Error in CMAP iter start: {}", e);
+//! }
+//! }
+//!
+//! // Close this connection
+//! match cmap::finalize(handle)
+//! {
+//! Ok(_) => {}
+//! Err(e) => {
+//! println!("Error in CMAP get: {}", e);
+//! return;
+//! }
+//! };
+//! }
+
+#[macro_use]
+extern crate lazy_static;
+#[macro_use]
+extern crate bitflags;
+
+/// cfg is the internal configuration and information library for corosync, it is
+/// mainly used by internal tools but may also contain API calls useful to some applications
+/// that need detailed information about or control of the operation of corosync and the cluster.
+pub mod cfg;
+/// cmap is the internal 'database' of corosync - though it is NOT replicated. Mostly it contains
+/// a copy of the corosync.conf file and information about the running state of the daemon.
+/// The cmap API provides two 'maps'. Icmap, which is as above, and Stats, which contains very detailed
+/// statistics on the running system, this includes network and IPC calls.
+pub mod cmap;
+/// cpg is the Control Process Groups subsystem of corosync and is usually used for sending
+/// messages around the cluster. All processes using CPG belong to a named group (whose members
+/// they can query) and all messages are sent with delivery guarantees.
+pub mod cpg;
+/// Quorum provides basic information about the quorate state of the cluster with callbacks
+/// when nodelists change.
+pub mod quorum;
+///votequorum is the main quorum provider for corosync, using this API, users can query the state
+/// of nodes in the cluster, request callbacks when the nodelists change, and set up a quorum device.
+pub mod votequorum;
+
+mod sys;
+
+use num_enum::TryFromPrimitive;
+use std::convert::TryFrom;
+use std::error::Error;
+use std::ffi::CString;
+use std::fmt;
+use std::ptr::copy_nonoverlapping;
+
+// This needs to be kept up-to-date!
+/// Error codes returned from the corosync libraries
+#[derive(Debug, Eq, PartialEq, Copy, Clone, TryFromPrimitive)]
+#[repr(u32)]
+pub enum CsError {
+ CsOk = 1,
+ CsErrLibrary = 2,
+ CsErrVersion = 3,
+ CsErrInit = 4,
+ CsErrTimeout = 5,
+ CsErrTryAgain = 6,
+ CsErrInvalidParam = 7,
+ CsErrNoMemory = 8,
+ CsErrBadHandle = 9,
+ CsErrBusy = 10,
+ CsErrAccess = 11,
+ CsErrNotExist = 12,
+ CsErrNameTooLong = 13,
+ CsErrExist = 14,
+ CsErrNoSpace = 15,
+ CsErrInterrupt = 16,
+ CsErrNameNotFound = 17,
+ CsErrNoResources = 18,
+ CsErrNotSupported = 19,
+ CsErrBadOperation = 20,
+ CsErrFailedOperation = 21,
+ CsErrMessageError = 22,
+ CsErrQueueFull = 23,
+ CsErrQueueNotAvailable = 24,
+ CsErrBadFlags = 25,
+ CsErrTooBig = 26,
+ CsErrNoSection = 27,
+ CsErrContextNotFound = 28,
+ CsErrTooManyGroups = 30,
+ CsErrSecurity = 100,
+ #[num_enum(default)]
+ CsErrRustCompat = 998, // Set if we get a unknown return from corosync
+ CsErrRustString = 999, // Set if we get a string conversion error
+}
+
+/// Result type returned from most corosync library calls.
+/// Contains a [CsError] and possibly other data as required
+pub type Result<T> = ::std::result::Result<T, CsError>;
+
+impl fmt::Display for CsError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ CsError::CsOk => write!(f, "OK"),
+ CsError::CsErrLibrary => write!(f, "ErrLibrary"),
+ CsError::CsErrVersion => write!(f, "ErrVersion"),
+ CsError::CsErrInit => write!(f, "ErrInit"),
+ CsError::CsErrTimeout => write!(f, "ErrTimeout"),
+ CsError::CsErrTryAgain => write!(f, "ErrTryAgain"),
+ CsError::CsErrInvalidParam => write!(f, "ErrInvalidParam"),
+ CsError::CsErrNoMemory => write!(f, "ErrNoMemory"),
+ CsError::CsErrBadHandle => write!(f, "ErrbadHandle"),
+ CsError::CsErrBusy => write!(f, "ErrBusy"),
+ CsError::CsErrAccess => write!(f, "ErrAccess"),
+ CsError::CsErrNotExist => write!(f, "ErrNotExist"),
+ CsError::CsErrNameTooLong => write!(f, "ErrNameTooLong"),
+ CsError::CsErrExist => write!(f, "ErrExist"),
+ CsError::CsErrNoSpace => write!(f, "ErrNoSpace"),
+ CsError::CsErrInterrupt => write!(f, "ErrInterrupt"),
+ CsError::CsErrNameNotFound => write!(f, "ErrNameNotFound"),
+ CsError::CsErrNoResources => write!(f, "ErrNoResources"),
+ CsError::CsErrNotSupported => write!(f, "ErrNotSupported"),
+ CsError::CsErrBadOperation => write!(f, "ErrBadOperation"),
+ CsError::CsErrFailedOperation => write!(f, "ErrFailedOperation"),
+ CsError::CsErrMessageError => write!(f, "ErrMEssageError"),
+ CsError::CsErrQueueFull => write!(f, "ErrQueueFull"),
+ CsError::CsErrQueueNotAvailable => write!(f, "ErrQueueNotAvailable"),
+ CsError::CsErrBadFlags => write!(f, "ErrBadFlags"),
+ CsError::CsErrTooBig => write!(f, "ErrTooBig"),
+ CsError::CsErrNoSection => write!(f, "ErrNoSection"),
+ CsError::CsErrContextNotFound => write!(f, "ErrContextNotFound"),
+ CsError::CsErrTooManyGroups => write!(f, "ErrTooManyGroups"),
+ CsError::CsErrSecurity => write!(f, "ErrSecurity"),
+ CsError::CsErrRustCompat => write!(f, "ErrRustCompat"),
+ CsError::CsErrRustString => write!(f, "ErrRustString"),
+ }
+ }
+}
+
+impl Error for CsError {}
+
+// This is dependant on the num_enum crate, converts a C cs_error_t into the Rust enum
+// There seems to be some debate as to whether this should be part of the language:
+// https://internals.rust-lang.org/t/pre-rfc-enum-from-integer/6348/25
+impl CsError {
+ fn from_c(cserr: u32) -> CsError {
+ match CsError::try_from(cserr) {
+ Ok(e) => e,
+ Err(_) => CsError::CsErrRustCompat,
+ }
+ }
+}
+
+/// Flags to use with dispatch functions, eg [cpg::dispatch]
+/// One will dispatch a single callback (blocking) and return.
+/// All will loop trying to dispatch all possible callbacks.
+/// Blocking is like All but will block between callbacks.
+/// OneNonBlocking will dispatch a single callback only if one is available,
+/// otherwise it will return even if no callback is available.
+#[derive(Copy, Clone)]
+// The numbers match the C enum, of course.
+pub enum DispatchFlags {
+ One = 1,
+ All = 2,
+ Blocking = 3,
+ OneNonblocking = 4,
+}
+
+/// Flags to use with (most) tracking API calls
+#[derive(Copy, Clone)]
+// Same here
+pub enum TrackFlags {
+ Current = 1,
+ Changes = 2,
+ ChangesOnly = 4,
+}
+
+/// A corosync nodeid
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct NodeId {
+ id: u32,
+}
+
+impl fmt::Display for NodeId {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.id)
+ }
+}
+
+// Conversion from a NodeId to and from u32
+impl From<u32> for NodeId {
+ fn from(id: u32) -> NodeId {
+ NodeId { id }
+ }
+}
+
+impl From<NodeId> for u32 {
+ fn from(nodeid: NodeId) -> u32 {
+ nodeid.id
+ }
+}
+
+// General internal routine to copy bytes from a C array into a Rust String
+fn string_from_bytes(bytes: *const ::std::os::raw::c_char, max_length: usize) -> Result<String> {
+ let mut newbytes = vec![0u8; max_length];
+
+ // Get length of the string in old-fashioned style
+ let mut length: usize = 0;
+ let mut count = 0;
+ let mut tmpbytes = bytes;
+ while count < max_length || length == 0 {
+ if unsafe { *tmpbytes } == 0 && length == 0 {
+ length = count;
+ break;
+ }
+ count += 1;
+ tmpbytes = unsafe { tmpbytes.offset(1) }
+ }
+
+ // Cope with an empty string
+ if length == 0 {
+ return Ok(String::new());
+ }
+
+ unsafe {
+ // We need to fully copy it, not shallow copy it.
+ // Messy casting on both parts of the copy here to get it to work on both signed
+ // and unsigned char machines
+ copy_nonoverlapping(bytes as *mut i8, newbytes.as_mut_ptr() as *mut i8, length);
+ }
+
+ let cs = match CString::new(&newbytes[0..length]) {
+ Ok(c1) => c1,
+ Err(_) => return Err(CsError::CsErrRustString),
+ };
+
+ // This is just to convert the error type
+ match cs.into_string() {
+ Ok(s) => Ok(s),
+ Err(_) => Err(CsError::CsErrRustString),
+ }
+}
diff --git a/bindings/rust/src/quorum.rs b/bindings/rust/src/quorum.rs
new file mode 100644
index 0000000..25c2fe6
--- /dev/null
+++ b/bindings/rust/src/quorum.rs
@@ -0,0 +1,298 @@
+// libquorum interface for Rust
+// Copyright (c) 2021 Red Hat, Inc.
+//
+// All rights reserved.
+//
+// Author: Christine Caulfield (ccaulfi@redhat.com)
+//
+
+#![allow(clippy::type_complexity)]
+#![allow(clippy::needless_range_loop)]
+#![allow(clippy::single_match)]
+
+// For the code generated by bindgen
+use crate::sys::quorum as ffi;
+
+use crate::{CsError, DispatchFlags, NodeId, Result, TrackFlags};
+use std::collections::HashMap;
+use std::os::raw::{c_int, c_void};
+use std::slice;
+use std::sync::Mutex;
+
+/// Data for model1 [initialize]
+#[derive(Copy, Clone)]
+pub enum ModelData {
+ ModelNone,
+ ModelV1(Model1Data),
+}
+
+/// Value returned from [initialize]. Indicates whether quorum is currently active on this cluster.
+pub enum QuorumType {
+ Free,
+ Set,
+}
+
+/// Flags for [initialize], none currently supported
+#[derive(Copy, Clone)]
+pub enum Model1Flags {
+ None,
+}
+
+/// RingId returned in quorum_notification_fn
+pub struct RingId {
+ pub nodeid: NodeId,
+ pub seq: u64,
+}
+
+// Used to convert a QUORUM handle into one of ours
+lazy_static! {
+ static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
+}
+
+fn list_to_vec(list_entries: u32, list: *const u32) -> Vec<NodeId> {
+ let mut r_member_list = Vec::<NodeId>::new();
+ let temp_members: &[u32] = unsafe { slice::from_raw_parts(list, list_entries as usize) };
+ for i in 0..list_entries as usize {
+ r_member_list.push(NodeId::from(temp_members[i]));
+ }
+ r_member_list
+}
+
+// Called from quorum callback function - munge params back to Rust from C
+extern "C" fn rust_quorum_notification_fn(
+ handle: ffi::quorum_handle_t,
+ quorate: u32,
+ ring_id: ffi::quorum_ring_id,
+ member_list_entries: u32,
+ member_list: *const u32,
+) {
+ if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
+ let r_ring_id = RingId {
+ nodeid: NodeId::from(ring_id.nodeid),
+ seq: ring_id.seq,
+ };
+ let r_member_list = list_to_vec(member_list_entries, member_list);
+ let r_quorate = match quorate {
+ 0 => false,
+ 1 => true,
+ _ => false,
+ };
+ match &h.model_data {
+ ModelData::ModelV1(md) => {
+ if let Some(cb) = md.quorum_notification_fn {
+ (cb)(h, r_quorate, r_ring_id, r_member_list);
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+extern "C" fn rust_nodelist_notification_fn(
+ handle: ffi::quorum_handle_t,
+ ring_id: ffi::quorum_ring_id,
+ member_list_entries: u32,
+ member_list: *const u32,
+ joined_list_entries: u32,
+ joined_list: *const u32,
+ left_list_entries: u32,
+ left_list: *const u32,
+) {
+ if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
+ let r_ring_id = RingId {
+ nodeid: NodeId::from(ring_id.nodeid),
+ seq: ring_id.seq,
+ };
+
+ let r_member_list = list_to_vec(member_list_entries, member_list);
+ let r_joined_list = list_to_vec(joined_list_entries, joined_list);
+ let r_left_list = list_to_vec(left_list_entries, left_list);
+
+ match &h.model_data {
+ ModelData::ModelV1(md) => {
+ if let Some(cb) = md.nodelist_notification_fn {
+ (cb)(h, r_ring_id, r_member_list, r_joined_list, r_left_list);
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+#[derive(Copy, Clone)]
+/// Data for model1 [initialize]
+pub struct Model1Data {
+ pub flags: Model1Flags,
+ pub quorum_notification_fn:
+ Option<fn(hande: &Handle, quorate: bool, ring_id: RingId, member_list: Vec<NodeId>)>,
+ pub nodelist_notification_fn: Option<
+ fn(
+ hande: &Handle,
+ ring_id: RingId,
+ member_list: Vec<NodeId>,
+ joined_list: Vec<NodeId>,
+ left_list: Vec<NodeId>,
+ ),
+ >,
+}
+
+/// A handle into the quorum library. Returned from [initialize] and needed for all other calls
+#[derive(Copy, Clone)]
+pub struct Handle {
+ quorum_handle: u64,
+ model_data: ModelData,
+}
+
+/// Initialize a connection to the quorum library. You must call this before doing anything
+/// else and use the passed back [Handle].
+/// Remember to free the handle using [finalize] when finished.
+pub fn initialize(model_data: &ModelData, context: u64) -> Result<(Handle, QuorumType)> {
+ let mut handle: ffi::quorum_handle_t = 0;
+ let mut quorum_type: u32 = 0;
+
+ let mut m = match model_data {
+ ModelData::ModelV1(_v1) => ffi::quorum_model_v1_data_t {
+ model: ffi::QUORUM_MODEL_V1,
+ quorum_notify_fn: Some(rust_quorum_notification_fn),
+ nodelist_notify_fn: Some(rust_nodelist_notification_fn),
+ },
+ // Only V1 supported. No point in doing legacy stuff in a new binding
+ _ => return Err(CsError::CsErrInvalidParam),
+ };
+
+ handle = unsafe {
+ let c_context: *mut c_void = &mut &context as *mut _ as *mut c_void;
+ let c_model: *mut ffi::quorum_model_data_t =
+ &mut m as *mut _ as *mut ffi::quorum_model_data_t;
+ let res = ffi::quorum_model_initialize(
+ &mut handle,
+ m.model,
+ c_model,
+ &mut quorum_type,
+ c_context,
+ );
+
+ if res == ffi::CS_OK {
+ handle
+ } else {
+ return Err(CsError::from_c(res));
+ }
+ };
+
+ let quorum_type = match quorum_type {
+ 0 => QuorumType::Free,
+ 1 => QuorumType::Set,
+ _ => QuorumType::Set,
+ };
+ let rhandle = Handle {
+ quorum_handle: handle,
+ model_data: *model_data,
+ };
+ HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
+ Ok((rhandle, quorum_type))
+}
+
+/// Finish with a connection to corosync
+pub fn finalize(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::quorum_finalize(handle.quorum_handle) };
+ if res == ffi::CS_OK {
+ HANDLE_HASH.lock().unwrap().remove(&handle.quorum_handle);
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+// Not sure if an FD is the right thing to return here, but it will do for now.
+/// Return a file descriptor to use for poll/select on the QUORUM handle
+pub fn fd_get(handle: Handle) -> Result<i32> {
+ let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
+ let res = unsafe { ffi::quorum_fd_get(handle.quorum_handle, c_fd) };
+ if res == ffi::CS_OK {
+ Ok(c_fd as i32)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Display any/all active QUORUM callbacks for this [Handle], see [DispatchFlags] for details
+pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
+ let res = unsafe { ffi::quorum_dispatch(handle.quorum_handle, flags as u32) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Return the quorate status of the cluster
+pub fn getquorate(handle: Handle) -> Result<bool> {
+ let c_quorate: *mut c_int = &mut 0 as *mut _ as *mut c_int;
+ let (res, r_quorate) = unsafe {
+ let res = ffi::quorum_getquorate(handle.quorum_handle, c_quorate);
+ let r_quorate: i32 = *c_quorate;
+ (res, r_quorate)
+ };
+ if res == ffi::CS_OK {
+ match r_quorate {
+ 0 => Ok(false),
+ 1 => Ok(true),
+ _ => Err(CsError::CsErrLibrary),
+ }
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Track node and quorum changes
+pub fn trackstart(handle: Handle, flags: TrackFlags) -> Result<()> {
+ let res = unsafe { ffi::quorum_trackstart(handle.quorum_handle, flags as u32) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Stop tracking node and quorum changes
+pub fn trackstop(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::quorum_trackstop(handle.quorum_handle) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Get the current 'context' value for this handle.
+/// The context value is an arbitrary value that is always passed
+/// back to callbacks to help identify the source
+pub fn context_get(handle: Handle) -> Result<u64> {
+ let (res, context) = unsafe {
+ let mut context: u64 = 0;
+ let c_context: *mut c_void = &mut context as *mut _ as *mut c_void;
+ let r = ffi::quorum_context_get(handle.quorum_handle, c_context as *mut *const c_void);
+ (r, context)
+ };
+ if res == ffi::CS_OK {
+ Ok(context)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Set the current 'context' value for this handle.
+/// The context value is an arbitrary value that is always passed
+/// back to callbacks to help identify the source.
+/// Normally this is set in [initialize], but this allows it to be changed
+pub fn context_set(handle: Handle, context: u64) -> Result<()> {
+ let res = unsafe {
+ let c_context = context as *mut c_void;
+ ffi::quorum_context_set(handle.quorum_handle, c_context)
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
diff --git a/bindings/rust/src/sys/mod.rs b/bindings/rust/src/sys/mod.rs
new file mode 100644
index 0000000..03dfec2
--- /dev/null
+++ b/bindings/rust/src/sys/mod.rs
@@ -0,0 +1,7 @@
+#![allow(non_camel_case_types, non_snake_case, dead_code, improper_ctypes)]
+
+pub mod cfg;
+pub mod cmap;
+pub mod cpg;
+pub mod quorum;
+pub mod votequorum;
diff --git a/bindings/rust/src/votequorum.rs b/bindings/rust/src/votequorum.rs
new file mode 100644
index 0000000..4718b58
--- /dev/null
+++ b/bindings/rust/src/votequorum.rs
@@ -0,0 +1,501 @@
+// libvotequorum interface for Rust
+// Copyright (c) 2021 Red Hat, Inc.
+//
+// All rights reserved.
+//
+// Author: Christine Caulfield (ccaulfi@redhat.com)
+//
+
+#![allow(clippy::type_complexity)]
+#![allow(clippy::needless_range_loop)]
+#![allow(clippy::single_match)]
+
+// For the code generated by bindgen
+use crate::sys::votequorum as ffi;
+
+use std::collections::HashMap;
+use std::ffi::CString;
+use std::fmt;
+use std::os::raw::{c_int, c_void};
+use std::slice;
+use std::sync::Mutex;
+
+use crate::string_from_bytes;
+use crate::{CsError, DispatchFlags, NodeId, Result, TrackFlags};
+
+/// RingId returned by votequorum_notification_fn
+pub struct RingId {
+ pub nodeid: NodeId,
+ pub seq: u64,
+}
+
+// Used to convert a VOTEQUORUM handle into one of ours
+lazy_static! {
+ static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
+}
+
+/// Current state of a node in the cluster, part of the [NodeInfo] and [Node] structs
+pub enum NodeState {
+ Member,
+ Dead,
+ Leaving,
+ Unknown,
+}
+impl NodeState {
+ pub fn new(state: u32) -> NodeState {
+ match state {
+ 1 => NodeState::Member,
+ 2 => NodeState::Dead,
+ 3 => NodeState::Leaving,
+ _ => NodeState::Unknown,
+ }
+ }
+}
+impl fmt::Debug for NodeState {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ NodeState::Member => write!(f, "Member"),
+ NodeState::Dead => write!(f, "Dead"),
+ NodeState::Leaving => write!(f, "Leaving"),
+ _ => write!(f, "Unknown"),
+ }
+ }
+}
+
+/// Basic information about a node in the cluster. Contains [NodeId], and [NodeState]
+pub struct Node {
+ nodeid: NodeId,
+ state: NodeState,
+}
+impl fmt::Debug for Node {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "nodeid: {}, state: {:?}", self.nodeid, self.state)
+ }
+}
+
+bitflags! {
+/// Flags in the [NodeInfo] struct
+ pub struct NodeInfoFlags: u32
+ {
+ const VOTEQUORUM_INFO_TWONODE = 1;
+ const VOTEQUORUM_INFO_QUORATE = 2;
+ const VOTEQUORUM_INFO_WAIT_FOR_ALL = 4;
+ const VOTEQUORUM_INFO_LAST_MAN_STANDING = 8;
+ const VOTEQUORUM_INFO_AUTO_TIE_BREAKER = 16;
+ const VOTEQUORUM_INFO_ALLOW_DOWNSCALE = 32;
+ const VOTEQUORUM_INFO_QDEVICE_REGISTERED = 64;
+ const VOTEQUORUM_INFO_QDEVICE_ALIVE = 128;
+ const VOTEQUORUM_INFO_QDEVICE_CAST_VOTE = 256;
+ const VOTEQUORUM_INFO_QDEVICE_MASTER_WINS = 512;
+ }
+}
+
+/// Detailed information about a node in the cluster, returned from [get_info]
+pub struct NodeInfo {
+ pub node_id: NodeId,
+ pub node_state: NodeState,
+ pub node_votes: u32,
+ pub node_expected_votes: u32,
+ pub highest_expected: u32,
+ pub quorum: u32,
+ pub flags: NodeInfoFlags,
+ pub qdevice_votes: u32,
+ pub qdevice_name: String,
+}
+
+// Turn a C nodeID list into a vec of NodeIds
+fn list_to_vec(list_entries: u32, list: *const u32) -> Vec<NodeId> {
+ let mut r_member_list = Vec::<NodeId>::new();
+ let temp_members: &[u32] = unsafe { slice::from_raw_parts(list, list_entries as usize) };
+ for i in 0..list_entries as usize {
+ r_member_list.push(NodeId::from(temp_members[i]));
+ }
+ r_member_list
+}
+
+// Called from votequorum callback function - munge params back to Rust from C
+extern "C" fn rust_expectedvotes_notification_fn(
+ handle: ffi::votequorum_handle_t,
+ context: u64,
+ expected_votes: u32,
+) {
+ if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
+ if let Some(cb) = h.callbacks.expectedvotes_notification_fn {
+ (cb)(h, context, expected_votes);
+ }
+ }
+}
+
+// Called from votequorum callback function - munge params back to Rust from C
+extern "C" fn rust_quorum_notification_fn(
+ handle: ffi::votequorum_handle_t,
+ context: u64,
+ quorate: u32,
+ node_list_entries: u32,
+ node_list: *mut ffi::votequorum_node_t,
+) {
+ if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
+ let r_quorate = match quorate {
+ 0 => false,
+ 1 => true,
+ _ => false,
+ };
+ let mut r_node_list = Vec::<Node>::new();
+ let temp_members: &[ffi::votequorum_node_t] =
+ unsafe { slice::from_raw_parts(node_list, node_list_entries as usize) };
+ for i in 0..node_list_entries as usize {
+ r_node_list.push(Node {
+ nodeid: NodeId::from(temp_members[i].nodeid),
+ state: NodeState::new(temp_members[i].state),
+ });
+ }
+ if let Some(cb) = h.callbacks.quorum_notification_fn {
+ (cb)(h, context, r_quorate, r_node_list);
+ }
+ }
+}
+
+// Called from votequorum callback function - munge params back to Rust from C
+extern "C" fn rust_nodelist_notification_fn(
+ handle: ffi::votequorum_handle_t,
+ context: u64,
+ ring_id: ffi::votequorum_ring_id_t,
+ node_list_entries: u32,
+ node_list: *mut u32,
+) {
+ if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
+ let r_ring_id = RingId {
+ nodeid: NodeId::from(ring_id.nodeid),
+ seq: ring_id.seq,
+ };
+
+ let r_node_list = list_to_vec(node_list_entries, node_list);
+
+ if let Some(cb) = h.callbacks.nodelist_notification_fn {
+ (cb)(h, context, r_ring_id, r_node_list);
+ }
+ }
+}
+
+/// Callbacks that can be called from votequorum, pass these in to [initialize]
+#[derive(Copy, Clone)]
+pub struct Callbacks {
+ pub quorum_notification_fn:
+ Option<fn(hande: &Handle, context: u64, quorate: bool, node_list: Vec<Node>)>,
+ pub nodelist_notification_fn:
+ Option<fn(hande: &Handle, context: u64, ring_id: RingId, node_list: Vec<NodeId>)>,
+ pub expectedvotes_notification_fn:
+ Option<fn(handle: &Handle, context: u64, expected_votes: u32)>,
+}
+
+/// A handle into the votequorum library. Returned from [initialize] and needed for all other calls
+#[derive(Copy, Clone)]
+pub struct Handle {
+ votequorum_handle: u64,
+ callbacks: Callbacks,
+}
+
+/// Initialize a connection to the votequorum library. You must call this before doing anything
+/// else and use the passed back [Handle].
+/// Remember to free the handle using [finalize] when finished.
+pub fn initialize(callbacks: &Callbacks) -> Result<Handle> {
+ let mut handle: ffi::votequorum_handle_t = 0;
+
+ let mut c_callbacks = ffi::votequorum_callbacks_t {
+ votequorum_quorum_notify_fn: Some(rust_quorum_notification_fn),
+ votequorum_nodelist_notify_fn: Some(rust_nodelist_notification_fn),
+ votequorum_expectedvotes_notify_fn: Some(rust_expectedvotes_notification_fn),
+ };
+
+ unsafe {
+ let res = ffi::votequorum_initialize(&mut handle, &mut c_callbacks);
+ if res == ffi::CS_OK {
+ let rhandle = Handle {
+ votequorum_handle: handle,
+ callbacks: *callbacks,
+ };
+ HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
+ Ok(rhandle)
+ } else {
+ Err(CsError::from_c(res))
+ }
+ }
+}
+
+/// Finish with a connection to corosync
+pub fn finalize(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::votequorum_finalize(handle.votequorum_handle) };
+ if res == ffi::CS_OK {
+ HANDLE_HASH
+ .lock()
+ .unwrap()
+ .remove(&handle.votequorum_handle);
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+// Not sure if an FD is the right thing to return here, but it will do for now.
+/// Return a file descriptor to use for poll/select on the VOTEQUORUM handle
+pub fn fd_get(handle: Handle) -> Result<i32> {
+ let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
+ let res = unsafe { ffi::votequorum_fd_get(handle.votequorum_handle, c_fd) };
+ if res == ffi::CS_OK {
+ Ok(c_fd as i32)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+const VOTEQUORUM_QDEVICE_MAX_NAME_LEN: usize = 255;
+
+/// Returns detailed information about a node in a [NodeInfo] structure
+pub fn get_info(handle: Handle, nodeid: NodeId) -> Result<NodeInfo> {
+ let mut c_info = ffi::votequorum_info {
+ node_id: 0,
+ node_state: 0,
+ node_votes: 0,
+ node_expected_votes: 0,
+ highest_expected: 0,
+ total_votes: 0,
+ quorum: 0,
+ flags: 0,
+ qdevice_votes: 0,
+ qdevice_name: [0; 255usize],
+ };
+ let res = unsafe {
+ ffi::votequorum_getinfo(handle.votequorum_handle, u32::from(nodeid), &mut c_info)
+ };
+
+ if res == ffi::CS_OK {
+ let info = NodeInfo {
+ node_id: NodeId::from(c_info.node_id),
+ node_state: NodeState::new(c_info.node_state),
+ node_votes: c_info.node_votes,
+ node_expected_votes: c_info.node_expected_votes,
+ highest_expected: c_info.highest_expected,
+ quorum: c_info.quorum,
+ flags: NodeInfoFlags { bits: c_info.flags },
+ qdevice_votes: c_info.qdevice_votes,
+ qdevice_name: match string_from_bytes(
+ c_info.qdevice_name.as_ptr(),
+ VOTEQUORUM_QDEVICE_MAX_NAME_LEN,
+ ) {
+ Ok(s) => s,
+ Err(_) => String::new(),
+ },
+ };
+ Ok(info)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Call any/all active votequorum callbacks for this [Handle]. see [DispatchFlags] for details
+pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
+ let res = unsafe { ffi::votequorum_dispatch(handle.votequorum_handle, flags as u32) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Track node and votequorum changes
+pub fn trackstart(handle: Handle, context: u64, flags: TrackFlags) -> Result<()> {
+ let res =
+ unsafe { ffi::votequorum_trackstart(handle.votequorum_handle, context, flags as u32) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Stop tracking node and votequorum changes
+pub fn trackstop(handle: Handle) -> Result<()> {
+ let res = unsafe { ffi::votequorum_trackstop(handle.votequorum_handle) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Get the current 'context' value for this handle.
+/// The context value is an arbitrary value that is always passed
+/// back to callbacks to help identify the source
+pub fn context_get(handle: Handle) -> Result<u64> {
+ let (res, context) = unsafe {
+ let mut c_context: *mut c_void = &mut 0u64 as *mut _ as *mut c_void;
+ let r = ffi::votequorum_context_get(handle.votequorum_handle, &mut c_context);
+ let context: u64 = c_context as u64;
+ (r, context)
+ };
+ if res == ffi::CS_OK {
+ Ok(context)
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Set the current 'context' value for this handle.
+/// The context value is an arbitrary value that is always passed
+/// back to callbacks to help identify the source.
+/// Normally this is set in [trackstart], but this allows it to be changed
+pub fn context_set(handle: Handle, context: u64) -> Result<()> {
+ let res = unsafe {
+ let c_context = context as *mut c_void;
+ ffi::votequorum_context_set(handle.votequorum_handle, c_context)
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Set the current expected_votes for the cluster, this value must
+/// be valid and not result in an inquorate cluster.
+pub fn set_expected(handle: Handle, expected_votes: u32) -> Result<()> {
+ let res = unsafe { ffi::votequorum_setexpected(handle.votequorum_handle, expected_votes) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Set the current votes for a node
+pub fn set_votes(handle: Handle, nodeid: NodeId, votes: u32) -> Result<()> {
+ let res =
+ unsafe { ffi::votequorum_setvotes(handle.votequorum_handle, u32::from(nodeid), votes) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Register a quorum device
+pub fn qdevice_register(handle: Handle, name: &str) -> Result<()> {
+ let c_string = {
+ match CString::new(name) {
+ Ok(cs) => cs,
+ Err(_) => return Err(CsError::CsErrInvalidParam),
+ }
+ };
+
+ let res =
+ unsafe { ffi::votequorum_qdevice_register(handle.votequorum_handle, c_string.as_ptr()) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Unregister a quorum device
+pub fn qdevice_unregister(handle: Handle, name: &str) -> Result<()> {
+ let c_string = {
+ match CString::new(name) {
+ Ok(cs) => cs,
+ Err(_) => return Err(CsError::CsErrInvalidParam),
+ }
+ };
+
+ let res =
+ unsafe { ffi::votequorum_qdevice_unregister(handle.votequorum_handle, c_string.as_ptr()) };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Update the name of a quorum device
+pub fn qdevice_update(handle: Handle, oldname: &str, newname: &str) -> Result<()> {
+ let on_string = {
+ match CString::new(oldname) {
+ Ok(cs) => cs,
+ Err(_) => return Err(CsError::CsErrInvalidParam),
+ }
+ };
+ let nn_string = {
+ match CString::new(newname) {
+ Ok(cs) => cs,
+ Err(_) => return Err(CsError::CsErrInvalidParam),
+ }
+ };
+
+ let res = unsafe {
+ ffi::votequorum_qdevice_update(
+ handle.votequorum_handle,
+ on_string.as_ptr(),
+ nn_string.as_ptr(),
+ )
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Poll a quorum device
+/// This must be done more often than the qdevice timeout (default 10s) while the device is active
+/// and the [RingId] must match the current value returned from the callbacks for it to be accepted.
+pub fn qdevice_poll(handle: Handle, name: &str, cast_vote: bool, ring_id: &RingId) -> Result<()> {
+ let c_string = {
+ match CString::new(name) {
+ Ok(cs) => cs,
+ Err(_) => return Err(CsError::CsErrInvalidParam),
+ }
+ };
+
+ let c_cast_vote: u32 = u32::from(cast_vote);
+ let c_ring_id = ffi::votequorum_ring_id_t {
+ nodeid: u32::from(ring_id.nodeid),
+ seq: ring_id.seq,
+ };
+
+ let res = unsafe {
+ ffi::votequorum_qdevice_poll(
+ handle.votequorum_handle,
+ c_string.as_ptr(),
+ c_cast_vote,
+ c_ring_id,
+ )
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
+
+/// Allow qdevice to tell votequorum if master_wins can be enabled or not
+pub fn qdevice_master_wins(handle: Handle, name: &str, master_wins: bool) -> Result<()> {
+ let c_string = {
+ match CString::new(name) {
+ Ok(cs) => cs,
+ Err(_) => return Err(CsError::CsErrInvalidParam),
+ }
+ };
+
+ let c_master_wins: u32 = u32::from(master_wins);
+
+ let res = unsafe {
+ ffi::votequorum_qdevice_master_wins(
+ handle.votequorum_handle,
+ c_string.as_ptr(),
+ c_master_wins,
+ )
+ };
+ if res == ffi::CS_OK {
+ Ok(())
+ } else {
+ Err(CsError::from_c(res))
+ }
+}
diff --git a/bindings/rust/tests/Cargo.toml.in b/bindings/rust/tests/Cargo.toml.in
new file mode 100644
index 0000000..1d8fabf
--- /dev/null
+++ b/bindings/rust/tests/Cargo.toml.in
@@ -0,0 +1,36 @@
+[package]
+name = "rust-corosync-tests"
+version = "@corosyncrustver@"
+authors = ["Christine Caulfield <ccaulfie@redhat.com>"]
+edition = "2021"
+
+[dependencies]
+rust-corosync = { path = ".." }
+
+[build-dependencies]
+pkg-config = "0.3"
+
+[[bin]]
+name = "cpg-test"
+test = false
+bench = false
+
+[[bin]]
+name = "quorum-test"
+test = false
+bench = false
+
+[[bin]]
+name = "votequorum-test"
+test = false
+bench = false
+
+[[bin]]
+name = "cfg-test"
+test = false
+bench = false
+
+[[bin]]
+name = "cmap-test"
+test = false
+bench = false
diff --git a/bindings/rust/tests/Makefile.am b/bindings/rust/tests/Makefile.am
new file mode 100644
index 0000000..a8ac087
--- /dev/null
+++ b/bindings/rust/tests/Makefile.am
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+#
+# Author: Christine Caulfield <ccaulfie@redhat.com>
+#
+# This software licensed under GPL-2.0+
+#
+
+MAINTAINERCLEANFILES = Makefile.in
+
+include $(top_srcdir)/build-aux/rust.mk
+
+EXTRA_DIST = \
+ $(RUST_COMMON) \
+ $(RUST_SHIP_SRCS)
+
+RUST_SHIP_SRCS = src/bin/cpg-test.rs \
+ src/bin/cfg-test.rs \
+ src/bin/cmap-test.rs \
+ src/bin/quorum-test.rs \
+ src/bin/votequorum-test.rs
+
+# This will build all of the tests
+noinst_SCRIPTS = target/$(RUST_TARGET_DIR)/cpg-test
+
+clean-local: cargo-clean
diff --git a/bindings/rust/tests/Makefile.in b/bindings/rust/tests/Makefile.in
new file mode 100644
index 0000000..36cc52a
--- /dev/null
+++ b/bindings/rust/tests/Makefile.in
@@ -0,0 +1,625 @@
+# Makefile.in generated by automake 1.13.4 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+#
+# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
+#
+# Author: Christine Caulfield <ccaulfie@redhat.com>
+#
+# This software licensed under GPL-2.0+
+#
+
+#
+# Copyright (C) 2021-2022 Red Hat, Inc. All rights reserved.
+#
+# Author: Fabio M. Di Nitto <fabbione@kronosnet.org>
+#
+# This software licensed under GPL-2.0+
+#
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+DIST_COMMON = $(top_srcdir)/build-aux/rust.mk $(srcdir)/Makefile.in \
+ $(srcdir)/Makefile.am $(srcdir)/Cargo.toml.in
+subdir = bindings/rust/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/lib/libcfg.verso $(top_srcdir)/lib/libcpg.verso \
+ $(top_srcdir)/lib/libquorum.verso \
+ $(top_srcdir)/lib/libsam.verso \
+ $(top_srcdir)/lib/libvotequorum.verso \
+ $(top_srcdir)/lib/libcmap.verso $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/include/corosync/config.h
+CONFIG_CLEAN_FILES = Cargo.toml
+CONFIG_CLEAN_VPATH_FILES =
+SCRIPTS = $(noinst_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUGTOOL = @AUGTOOL@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BASHPATH = @BASHPATH@
+BINDGEN = @BINDGEN@
+CARGO = @CARGO@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFG_SONAME = @CFG_SONAME@
+CFLAGS = @CFLAGS@
+CLIPPY = @CLIPPY@
+CMAP_SONAME = @CMAP_SONAME@
+COROSYSCONFDIR = @COROSYSCONFDIR@
+CPG_SONAME = @CPG_SONAME@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DBUS_CFLAGS = @DBUS_CFLAGS@
+DBUS_LIBS = @DBUS_LIBS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOT = @DOT@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+GROFF = @GROFF@
+INITCONFIGDIR = @INITCONFIGDIR@
+INITDDIR = @INITDDIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBQB_CFLAGS = @LIBQB_CFLAGS@
+LIBQB_LIBS = @LIBQB_LIBS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LINT_FLAGS = @LINT_FLAGS@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOGDIR = @LOGDIR@
+LOGROTATEDIR = @LOGROTATEDIR@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUORUM_SONAME = @QUORUM_SONAME@
+RANLIB = @RANLIB@
+RUSTC = @RUSTC@
+RUSTDOC = @RUSTDOC@
+RUSTFMT = @RUSTFMT@
+RUST_FLAGS = @RUST_FLAGS@
+RUST_TARGET_DIR = @RUST_TARGET_DIR@
+SAM_SONAME = @SAM_SONAME@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SNMPCONFIG = @SNMPCONFIG@
+SNMP_LIBS = @SNMP_LIBS@
+SOMAJOR = @SOMAJOR@
+SOMICRO = @SOMICRO@
+SOMINOR = @SOMINOR@
+SONAME = @SONAME@
+STRIP = @STRIP@
+SYSTEMDDIR = @SYSTEMDDIR@
+VERSCRIPT_LDFLAGS = @VERSCRIPT_LDFLAGS@
+VERSION = @VERSION@
+VOTEQUORUM_SONAME = @VOTEQUORUM_SONAME@
+WITH_LIST = @WITH_LIST@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+corosyncrustver = @corosyncrustver@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+knet_CFLAGS = @knet_CFLAGS@
+knet_LIBS = @knet_LIBS@
+libdir = @libdir@
+libexecdir = @libexecdir@
+libsystemd_CFLAGS = @libsystemd_CFLAGS@
+libsystemd_LIBS = @libsystemd_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+nozzle_CFLAGS = @nozzle_CFLAGS@
+nozzle_LIBS = @nozzle_LIBS@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+statgrab_CFLAGS = @statgrab_CFLAGS@
+statgrab_LIBS = @statgrab_LIBS@
+statgrabge090_CFLAGS = @statgrabge090_CFLAGS@
+statgrabge090_LIBS = @statgrabge090_LIBS@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+MAINTAINERCLEANFILES = Makefile.in
+RUST_COMMON = \
+ build.rs.in
+
+RUST_SRCS = $(RUST_SHIP_SRCS) $(RUST_BUILT_SRCS)
+EXTRA_DIST = \
+ $(RUST_COMMON) \
+ $(RUST_SHIP_SRCS)
+
+RUST_SHIP_SRCS = src/bin/cpg-test.rs \
+ src/bin/cfg-test.rs \
+ src/bin/cmap-test.rs \
+ src/bin/quorum-test.rs \
+ src/bin/votequorum-test.rs
+
+
+# This will build all of the tests
+noinst_SCRIPTS = target/$(RUST_TARGET_DIR)/cpg-test
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/build-aux/rust.mk $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign bindings/rust/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign bindings/rust/tests/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+$(top_srcdir)/build-aux/rust.mk:
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+Cargo.toml: $(top_builddir)/config.status $(srcdir)/Cargo.toml.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(SCRIPTS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES)
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-local mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: all all-am check check-am check-local clean clean-generic \
+ clean-libtool clean-local cscopelist-am ctags-am distclean \
+ distclean-generic distclean-libtool distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags-am uninstall \
+ uninstall-am
+
+
+%.rlib: $(RUST_SRCS) Cargo.toml build.rs
+ PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(CARGO) build $(RUST_FLAGS)
+
+%-test: $(RUST_SRCS) Cargo.toml build.rs
+ PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(CARGO) build $(RUST_FLAGS)
+
+build.rs: build.rs.in
+ rm -f $@ $@-t
+ cat $^ | sed \
+ -e 's#@ABSTOPLEVELSRC@#$(abs_top_srcdir)#g' \
+ -e 's#@ABSTOPLEVELBUILD@#$(abs_top_builddir)#g' \
+ -e 's#@LIBQBLIBS@#$(LIBQB_LIBS)#g' \
+ > $@-t
+ chmod a-w $@-t
+ mv $@-t $@
+ rm -f $@-t
+
+cargo-tree-prep:
+ if [ "${abs_builddir}" != "${abs_srcdir}" ]; then \
+ echo "Generating builddir out-of-tree rust symlinks"; \
+ src_realpath=$(shell realpath ${abs_srcdir}); \
+ for i in `find "$$src_realpath/" -type d | \
+ grep -v "${abs_builddir}" | \
+ sed -e 's#^'$$src_realpath'/##g'`; do \
+ $(MKDIR_P) ${abs_builddir}/$${i}; \
+ done; \
+ find "$$src_realpath/" -type f | { while read src; do \
+ process=no; \
+ copy=no; \
+ case $$src in \
+ ${abs_builddir}*) \
+ ;; \
+ *Makefile.*|*.in) \
+ ;; \
+ *) \
+ process=yes; \
+ ;; \
+ esac ; \
+ dst=`echo $$src | sed -e 's#^'$$src_realpath'/##g'`; \
+ if [ $${process} == yes ]; then \
+ rm -f ${abs_builddir}/$$dst; \
+ $(LN_S) $$src ${abs_builddir}/$$dst; \
+ fi; \
+ if [ $${copy} == yes ]; then \
+ rm -f ${abs_builddir}/$$dst; \
+ cp $$src ${abs_builddir}/$$dst; \
+ chmod u+w ${abs_builddir}/$$dst; \
+ fi; \
+ done; }; \
+ fi
+
+cargo-clean:
+ -$(CARGO) clean
+ rm -rf Cargo.lock $(RUST_BUILT_SRCS) build.rs target/
+ if [ "${abs_builddir}" != "${abs_srcdir}" ]; then \
+ echo "Cleaning out-of-tree rust symlinks" ; \
+ find "${abs_builddir}/" -type l -delete; \
+ find "${abs_builddir}/" -type d -empty -delete; \
+ fi
+
+clippy-check:
+ $(CARGO) clippy --verbose --all-features -- -D warnings
+
+format-check:
+ if [ "${abs_builddir}" = "${abs_srcdir}" ]; then \
+ $(CARGO) fmt --all --check; \
+ else \
+ echo "!!!!! WARNING: skipping format check !!!!!"; \
+ fi
+
+doc-check:
+ $(CARGO) doc --verbose --all-features
+
+publish-check:
+ if [ -f "${abs_srcdir}/README.md" ]; then \
+ $(CARGO) publish --dry-run; \
+ fi
+
+crates-publish:
+ if [ -f "${abs_srcdir}/README.md" ]; then \
+ bindingname=`cat Cargo.toml | grep ^name | sed -e 's#.*= ##g' -e 's#"##g'` && \
+ cratesver=`cargo search $$bindingname | grep "^$$bindingname " | sed -e 's#.*= ##g' -e 's#"##g' -e 's/\+.*//g'` && \
+ testver=`echo $(localver) | sed -e 's/\+.*//g'` && \
+ if [ "$$cratesver" != "$$testver" ]; then \
+ $(CARGO) publish; \
+ fi; \
+ fi
+
+crates-check:
+ if [ -f "${abs_srcdir}/README.md" ]; then \
+ bindingname=`cat Cargo.toml | grep ^name | sed -e 's#.*= ##g' -e 's#"##g'` && \
+ cratesver=`cargo search $$bindingname | grep "^$$bindingname " | sed -e 's#.*= ##g' -e 's#"##g' -e 's/\+.*//g'` && \
+ testver=`echo $(localver) | sed -e 's/\+.*//g'` && \
+ if [ "$$cratesver" != "$$testver" ]; then \
+ echo "!!!!! WARNING !!!!!"; \
+ echo "!!!!! WARNING: $$bindingname local version ($$testver) is higher than the current published one on crates.io ($$cratesver)"; \
+ echo "!!!!! WARNING !!!!!"; \
+ fi; \
+ fi
+
+check-local: clippy-check format-check doc-check crates-check publish-check
+
+clean-local: cargo-clean
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/bindings/rust/tests/build.rs.in b/bindings/rust/tests/build.rs.in
new file mode 100644
index 0000000..39a97ba
--- /dev/null
+++ b/bindings/rust/tests/build.rs.in
@@ -0,0 +1,22 @@
+// Copyright (C) 2021-2023 Red Hat, Inc.
+//
+// All rights reserved.
+//
+// Author: Christine Caulfield (ccaulfi@redhat.com)
+//
+
+extern crate pkg_config;
+
+fn main() {
+ // Tell the compiler to use the build-tree libs & headers for compiling
+ println!("cargo:rustc-link-search=native=../../../lib/.libs/");
+ println!("cargo:rustc-link-search=native=../../../common_lib/.libs/");
+ println!("cargo:rustc-flags=@LIBQBLIBS@");
+ println!("cargo:rustc-link-lib=cpg");
+ println!("cargo:rustc-link-lib=cfg");
+ println!("cargo:rustc-link-lib=cmap");
+ println!("cargo:rustc-link-lib=quorum");
+ println!("cargo:rustc-link-lib=votequorum");
+ println!("cargo:rustc-link-lib=corosync_common");
+ println!("cargo:rustc-link-lib=qb");
+}
diff --git a/bindings/rust/tests/src/bin/cfg-test.rs b/bindings/rust/tests/src/bin/cfg-test.rs
new file mode 100644
index 0000000..cd70d38
--- /dev/null
+++ b/bindings/rust/tests/src/bin/cfg-test.rs
@@ -0,0 +1,135 @@
+// Test the CFG library. Requires that corosync is running and that we are root.
+
+extern crate rust_corosync as corosync;
+use corosync::{cfg, NodeId};
+
+use std::thread::spawn;
+
+fn dispatch_thread(handle: cfg::Handle) {
+ loop {
+ if cfg::dispatch(handle, corosync::DispatchFlags::One).is_err() {
+ return;
+ }
+ }
+}
+
+// Test the shutdown callback
+fn shutdown_check_fn(handle: &cfg::Handle, _flags: u32) {
+ println!("in shutdown callback");
+
+ // DON'T shutdown corosync - we're just testing
+ if let Err(e) = cfg::reply_to_shutdown(*handle, cfg::ShutdownReply::No) {
+ println!("Error in CFG replyto_shutdown: {e}");
+ }
+}
+
+fn main() {
+ // Initialise the callbacks data
+ let cb = cfg::Callbacks {
+ corosync_cfg_shutdown_callback_fn: Some(shutdown_check_fn),
+ };
+
+ let handle = match cfg::initialize(&cb) {
+ Ok(h) => {
+ println!("cfg initialized.");
+ h
+ }
+ Err(e) => {
+ println!("Error in CFG init: {e}");
+ return;
+ }
+ };
+
+ // Open two handles to CFG so that the second one can refuse shutdown
+ let handle2 = match cfg::initialize(&cb) {
+ Ok(h) => {
+ println!("cfg2 initialized.");
+ h
+ }
+ Err(e) => {
+ println!("Error in CFG init: {e}");
+ return;
+ }
+ };
+
+ match cfg::track_start(handle2, cfg::TrackFlags::None) {
+ Ok(_) => {
+ // Run handle2 dispatch in its own thread
+ spawn(move || dispatch_thread(handle2));
+ }
+ Err(e) => {
+ println!("Error in CFG track_start: {e}");
+ }
+ };
+
+ let local_nodeid = {
+ match cfg::local_get(handle) {
+ Ok(n) => {
+ println!("Local nodeid is {n}");
+ Some(n)
+ }
+ Err(e) => {
+ println!("Error in CFG local_get: {e}");
+ None
+ }
+ }
+ };
+
+ // Test node_status_get.
+ // node status for the local node looks odd (cos it's the loopback connection), so
+ // we try for a node ID one less or more than us just to get output that looks
+ // sensible to the user.
+ if let Some(our_nodeid) = local_nodeid {
+ let us_plus1 = NodeId::from(u32::from(our_nodeid) + 1);
+ let us_less1 = NodeId::from(u32::from(our_nodeid) - 1);
+ let mut res = cfg::node_status_get(handle, us_plus1, cfg::NodeStatusVersion::V1);
+ if let Err(e) = res {
+ println!("Error from node_status_get on nodeid {us_plus1}: {e}");
+ res = cfg::node_status_get(handle, us_less1, cfg::NodeStatusVersion::V1);
+ };
+ match res {
+ Ok(ns) => {
+ println!("Node Status for nodeid {}", ns.nodeid);
+ println!(" reachable: {}", ns.reachable);
+ println!(" remote: {}", ns.remote);
+ println!(" onwire_min: {}", ns.onwire_min);
+ println!(" onwire_max: {}", ns.onwire_max);
+ println!(" onwire_ver: {}", ns.onwire_ver);
+ for (ls_num, ls) in ns.link_status.iter().enumerate() {
+ if ls.enabled {
+ println!(" Link {ls_num}");
+ println!(" connected: {}", ls.connected);
+ println!(" mtu: {}", ls.mtu);
+ println!(" src: {}", ls.src_ipaddr);
+ println!(" dst: {}", ls.dst_ipaddr);
+ }
+ }
+ }
+ Err(e) => {
+ println!(
+ "Error in CFG node_status get: {e} (tried nodeids {us_plus1} & {us_less1})"
+ );
+ }
+ }
+ }
+
+ // This should not shutdown corosync because the callback on handle2 will refuse it.
+ match cfg::try_shutdown(handle, cfg::ShutdownFlags::Request) {
+ Ok(_) => {
+ println!("CFG try_shutdown suceeded, should return busy");
+ }
+ Err(e) => {
+ if e != corosync::CsError::CsErrBusy {
+ println!("Error in CFG try_shutdown: {e}");
+ }
+ }
+ }
+
+ // Wait for events
+ loop {
+ if cfg::dispatch(handle, corosync::DispatchFlags::One).is_err() {
+ break;
+ }
+ }
+ println!("ERROR: Corosync quit");
+}
diff --git a/bindings/rust/tests/src/bin/cmap-test.rs b/bindings/rust/tests/src/bin/cmap-test.rs
new file mode 100644
index 0000000..f435653
--- /dev/null
+++ b/bindings/rust/tests/src/bin/cmap-test.rs
@@ -0,0 +1,195 @@
+// Test the CMAP library. Requires that corosync is running and that we are root.
+
+extern crate rust_corosync as corosync;
+use corosync::cmap;
+
+fn track_notify_fn(
+ _handle: &cmap::Handle,
+ _track_handle: &cmap::TrackHandle,
+ event: cmap::TrackType,
+ key_name: &str,
+ old_value: &cmap::Data,
+ new_value: &cmap::Data,
+ user_data: u64,
+) {
+ println!("Track notify callback");
+ println!("Key: {key_name}, event: {event}, user_data: {user_data}");
+ println!(" Old value: {old_value}");
+ println!(" New value: {new_value}");
+}
+
+fn main() {
+ let handle = match cmap::initialize(cmap::Map::Icmap) {
+ Ok(h) => {
+ println!("cmap initialized.");
+ h
+ }
+ Err(e) => {
+ println!("Error in CMAP (Icmap) init: {e}");
+ return;
+ }
+ };
+
+ // Test some SETs
+ if let Err(e) = cmap::set_u32(handle, "test.test_uint32", 456) {
+ println!("Error in CMAP set_u32: {e}");
+ return;
+ };
+
+ if let Err(e) = cmap::set_i16(handle, "test.test_int16", -789) {
+ println!("Error in CMAP set_i16: {e}");
+ return;
+ };
+
+ if let Err(e) = cmap::set_number(handle, "test.test_num_1", 6809u32) {
+ println!("Error in CMAP set_number(u32): {e}");
+ return;
+ };
+
+ // NOT PI (just to avoid clippy whingeing)
+ if let Err(e) = cmap::set_number(handle, "test.test_num_2", 3.24159265) {
+ println!("Error in CMAP set_number(f32): {e}");
+ return;
+ };
+
+ if let Err(e) = cmap::set_string(handle, "test.test_string", "Hello from Rust") {
+ println!("Error in CMAP set_string: {e}");
+ return;
+ };
+
+ let test_d = cmap::Data::UInt64(0xdeadbeefbacecafe);
+ if let Err(e) = cmap::set(handle, "test.test_data", &test_d) {
+ println!("Error in CMAP set_data: {e}");
+ return;
+ };
+
+ // let test_d2 = cmap::Data::UInt32(6809);
+ let test_d2 = cmap::Data::String("Test string in data 12345".to_string());
+ if let Err(e) = cmap::set(handle, "test.test_again", &test_d2) {
+ println!("Error in CMAP set_data2: {e}");
+ return;
+ };
+
+ // get them back again
+ match cmap::get(handle, "test.test_uint32") {
+ Ok(v) => {
+ println!("GOT uint32 {v}");
+ }
+
+ Err(e) => {
+ println!("Error in CMAP get: {e}");
+ return;
+ }
+ };
+ match cmap::get(handle, "test.test_int16") {
+ Ok(v) => {
+ println!("GOT uint16 {v}");
+ }
+
+ Err(e) => {
+ println!("Error in CMAP get: {e}");
+ return;
+ }
+ };
+
+ match cmap::get(handle, "test.test_num_1") {
+ Ok(v) => {
+ println!("GOT num {v}");
+ }
+
+ Err(e) => {
+ println!("Error in CMAP get: {e}");
+ return;
+ }
+ };
+ match cmap::get(handle, "test.test_num_2") {
+ Ok(v) => {
+ println!("GOT num {v}");
+ }
+
+ Err(e) => {
+ println!("Error in CMAP get: {e}");
+ return;
+ }
+ };
+ match cmap::get(handle, "test.test_string") {
+ Ok(v) => {
+ println!("GOT string {v}");
+ }
+
+ Err(e) => {
+ println!("Error in CMAP get: {e}");
+ return;
+ }
+ };
+
+ match cmap::get(handle, "test.test_data") {
+ Ok(v) => match v {
+ cmap::Data::UInt64(u) => println!("GOT data value {u:x}"),
+ _ => println!("ERROR type was not UInt64, got {v}"),
+ },
+
+ Err(e) => {
+ println!("Error in CMAP get: {e}");
+ return;
+ }
+ };
+
+ // Test an iterator
+ match cmap::CmapIterStart::new(handle, "totem.") {
+ Ok(cmap_iter) => {
+ for i in cmap_iter {
+ println!("ITER: {i:?}");
+ }
+ println!();
+ }
+ Err(e) => {
+ println!("Error in CMAP iter start: {e}");
+ }
+ }
+
+ // Close this handle
+ if let Err(e) = cmap::finalize(handle) {
+ println!("Error in CMAP get: {e}");
+ return;
+ };
+
+ // Test notifications on the stats map
+ let handle = match cmap::initialize(cmap::Map::Stats) {
+ Ok(h) => h,
+ Err(e) => {
+ println!("Error in CMAP (Stats) init: {e}");
+ return;
+ }
+ };
+
+ let cb = cmap::NotifyCallback {
+ notify_fn: Some(track_notify_fn),
+ };
+ let _track_handle = match cmap::track_add(
+ handle,
+ "stats.srp.memb_merge_detect_tx",
+ cmap::TrackType::MODIFY | cmap::TrackType::ADD | cmap::TrackType::DELETE,
+ &cb,
+ 997u64,
+ ) {
+ Ok(th) => th,
+ Err(e) => {
+ println!("Error in CMAP track_add {e}");
+ return;
+ }
+ };
+
+ // Wait for events
+ let mut event_num = 0;
+ loop {
+ if let Err(e) = cmap::dispatch(handle, corosync::DispatchFlags::One) {
+ println!("Error from CMAP dispatch: {e}");
+ }
+ // Just do 5
+ event_num += 1;
+ if event_num > 5 {
+ break;
+ }
+ }
+}
diff --git a/bindings/rust/tests/src/bin/cpg-test.rs b/bindings/rust/tests/src/bin/cpg-test.rs
new file mode 100644
index 0000000..df83c2d
--- /dev/null
+++ b/bindings/rust/tests/src/bin/cpg-test.rs
@@ -0,0 +1,142 @@
+// Test the CPG library. Requires that corosync is running and that we are root.
+
+extern crate rust_corosync as corosync;
+use corosync::{cpg, NodeId};
+use std::str;
+
+fn deliver_fn(
+ _handle: &cpg::Handle,
+ group_name: String,
+ nodeid: NodeId,
+ pid: u32,
+ msg: &[u8],
+ msg_len: usize,
+) {
+ println!(
+ "TEST deliver_fn called for {group_name}, from nodeid/pid {nodeid}/{pid}. len={msg_len}"
+ );
+
+ // Print as text if it's valid UTF8
+ match str::from_utf8(msg) {
+ Ok(s) => println!(" {s}"),
+ Err(_) => {
+ for i in msg {
+ print!("{i:02x} ");
+ }
+ println!();
+ }
+ }
+}
+
+fn confchg_fn(
+ _handle: &cpg::Handle,
+ group_name: &str,
+ member_list: Vec<cpg::Address>,
+ left_list: Vec<cpg::Address>,
+ joined_list: Vec<cpg::Address>,
+) {
+ println!("TEST confchg_fn called for {group_name}");
+ println!(" members: {member_list:?}");
+ println!(" left: {left_list:?}");
+ println!(" joined: {joined_list:?}");
+}
+
+fn totem_confchg_fn(_handle: &cpg::Handle, ring_id: cpg::RingId, member_list: Vec<NodeId>) {
+ println!(
+ "TEST totem_confchg_fn called for {}/{}",
+ ring_id.nodeid, ring_id.seq
+ );
+ println!(" members: {member_list:?}");
+}
+
+fn main() {
+ // Initialise the model data
+ let md = cpg::ModelData::ModelV1(cpg::Model1Data {
+ flags: cpg::Model1Flags::None,
+ deliver_fn: Some(deliver_fn),
+ confchg_fn: Some(confchg_fn),
+ totem_confchg_fn: Some(totem_confchg_fn),
+ });
+
+ let handle = match cpg::initialize(&md, 99_u64) {
+ Ok(h) => h,
+ Err(e) => {
+ println!("Error in CPG init: {e}");
+ return;
+ }
+ };
+
+ if let Err(e) = cpg::join(handle, "TEST") {
+ println!("Error in CPG join: {e}");
+ return;
+ }
+
+ match cpg::local_get(handle) {
+ Ok(n) => {
+ println!("Local nodeid is {n}");
+ }
+ Err(e) => {
+ println!("Error in CPG local_get: {e}");
+ }
+ }
+
+ // Test membership_get()
+ match cpg::membership_get(handle, "TEST") {
+ Ok(m) => {
+ println!(" members: {m:?}");
+ println!();
+ }
+ Err(e) => {
+ println!("Error in CPG membership_get: {e}");
+ }
+ }
+
+ // Test context APIs
+ let set_context: u64 = 0xabcdbeefcafe;
+ if let Err(e) = cpg::context_set(handle, set_context) {
+ println!("Error in CPG context_set: {e}");
+ return;
+ }
+
+ // NOTE This will fail on 32 bit systems because void* is not u64
+ match cpg::context_get(handle) {
+ Ok(c) => {
+ if c != set_context {
+ println!("Error: context_get() returned {c:x}, context should be {set_context:x}");
+ }
+ }
+ Err(e) => {
+ println!("Error in CPG context_get: {e}");
+ }
+ }
+
+ // Test iterator
+ match cpg::CpgIterStart::new(handle, "", cpg::CpgIterType::All) {
+ Ok(cpg_iter) => {
+ for i in cpg_iter {
+ println!("ITER: {i:?}");
+ }
+ println!();
+ }
+ Err(e) => {
+ println!("Error in CPG iter start: {e}");
+ }
+ }
+
+ // We should receive our own message (at least) in the event loop
+ if let Err(e) = cpg::mcast_joined(
+ handle,
+ cpg::Guarantee::TypeAgreed,
+ &"This is a test".to_string().into_bytes(),
+ ) {
+ println!("Error in CPG mcast_joined: {e}");
+ }
+
+ // Wait for events
+ loop {
+ if cpg::dispatch(handle, corosync::DispatchFlags::One).is_err() {
+ break;
+ }
+ }
+ println!("ERROR: Corosync quit");
+}
diff --git a/bindings/rust/tests/src/bin/quorum-test.rs b/bindings/rust/tests/src/bin/quorum-test.rs
new file mode 100644
index 0000000..5797b7d
--- /dev/null
+++ b/bindings/rust/tests/src/bin/quorum-test.rs
@@ -0,0 +1,83 @@
+// Test the QUORUM library. Requires that corosync is running and that we are root.
+
+extern crate rust_corosync as corosync;
+use corosync::{quorum, NodeId};
+
+fn quorum_fn(
+ _handle: &quorum::Handle,
+ quorate: bool,
+ ring_id: quorum::RingId,
+ member_list: Vec<NodeId>,
+) {
+ println!("TEST quorum_fn called. quorate = {quorate}");
+ println!(" ring_id: {}/{}", ring_id.nodeid, ring_id.seq);
+ println!(" members: {member_list:?}");
+}
+
+fn nodelist_fn(
+ _handle: &quorum::Handle,
+ ring_id: quorum::RingId,
+ member_list: Vec<NodeId>,
+ joined_list: Vec<NodeId>,
+ left_list: Vec<NodeId>,
+) {
+ println!(
+ "TEST nodelist_fn called for {}/{}",
+ ring_id.nodeid, ring_id.seq
+ );
+ println!(" members: {member_list:?}");
+ println!(" joined: {joined_list:?}");
+ println!(" left: {left_list:?}");
+}
+
+fn main() {
+ // Initialise the model data
+ let md = quorum::ModelData::ModelV1(quorum::Model1Data {
+ flags: quorum::Model1Flags::None,
+ quorum_notification_fn: Some(quorum_fn),
+ nodelist_notification_fn: Some(nodelist_fn),
+ });
+
+ let handle = match quorum::initialize(&md, 99_u64) {
+ Ok((h, t)) => {
+ println!("Quorum initialized; type = {}", t as u32);
+ h
+ }
+ Err(e) => {
+ println!("Error in QUORUM init: {e}");
+ return;
+ }
+ };
+
+ // Test context APIs
+ let set_context: u64 = 0xabcdbeefcafe;
+ if let Err(e) = quorum::context_set(handle, set_context) {
+ println!("Error in QUORUM context_set: {e}");
+ return;
+ }
+
+ // NOTE This will fail on 32 bit systems because void* is not u64
+ match quorum::context_get(handle) {
+ Ok(c) => {
+ if c != set_context {
+ println!("Error: context_get() returned {c:x}, context should be {set_context:x}");
+ }
+ }
+ Err(e) => {
+ println!("Error in QUORUM context_get: {e}");
+ }
+ }
+
+ if let Err(e) = quorum::trackstart(handle, corosync::TrackFlags::Changes) {
+ println!("Error in QUORUM trackstart: {e}");
+ return;
+ }
+
+ // Wait for events
+ loop {
+ if quorum::dispatch(handle, corosync::DispatchFlags::One).is_err() {
+ break;
+ }
+ }
+ println!("ERROR: Corosync quit");
+}
diff --git a/bindings/rust/tests/src/bin/votequorum-test.rs b/bindings/rust/tests/src/bin/votequorum-test.rs
new file mode 100644
index 0000000..cf9746b
--- /dev/null
+++ b/bindings/rust/tests/src/bin/votequorum-test.rs
@@ -0,0 +1,117 @@
+// Test the VOTEQUORUM library. Requires that corosync is running and that we are root.
+
+extern crate rust_corosync as corosync;
+use corosync::votequorum;
+
+fn quorum_fn(
+ _handle: &votequorum::Handle,
+ _context: u64,
+ quorate: bool,
+ member_list: Vec<votequorum::Node>,
+) {
+ println!("TEST votequorum_quorum_fn called. quorate = {quorate}");
+ println!(" members: {member_list:?}");
+}
+
+fn nodelist_fn(
+ _handle: &votequorum::Handle,
+ _context: u64,
+ ring_id: votequorum::RingId,
+ member_list: Vec<corosync::NodeId>,
+) {
+ println!(
+ "TEST nodelist_fn called for {}/{}",
+ ring_id.nodeid, ring_id.seq
+ );
+ println!(" members: {member_list:?}");
+}
+
+fn expectedvotes_fn(_handle: &votequorum::Handle, _context: u64, expected_votes: u32) {
+ println!("TEST expected_votes_fn called: value is {expected_votes}");
+}
+
+fn main() {
+ // Initialise the model data
+ let cb = votequorum::Callbacks {
+ quorum_notification_fn: Some(quorum_fn),
+ nodelist_notification_fn: Some(nodelist_fn),
+ expectedvotes_notification_fn: Some(expectedvotes_fn),
+ };
+
+ let handle = match votequorum::initialize(&cb) {
+ Ok(h) => {
+ println!("Votequorum initialized.");
+ h
+ }
+ Err(e) => {
+ println!("Error in VOTEQUORUM init: {e}");
+ return;
+ }
+ };
+
+ // Test context APIs
+ let set_context: u64 = 0xabcdbeefcafe;
+ if let Err(e) = votequorum::context_set(handle, set_context) {
+ println!("Error in VOTEQUORUM context_set: {e}");
+ }
+
+ // NOTE This will fail on 32 bit systems because void* is not u64
+ match votequorum::context_get(handle) {
+ Ok(c) => {
+ if c != set_context {
+ println!("Error: context_get() returned {c:x}, context should be {set_context:x}");
+ }
+ }
+ Err(e) => {
+ println!("Error in VOTEQUORUM context_get: {e}");
+ }
+ }
+
+ const QDEVICE_NAME: &str = "RustQdevice";
+
+ if let Err(e) = votequorum::qdevice_register(handle, QDEVICE_NAME) {
+ println!("Error in VOTEQUORUM qdevice_register: {e}");
+ }
+
+ match votequorum::get_info(handle, corosync::NodeId::from(1u32)) {
+ Ok(i) => {
+ println!("Node info for nodeid 1");
+ println!(" nodeid: {}", i.node_id);
+ println!(" node_state: {:?}", i.node_state);
+ println!(" node_votes: {}", i.node_votes);
+ println!(" node_expected: {}", i.node_expected_votes);
+ println!(" highest_expected: {}", i.highest_expected);
+ println!(" quorum: {}", i.quorum);
+ println!(" flags: {:x}", i.flags);
+ println!(" qdevice_votes: {}", i.qdevice_votes);
+ println!(" qdevice_name: {}", i.qdevice_name);
+
+ if i.qdevice_name != QDEVICE_NAME {
+ println!(
+ "qdevice names do not match: s/b: \"{}\" is: \"{}\"",
+ QDEVICE_NAME, i.qdevice_name
+ );
+ }
+ }
+ Err(e) => {
+ println!("Error in VOTEQUORUM get_info: {e} (check nodeid 1 has been online)");
+ }
+ }
+
+ if let Err(e) = votequorum::qdevice_unregister(handle, QDEVICE_NAME) {
+ println!("Error in VOTEQUORUM qdevice_unregister: {e}");
+ }
+
+ if let Err(e) = votequorum::trackstart(handle, 99_u64, corosync::TrackFlags::Changes) {
+ println!("Error in VOTEQUORUM trackstart: {e}");
+ return;
+ }
+
+ // Wait for events
+ loop {
+ if votequorum::dispatch(handle, corosync::DispatchFlags::One).is_err() {
+ break;
+ }
+ }
+ println!("ERROR: Corosync quit");
+}