diff options
Diffstat (limited to 'bindings/rust')
-rw-r--r-- | bindings/rust/Cargo.toml.in | 16 | ||||
-rw-r--r-- | bindings/rust/Makefile.am | 60 | ||||
-rw-r--r-- | bindings/rust/Makefile.in | 836 | ||||
-rw-r--r-- | bindings/rust/README.md | 20 | ||||
-rw-r--r-- | bindings/rust/build.rs.in | 7 | ||||
-rw-r--r-- | bindings/rust/src/cfg.rs | 348 | ||||
-rw-r--r-- | bindings/rust/src/cmap.rs | 894 | ||||
-rw-r--r-- | bindings/rust/src/cpg.rs | 628 | ||||
-rw-r--r-- | bindings/rust/src/lib.rs | 296 | ||||
-rw-r--r-- | bindings/rust/src/quorum.rs | 298 | ||||
-rw-r--r-- | bindings/rust/src/sys/mod.rs | 7 | ||||
-rw-r--r-- | bindings/rust/src/votequorum.rs | 501 | ||||
-rw-r--r-- | bindings/rust/tests/Cargo.toml.in | 36 | ||||
-rw-r--r-- | bindings/rust/tests/Makefile.am | 26 | ||||
-rw-r--r-- | bindings/rust/tests/Makefile.in | 625 | ||||
-rw-r--r-- | bindings/rust/tests/build.rs.in | 22 | ||||
-rw-r--r-- | bindings/rust/tests/src/bin/cfg-test.rs | 135 | ||||
-rw-r--r-- | bindings/rust/tests/src/bin/cmap-test.rs | 195 | ||||
-rw-r--r-- | bindings/rust/tests/src/bin/cpg-test.rs | 142 | ||||
-rw-r--r-- | bindings/rust/tests/src/bin/quorum-test.rs | 83 | ||||
-rw-r--r-- | bindings/rust/tests/src/bin/votequorum-test.rs | 117 |
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"); +} |