diff --git a/.gitignore b/.gitignore index 8285f01e..e4be2e2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,42 @@ *.o *.a *.so* *.lo *.la .libs .deps .version doc Makefile Makefile.in corosync.spec aclocal.m4 autom4te.cache/ compile config.guess config.log config.status config.sub configure corosync-*.tar* depcomp install-sh libtool ltmain.sh m4 missing tags ID Doxyfile test-driver +Cargo.toml +build.rs +Cargo.lock +bindings/rust/src/sys/cpg.rs +bindings/rust/src/sys/cfg.rs +bindings/rust/src/sys/cmap.rs +bindings/rust/src/sys/quorum.rs +bindings/rust/src/sys/votequorum.rs +test-suite.log +bindings/rust/target/ +bindings/rust/tests/target/ diff --git a/Makefile.am b/Makefile.am index 73357cda..68c78660 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,199 +1,201 @@ # Copyright (c) 2009 Red Hat, Inc. # # Authors: Andrew Beekhof # Steven Dake (sdake@redhat.com) # # This software licensed under BSD license, the text of which follows: # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # - Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # - Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # - Neither the name of the MontaVista Software, Inc. nor the names of its # contributors may be used to endorse or promote products derived from this # software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # THE POSSIBILITY OF SUCH DAMAGE. SPEC = $(PACKAGE_NAME).spec TARFILE = $(PACKAGE_NAME)-$(VERSION).tar.gz EXTRA_DIST = autogen.sh $(SPEC).in \ build-aux/git-version-gen \ build-aux/gitlog-to-changelog \ build-aux/release.mk \ + build-aux/rust.mk \ + build-aux/rust-regen.sh \ .version ACLOCAL_AMFLAGS = -I m4 MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure depcomp \ config.guess config.sub missing install-sh \ autoheader automake autoconf test_lense.sh \ autoscan.log configure.scan ltmain.sh test-driver dist_doc_DATA = LICENSE INSTALL README.recovery AUTHORS SUBDIRS = include common_lib lib exec tools test pkgconfig \ - man init conf vqsim + man init conf vqsim bindings coverity: rm -rf cov make clean cov-build --dir=cov make cov-analyze --dir cov \ --concurrency \ -co BAD_FREE:allow_first_field:true \ --security \ --wait-for-license cov-format-errors --dir cov coverity-aggressive: rm -rf cov make clean cov-build --dir=cov make cov-analyze --dir cov \ --concurrency \ --all \ --aggressiveness-level high \ --security \ --wait-for-license cov-format-errors --dir cov install-exec-local: $(INSTALL) -d $(DESTDIR)/${COROSYSCONFDIR}/service.d $(INSTALL) -d $(DESTDIR)/${COROSYSCONFDIR}/uidgid.d $(INSTALL) -d $(DESTDIR)/${localstatedir}/lib/corosync $(INSTALL) -d $(DESTDIR)/${localstatedir}/log/cluster uninstall-local: rmdir $(DESTDIR)/${COROSYSCONFDIR}/service.d || :; rmdir $(DESTDIR)/${COROSYSCONFDIR}/uidgid.d || :; rmdir $(DESTDIR)/${localstatedir}/lib/corosync || :; rmdir $(DESTDIR)/${localstatedir}/log/cluster || :; if AUGTOOL check_SCRIPTS = test_lense.sh TESTS = $(check_SCRIPTS) test_lense.sh: echo "augparse -I $(srcdir)/conf/lenses/ $(srcdir)/conf/lenses/tests/test_corosync.aug" > $@ chmod +x $@ endif lint: for dir in lib exec tools test; do make -C $$dir lint; done .PHONY: doxygen doxygen: @if [ "$(DOXYGEN)" = "" ] || [ "$(DOT)" = "" ] ; then \ echo "*********************************************" ; \ echo "*** ***" ; \ echo "*** You must install doxygen and graphviz ***" ; \ echo "*** to generate the API documentation. ***" ; \ echo "*** ***" ; \ echo "*********************************************" ; \ exit 1 ; \ else \ mkdir -p doc/api && $(DOXYGEN) ; \ fi dist-clean-local: rm -f autoconf automake autoheader test_lense.sh clean-generic: rm -rf doc/api $(SPEC) $(TARFILE) test_lense.sh ## make rpm/srpm section. $(SPEC): $(SPEC).in rm -f $@-t $@ date="$(shell LC_ALL=C date "+%a %b %d %Y")" && \ gvgver="`cd $(abs_srcdir); build-aux/git-version-gen --fallback $(VERSION) .tarball-version .gitarchivever`" && \ if [ "$$gvgver" = "`echo $$gvgver | sed 's/-/./'`" ];then \ rpmver="$$gvgver" && \ alphatag="" && \ dirty="" && \ numcomm="0"; \ else \ gitver="`echo $$gvgver | sed 's/\(.*\)\./\1-/'`" && \ rpmver=`echo $$gitver | sed 's/-.*//g'` && \ alphatag=`echo $$gvgver | sed 's/[^-]*-\([^-]*\).*/\1/'` && \ numcomm=`echo $$gitver | sed 's/[^-]*-\([^-]*\).*/\1/'` && \ dirty="" && \ if [ "`echo $$gitver | sed 's/^.*-dirty$$//g'`" = "" ];then \ dirty="dirty"; \ fi \ fi && \ if [ -n "$$dirty" ]; then dirty="dirty"; else dirty=""; fi && \ if [ "$$numcomm" = "0" ]; then numcomm=""; fi && \ if [ -n "$$numcomm" ]; then numcomm="%global numcomm $$numcomm"; fi && \ if [ "$$alphatag" = "$$gitver" ]; then alphatag=""; fi && \ if [ -n "$$alphatag" ]; then alphatag="%global alphatag $$alphatag"; fi && \ if [ -n "$$dirty" ]; then dirty="%global dirty dirty"; fi && \ $(SED) \ -e "s#@version@#$$rpmver#g" \ -e "s#@ALPHATAG@#$$alphatag#g" \ -e "s#@NUMCOMM@#$$numcomm#g" \ -e "s#@DIRTY@#$$dirty#g" \ -e "s#@date@#$$date#g" \ $< > $@-t; \ chmod a-w $@-t mv $@-t $@ $(TARFILE): $(MAKE) dist RPMBUILDOPTS = --define "_sourcedir $(abs_builddir)" \ --define "_specdir $(abs_builddir)" \ --define "_builddir $(abs_builddir)" \ --define "_srcrpmdir $(abs_builddir)" \ --define "_rpmdir $(abs_builddir)" srpm: clean $(MAKE) $(SPEC) $(TARFILE) rpmbuild $(WITH_LIST) $(RPMBUILDOPTS) --nodeps -bs $(SPEC) rpm: clean _version $(MAKE) $(SPEC) $(TARFILE) rpmbuild $(WITH_LIST) $(RPMBUILDOPTS) -ba $(SPEC) # release/versioning BUILT_SOURCES = .version .version: echo $(VERSION) > $@-t && mv $@-t $@ dist-hook: gen-ChangeLog echo $(VERSION) > $(distdir)/.tarball-version gen_start_date = 2000-01-01 .PHONY: gen-ChangeLog _version gen-ChangeLog: if test -d .git; then \ LC_ALL=C $(top_srcdir)/build-aux/gitlog-to-changelog \ --since=$(gen_start_date) > $(distdir)/cl-t; \ rm -f $(distdir)/ChangeLog; \ mv $(distdir)/cl-t $(distdir)/ChangeLog; \ fi _version: cd $(srcdir) && rm -rf autom4te.cache .version && autoreconf -i $(MAKE) $(AM_MAKEFLAGS) Makefile maintainer-clean-local: rm -rf m4 diff --git a/bindings/Makefile.am b/bindings/Makefile.am new file mode 100644 index 00000000..0ba6aac2 --- /dev/null +++ b/bindings/Makefile.am @@ -0,0 +1,15 @@ +# +# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved. +# +# Author: Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+ +# + +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = . + +if BUILD_RUST_BINDINGS +SUBDIRS += rust +endif diff --git a/bindings/rust/Cargo.toml.in b/bindings/rust/Cargo.toml.in new file mode 100644 index 00000000..d357605a --- /dev/null +++ b/bindings/rust/Cargo.toml.in @@ -0,0 +1,16 @@ +[package] +name = "rust-corosync" +version = "@corosyncrustver@" +authors = ["Christine Caulfield "] +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/LICENSE b/bindings/rust/LICENSE new file mode 100644 index 00000000..751abc5c --- /dev/null +++ b/bindings/rust/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2023 Red Hat Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bindings/rust/Makefile.am b/bindings/rust/Makefile.am new file mode 100644 index 00000000..b152ba7e --- /dev/null +++ b/bindings/rust/Makefile.am @@ -0,0 +1,60 @@ +# +# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# 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: ../../include/corosync/cpg.h + $(top_srcdir)/build-aux/rust-regen.sh $^ $@ CPG -- -I$(top_srcdir)/include + +src/sys/cfg.rs: ../../include/corosync/cfg.h + $(top_srcdir)/build-aux/rust-regen.sh $^ $@ CFG -- -I$(top_srcdir)/include + +src/sys/quorum.rs: ../../include/corosync/quorum.h + $(top_srcdir)/build-aux/rust-regen.sh $^ $@ QUORUM -- -I$(top_srcdir)/include + +src/sys/votequorum.rs: ../../include/corosync/votequorum.h + $(top_srcdir)/build-aux/rust-regen.sh $^ $@ VOTEQUORUM -- -I$(top_srcdir)/include + +src/sys/cmap.rs: ../../include/corosync/cmap.h + $(top_srcdir)/build-aux/rust-regen.sh $^ $@ CMAP -- -I$(top_srcdir)/include + +all-local: cargo-tree-prep 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/README.md b/bindings/rust/README.md new file mode 100644 index 00000000..cb2f9cc8 --- /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 00000000..e7de170d --- /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 00000000..b4eecacc --- /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> = 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, +} + +/// 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, +} + +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 { + 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 { + 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 { + 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 { + 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::::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 { + // 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 00000000..f35f58f9 --- /dev/null +++ b/bindings/rust/src/cmap.rs @@ -0,0 +1,898 @@ +// 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> = Mutex::new(HashMap::new()); + static ref HANDLE_HASH: Mutex> = 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 { + 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 { + 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 { + 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), + 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, "{} (Int8)", v), + Data::UInt8(v) => write!(f, "{} (UInt8)", v), + Data::Int16(v) => write!(f, "{} (Int16)", v), + Data::UInt16(v) => write!(f, "{} (UInt16)", v), + Data::Int32(v) => write!(f, "{} (Int32)", v), + Data::UInt32(v) => write!(f, "{} (UInt32)", v), + Data::Int64(v) => write!(f, "{} (Int64)", v), + Data::UInt64(v) => write!(f, "{} (UInt64)", v), + Data::Float(v) => write!(f, "{} (Float)", v), + Data::Double(v) => write!(f, "{} (Double)", v), + Data::String(v) => write!(f, "{} (String)", v), + Data::Binary(v) => write!(f, "{:?} (Binary)", v), + Data::Unknown => write!(f, "Unknown)"), + } + } +} + +const CMAP_KEYNAME_MAXLENGTH: usize = 255; +fn string_to_cstring_validated(key: &str, maxlen: usize) -> Result { + 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(_value: T) -> (DataType, usize) { + match type_name::() { + "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(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 { + 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() as *mut u8, 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::::new(); + ints.resize(value_size, 0u8); + copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, 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::::new(); + ints.resize(value_size, 0u8); + copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, 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 { + let csname = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?; + let mut value_size: usize = 16; + let mut c_key_type: u32 = 0; + let mut c_value = Vec::::new(); + + // First guess at a size for Strings and Binaries. Expand if needed + c_value.resize(INITIAL_SIZE, 0u8); + + 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 { + 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 { + 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::::new(); + c_value.resize(c_value_len, 0u8); + 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 { + 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 00000000..12464975 --- /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
, + left_list: Vec
, + joined_list: Vec
, + ), + >, + pub totem_confchg_fn: Option)>, +} + +/// 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> = Mutex::new(HashMap::new()); +} + +// Convert a Rust String into a cpg_name struct for libcpg +fn string_to_cpg_name(group: &str) -> Result { + 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 - used in callbacks +fn cpg_array_to_vec(list: *const ffi::cpg_address, list_entries: usize) -> Vec
{ + let temp: &[ffi::cpg_address] = unsafe { slice::from_raw_parts(list, list_entries) }; + let mut r_vec = Vec::
::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::::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 { + 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 { + 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 { + 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> { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 00000000..cd4326e7 --- /dev/null +++ b/bindings/rust/src/lib.rs @@ -0,0 +1,297 @@ +//! 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 +//! +//! 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 = ::std::result::Result; + +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 for NodeId { + fn from(id: u32) -> NodeId { + NodeId { id } + } +} + +impl From 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 { + let mut newbytes = Vec::::new(); + newbytes.resize(max_length, 0u8); + + // 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 00000000..25c2fe62 --- /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> = Mutex::new(HashMap::new()); +} + +fn list_to_vec(list_entries: u32, list: *const u32) -> Vec { + let mut r_member_list = Vec::::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)>, + pub nodelist_notification_fn: Option< + fn( + hande: &Handle, + ring_id: RingId, + member_list: Vec, + joined_list: Vec, + left_list: Vec, + ), + >, +} + +/// 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 { + 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 { + 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 { + 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 00000000..03dfec24 --- /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 00000000..4718b586 --- /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> = 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 { + let mut r_member_list = Vec::::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::::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)>, + pub nodelist_notification_fn: + Option)>, + pub expectedvotes_notification_fn: + Option, +} + +/// 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 { + 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 { + 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 { + 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 { + 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 00000000..1d8fabfb --- /dev/null +++ b/bindings/rust/tests/Cargo.toml.in @@ -0,0 +1,36 @@ +[package] +name = "rust-corosync-tests" +version = "@corosyncrustver@" +authors = ["Christine Caulfield "] +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 00000000..3edff1f6 --- /dev/null +++ b/bindings/rust/tests/Makefile.am @@ -0,0 +1,32 @@ +# +# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# 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 +check_SCRIPTS = target/$(RUST_TARGET_DIR)/cpg-test + +noinst_SCRIPTS = $(check_SCRIPTS) + +AM_TESTS_ENVIRONMENT=LD_LIBRARY_PATH="$(abs_top_builddir)/lib/.libs" + +TESTS = $(check_SCRIPTS) + +clean-local: cargo-clean diff --git a/bindings/rust/tests/build.rs.in b/bindings/rust/tests/build.rs.in new file mode 100644 index 00000000..b187d1a5 --- /dev/null +++ b/bindings/rust/tests/build.rs.in @@ -0,0 +1,21 @@ +// 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-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 00000000..02eda099 --- /dev/null +++ b/bindings/rust/tests/src/bin/cfg-test.rs @@ -0,0 +1,136 @@ +// 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: {} (tried nodeids {} & {})", + e, 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 00000000..5747ba35 --- /dev/null +++ b/bindings/rust/tests/src/bin/cmap-test.rs @@ -0,0 +1,198 @@ +// 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: {}, event: {}, user_data: {}", + key_name, event, 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 {:x}", u), + _ => 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 00000000..f5f336c4 --- /dev/null +++ b/bindings/rust/tests/src/bin/cpg-test.rs @@ -0,0 +1,146 @@ +// 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 {}, from nodeid/pid {}/{}. len={}", + group_name, nodeid, pid, 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!("{:02x} ", i); + } + println!(); + } + } +} + +fn confchg_fn( + _handle: &cpg::Handle, + group_name: &str, + member_list: Vec, + left_list: Vec, + joined_list: Vec, +) { + 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) { + 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 {:x}, context should be {:x}", + c, set_context + ); + } + } + 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 00000000..c65bfba8 --- /dev/null +++ b/bindings/rust/tests/src/bin/quorum-test.rs @@ -0,0 +1,86 @@ +// 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, +) { + 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, + joined_list: Vec, + left_list: Vec, +) { + 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 {:x}, context should be {:x}", + c, set_context + ); + } + } + 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 00000000..59c50b2e --- /dev/null +++ b/bindings/rust/tests/src/bin/votequorum-test.rs @@ -0,0 +1,123 @@ +// 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, +) { + 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, +) { + 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 {:x}, context should be {:x}", + c, set_context + ); + } + } + 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: {} (check nodeid 1 has been online)", + e + ); + } + } + + 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"); +} diff --git a/build-aux/rust-regen.sh b/build-aux/rust-regen.sh new file mode 100755 index 00000000..15a3790d --- /dev/null +++ b/build-aux/rust-regen.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Copyright (C) 2021-2023 Red Hat, Inc. All rights reserved. +# +# Authors: Christine Caulfield +# Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+ +# +# +# Regerate the FFI bindings in src/sys from the current headers +# + +srcheader="$1" +dstrs="$2" +filter="$3" +shift; shift; shift + +bindgen \ + --size_t-is-usize \ + --no-recursive-allowlist \ + --no-prepend-enum-name \ + --no-layout-tests \ + --no-doc-comments \ + --generate functions,types \ + --fit-macro-constant-types \ + --allowlist-var=$filter.* \ + --allowlist-type=.* \ + --allowlist-function=.* \ + $srcheader -o $dstrs "$@" diff --git a/build-aux/rust.mk b/build-aux/rust.mk new file mode 100644 index 00000000..a1fee17f --- /dev/null +++ b/build-aux/rust.mk @@ -0,0 +1,113 @@ +# +# Copyright (C) 2021-2022 Red Hat, Inc. All rights reserved. +# +# Author: Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+ +# + +RUST_COMMON = \ + build.rs.in + +RUST_SRCS = $(RUST_SHIP_SRCS) $(RUST_BUILT_SRCS) + +%.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' \ + > $@-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 diff --git a/configure.ac b/configure.ac index 2b23ba95..7a9e4200 100644 --- a/configure.ac +++ b/configure.ac @@ -1,790 +1,842 @@ # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. # bootstrap / init AC_PREREQ([2.69]) AC_INIT([corosync], [m4_esyscmd([build-aux/git-version-gen .tarball-version .gitarchivever])], [users@clusterlabs.org]) AC_USE_SYSTEM_EXTENSIONS AM_INIT_AUTOMAKE([foreign 1.11]) LT_PREREQ([2.2.6]) LT_INIT AM_SILENT_RULES([yes]) AC_CONFIG_SRCDIR([lib/cpg.c]) AC_CONFIG_HEADERS([include/corosync/config.h]) AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_HOST AC_LANG([C]) AC_SUBST(WITH_LIST, [""]) #Enable inter-library dependencies AC_ARG_ENABLE(interlib-deps, [AS_HELP_STRING([--disable-interlib-deps ],[disable inter-library dependencies (might break builds)])], [enable_interlib_deps="$enableval"], [enable_interlib_deps="yes"]) AC_MSG_NOTICE([enable inter-library dependencies: $enable_interlib_deps]) if test "x${enable_interlib_deps}" = "xyes"; then link_all_deplibs=yes link_all_deplibs_CXX=yes else link_all_deplibs=no link_all_deplibs_CXX=no fi +AC_ARG_ENABLE([rust-bindings], + [AS_HELP_STRING([--enable-rust-bindings],[rust bindings support])],, + [ enable_rust_bindings="no" ]) +AM_CONDITIONAL([BUILD_RUST_BINDINGS], [test x$enable_rust_bindings = xyes]) +corosyncrustver="`echo ${VERSION} | sed 's/\(.*\)\./\1-/'`" +AC_SUBST([corosyncrustver]) + dnl Fix default variables - "prefix" variable if not specified systemddir=${prefix}/lib/systemd/system if test "$prefix" = "NONE"; then prefix="/usr" dnl Fix "localstatedir" variable if not specified if test "$localstatedir" = "\${prefix}/var"; then localstatedir="/var" fi dnl Fix "sysconfdir" variable if not specified if test "$sysconfdir" = "\${prefix}/etc"; then sysconfdir="/etc" fi if test "$systemddir" = "NONE/lib/systemd/system"; then systemddir=/lib/systemd/system fi dnl Fix "libdir" variable if not specified if test "$libdir" = "\${exec_prefix}/lib"; then if test -e /usr/lib64; then libdir="/usr/lib64" else libdir="/usr/lib" fi fi fi AC_MSG_NOTICE(Sanitizing exec_prefix: ${exec_prefix}) case $exec_prefix in dnl For consistency with Corosync, map NONE->$prefix NONE) exec_prefix=$prefix;; prefix) exec_prefix=$prefix;; esac # Checks for programs. # check stolen from gnulib/m4/gnu-make.m4 if ! ${MAKE-make} --version /cannot/make/this >/dev/null 2>&1; then AC_MSG_ERROR([you don't seem to have GNU make; it is required]) fi sinclude(corosync-default.m4) AC_PROG_CC m4_version_prereq([2.70], [:], [AC_PROG_CC_C99]) if test "x$ac_cv_prog_cc_c99" = "xno"; then AC_MSG_ERROR(["C99 support is required"]) fi AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_SED PKG_PROG_PKG_CONFIG AC_CHECK_PROGS([GROFF], [groff]) AC_CHECK_PROGS([AUGTOOL], [augtool]) AC_CHECK_PROGS([DOT], [dot]) AC_CHECK_PROGS([DOXYGEN], [doxygen]) AC_CHECK_PROGS([AWK], [awk]) AC_PATH_PROG([BASHPATH], [bash]) # Checks for compiler characteristics. AC_PROG_GCC_TRADITIONAL AC_C_CONST AC_C_INLINE AC_C_VOLATILE # Checks for header files. AC_HEADER_DIRENT AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netdb.h netinet/in.h stdint.h \ stdlib.h string.h sys/ioctl.h sys/param.h sys/socket.h \ sys/time.h syslog.h unistd.h sys/types.h getopt.h malloc.h \ utmpx.h ifaddrs.h stddef.h sys/file.h sys/uio.h]) # Check entries in specific structs AC_CHECK_MEMBER([struct sockaddr_in.sin_len], [AC_DEFINE_UNQUOTED([HAVE_SOCK_SIN_LEN], [1], [sockaddr_in needs sin_len])], [], [[#include ]]) AC_CHECK_MEMBER([struct sockaddr_in6.sin6_len], [AC_DEFINE_UNQUOTED([HAVE_SOCK_SIN6_LEN], [1], [sockaddr_in6 needs sin6_len])], [], [[#include ]]) AC_CHECK_MEMBER([struct msghdr.msg_control], [AC_DEFINE_UNQUOTED([HAVE_MSGHDR_CONTROL], [1], [msghdr has msg_control])], [], [[#include ]]) AC_CHECK_MEMBER([struct msghdr.msg_controllen], [AC_DEFINE_UNQUOTED([HAVE_MSGHDR_CONTROLLEN], [1], [msghdr has msg_controllen])], [], [[#include ]]) AC_CHECK_MEMBER([struct msghdr.msg_flags], [AC_DEFINE_UNQUOTED([HAVE_MSGHDR_FLAGS], [1], [msghdr has msg_flags])], [], [[#include ]]) AC_CHECK_MEMBER([struct msghdr.msg_accrights], [AC_DEFINE_UNQUOTED([HAVE_MSGHDR_ACCRIGHTS], [1], [msghdr has msg_accrights])], [], [[#include ]]) AC_CHECK_MEMBER([struct msghdr.msg_accrightslen], [AC_DEFINE_UNQUOTED([HAVE_MSGHDR_ACCRIGHTSLEN], [1], [msghdr has msg_accrightslen])], [], [[#include ]]) # Checks for typedefs. AC_TYPE_UID_T AC_TYPE_INT16_T AC_TYPE_INT32_T AC_TYPE_INT64_T AC_TYPE_INT8_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_UINT8_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T # Checks for libraries. SAVE_CPPFLAGS="$CPPFLAGS" SAVE_LIBS="$LIBS" PKG_CHECK_MODULES([LIBQB], [libqb]) CPPFLAGS="$CPPFLAGS $LIBQB_CFLAGS" LIBS="$LIBS $LIBQB_LIBS" AC_CHECK_LIB([qb], [qb_log_thread_priority_set], \ have_qb_log_thread_priority_set="yes", \ have_qb_log_thread_priority_set="no") if test "x${have_qb_log_thread_priority_set}" = xyes; then AC_DEFINE_UNQUOTED([HAVE_QB_LOG_THREAD_PRIORITY_SET], 1, [have qb_log_thread_priority_set]) fi AC_CHECK_LIB([qb], [qb_log_file_reopen], \ have_qb_log_file_reopen="yes", \ have_qb_log_file_reopen="no") if test "x${have_qb_log_file_reopen}" = xyes; then AC_DEFINE_UNQUOTED([HAVE_QB_LOG_FILE_REOPEN], 1, [have qb_log_file_reopen]) fi AM_CONDITIONAL(HAVE_QB_LOG_FILE_REOPEN, test x$have_qb_log_file_reopen = xyes) CPPFLAGS="$SAVE_CPPFLAGS" LIBS="$SAVE_LIBS" AC_CHECK_LIB([pthread], [pthread_create]) AC_CHECK_LIB([socket], [socket]) PKG_CHECK_MODULES([knet],[libknet]) AC_CHECK_LIB([nsl], [t_open]) AC_CHECK_LIB([rt], [sched_getscheduler]) AC_CHECK_LIB([z], [crc32], AM_CONDITIONAL([HAVE_CRC32], true), AM_CONDITIONAL([HAVE_CRC32], false)) # this hack is necessary to check for symbols on out of tree builds # but it is as horrible as it gets and in theory users should be # invoking ./configure with proper LIBRARY_PATH set. OLDLIBS="$LIBS" LIBS="$knet_LIBS $LIBS" AC_CHECK_LIB([knet],[knet_handle_enable_access_lists], [AC_DEFINE_UNQUOTED([HAVE_KNET_ACCESS_LIST], 1, [have knet access list])]) AC_CHECK_LIB([knet],[knet_handle_crypto_set_config], [AC_DEFINE_UNQUOTED([HAVE_KNET_CRYPTO_RECONF], 1, [have knet crypto reconfig support])]) AC_CHECK_LIB([knet],[knet_handle_get_onwire_ver], [AC_DEFINE_UNQUOTED([HAVE_KNET_ONWIRE_VER], 1, [have knet onwire versioning])]) LIBS="$OLDLIBS" # Checks for library functions. AC_FUNC_ALLOCA AC_FUNC_CLOSEDIR_VOID AC_FUNC_ERROR_AT_LINE AC_FUNC_FORK AC_FUNC_MALLOC AC_FUNC_MEMCMP AC_FUNC_MMAP AC_FUNC_REALLOC AC_FUNC_SELECT_ARGTYPES AC_FUNC_VPRINTF AC_CHECK_FUNCS([alarm alphasort atexit bzero dup2 endgrent endpwent fdatasync \ fcntl getcwd getpeerucred getpeereid gettimeofday inet_ntoa \ memmove memset mkdir scandir select socket strcasecmp strchr \ strdup strerror strrchr strspn strstr pthread_setschedparam \ sched_get_priority_max sched_setscheduler getifaddrs \ clock_gettime ftruncate gethostname localtime_r munmap strtol]) AC_CONFIG_FILES([Makefile exec/Makefile include/Makefile init/Makefile lib/Makefile common_lib/Makefile man/Makefile pkgconfig/Makefile test/Makefile tools/Makefile conf/Makefile vqsim/Makefile Doxyfile - conf/logrotate/Makefile]) + conf/logrotate/Makefile + bindings/Makefile + bindings/rust/Makefile + bindings/rust/tests/Makefile + bindings/rust/Cargo.toml + bindings/rust/tests/Cargo.toml]) ### Local business +# check for rust tools to build bindings +if test "x$enable_rust_bindings" = "xyes"; then + AC_PATH_PROG([CARGO], [cargo], [no]) + if test "x$CARGO" = xno; then + AC_MSG_ERROR(["cargo command not found"]) + fi + + AC_PATH_PROG([RUSTC], [rustc], [no]) + if test "x$RUSTC" = xno; then + AC_MSG_ERROR(["rustc command not found"]) + fi + + AC_PATH_PROG([RUSTDOC], [rustdoc], [no]) + if test "x$RUSTDOC" = xno; then + AC_MSG_ERROR(["rustdoc command not found"]) + fi + + AC_PATH_PROG([BINDGEN], [bindgen], [no]) + if test "x$BINDGEN" = xno; then + AC_MSG_ERROR(["bindgen command not found"]) + fi + + AC_PATH_PROG([CLIPPY], [clippy-driver], [no]) + if test "x$CLIPPY" = xno; then + AC_MSG_ERROR(["clippy-driver command not found"]) + fi + + AC_PATH_PROG([RUSTFMT], [rustfmt], [no]) + if test "x$RUSTFMT" = xno; then + AC_MSG_ERROR(["rustfmt command not found"]) + fi +fi + dnl =============================================== dnl Functions / global M4 variables dnl =============================================== dnl Global list of LIB names m4_define([local_soname_list], [])dnl dnl Upcase parameter m4_define([local_upcase], [translit([$*], [a-z], [A-Z])])dnl dnl M4 macro for include lib/lib$1.soname and subst that m4_define([LIB_SONAME_IMPORT],[dnl m4_define([local_libname], local_upcase($1)[_SONAME])dnl m4_define([local_soname], translit(m4_sinclude(lib/lib$1.verso), [ ], []))dnl local_libname="local_soname"dnl m4_define([local_soname_list], m4_defn([local_soname_list])[,]local_libname[,]local_upcase($1))dnl AC_SUBST(local_libname)dnl ])dnl dnl M4 macro for print padspaces (used in LIB_MSG_RESULT). It takes 2 arguments, length of string to pad and desired dnl (padded) length m4_define([m4_printpadspace],[ifelse(m4_eval([$2 - $1 < 1]),[1],,[ ][m4_printpadspace([$1],m4_eval([$2 - 1]))])])dnl dnl Show AC_MSG_RESULT for specific libraries m4_define([LIB_MSG_RESULT], [ifelse([$#], [1], ,[dnl AC_MSG_RESULT([ $2 Library SONAME m4_printpadspace(len($2),8) = ${$1}]) LIB_MSG_RESULT(m4_shift(m4_shift($@)))dnl ])])dnl # =============================================== # Helpers # =============================================== ## check if the compiler supports -Werror -Wunknown-warning-option AC_MSG_CHECKING([whether $CC supports -Wunknown-warning-option -Werror]) BACKUP="$CPPFLAGS" CPPFLAGS="$CPPFLAGS -Werror -Wunknown-warning-option" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])], [unknown_warnings_as_errors='-Wunknown-warning-option -Werror'; AC_MSG_RESULT([yes])], [unknown_warnings_as_errors=''; AC_MSG_RESULT([no])]) CPPFLAGS="$BACKUP" ## helper for CC stuff cc_supports_flag() { BACKUP="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $@ $unknown_warnings_as_errors" AC_MSG_CHECKING([whether $CC supports "$@"]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])], [RC=0; AC_MSG_RESULT([yes])], [RC=1; AC_MSG_RESULT([no])]) CPPFLAGS="$BACKUP" return $RC } ## local defines PACKAGE_FEATURES="" LINT_FLAGS="-weak -unrecog +posixlib +ignoresigns -fcnuse \ -badflag -D__gnuc_va_list=va_list -D__attribute\(x\)=" # default libraries SONAME SOMAJOR="5" SOMINOR="0" SOMICRO="0" SONAME="${SOMAJOR}.${SOMINOR}.${SOMICRO}" # specific libraries SONAME LIB_SONAME_IMPORT([cfg]) LIB_SONAME_IMPORT([cpg]) LIB_SONAME_IMPORT([quorum]) LIB_SONAME_IMPORT([sam]) LIB_SONAME_IMPORT([votequorum]) LIB_SONAME_IMPORT([cmap]) # local options AC_ARG_ENABLE([ansi], [ --enable-ansi : force to build with ANSI standards. ], [ default="no" ]) AC_ARG_ENABLE([fatal-warnings], [ --enable-fatal-warnings : enable fatal warnings. ], [ default="no" ]) AC_ARG_ENABLE([debug], [ --enable-debug : enable debug build. ], [ default="no" ]) AC_ARG_WITH([sanitizers], [AS_HELP_STRING([--with-sanitizers=...,...], [enable SANitizer build, do *NOT* use for production. Only ASAN/UBSAN/TSAN are currently supported])], [ SANITIZERS="$withval" ], [ SANITIZERS="" ]) AC_ARG_ENABLE([secure-build], [ --enable-secure-build : enable PIE/RELRO build. ], [], [enable_secure_build="yes"]) AC_ARG_ENABLE([user-flags], [ --enable-user-flags : rely on user environment. ], [ default="no" ]) AC_ARG_ENABLE([coverage], [ --enable-coverage : coverage analysis of the codebase. ], [ default="no" ]) AC_ARG_ENABLE([small-memory-footprint], [ --enable-small-memory-footprint : Use small message queues and small messages sizes. ], [ default="no" ]) AC_ARG_ENABLE([dbus], [ --enable-dbus : dbus events. ],, [ enable_dbus="no" ]) AC_ARG_ENABLE([monitoring], [ --enable-monitoring : resource monitoring ],, [ default="no" ]) AM_CONDITIONAL(BUILD_MONITORING, test x$enable_monitoring = xyes) AC_ARG_ENABLE([watchdog], [ --enable-watchdog : Watchdog support ],, [ default="no" ]) AM_CONDITIONAL(BUILD_WATCHDOG, test x$enable_watchdog = xyes) AC_ARG_ENABLE([augeas], [ --enable-augeas : Install the augeas lens for corosync.conf ],, [ enable_augeas="no" ]) AM_CONDITIONAL(INSTALL_AUGEAS, test x$enable_augeas = xyes) AC_ARG_ENABLE([systemd], [ --enable-systemd : Install systemd service files],, [ enable_systemd="no" ]) AM_CONDITIONAL(INSTALL_SYSTEMD, test x$enable_systemd = xyes) AC_ARG_WITH([initconfigdir], [AS_HELP_STRING([--with-initconfigdir=DIR], [configuration directory @<:@SYSCONFDIR/sysconfig@:>@])], [INITCONFIGDIR="$withval"], [INITCONFIGDIR='${sysconfdir}/sysconfig']) AC_SUBST([INITCONFIGDIR]) AC_ARG_WITH([initddir], [ --with-initddir=DIR : path to init script directory. ], [ INITDDIR="$withval" ], [ INITDDIR="$sysconfdir/init.d" ]) AC_ARG_WITH([systemddir], [ --with-systemddir=DIR : path to systemd unit files directory. ], [ SYSTEMDDIR="$withval" ], [ SYSTEMDDIR="$systemddir" ]) AC_ARG_WITH([logdir], [ --with-logdir=DIR : the base directory for corosync logging files. ], [ LOGDIR="$withval" ], [ LOGDIR="$localstatedir/log/cluster" ]) AC_ARG_WITH([logrotatedir], [ --with-logrotatedir=DIR : the base directory for logrorate.d files. ], [ LOGROTATEDIR="$withval" ], [ LOGROTATEDIR="$sysconfdir/logrotate.d" ]) AC_ARG_ENABLE([snmp], [ --enable-snmp : SNMP protocol support ], [ default="no" ]) AC_ARG_ENABLE([xmlconf], [ --enable-xmlconf : XML configuration support ],, [ enable_xmlconf="no" ]) AM_CONDITIONAL(INSTALL_XMLCONF, test x$enable_xmlconf = xyes) AC_ARG_ENABLE([vqsim], [ --enable-vqsim : Quorum simulator support ],, [ enable_vqsim="no" ]) AM_CONDITIONAL(BUILD_VQSIM, test x$enable_vqsim = xyes) AC_ARG_ENABLE([nozzle], [ --enable-nozzle : Support for nozzle ],, [ enable_nozzle="no" ]) # *FLAGS handling goes here ENV_CFLAGS="$CFLAGS" ENV_CPPFLAGS="$CPPFLAGS" ENV_LDFLAGS="$LDFLAGS" # debug build stuff if test "x${enable_debug}" = xyes; then AC_DEFINE_UNQUOTED([DEBUG], [1], [Compiling Debugging code]) OPT_CFLAGS="-O0" PACKAGE_FEATURES="$PACKAGE_FEATURES debug" + RUST_FLAGS="" + RUST_TARGET_DIR="debug" else OPT_CFLAGS="-O3" + RUST_FLAGS="--release" + RUST_TARGET_DIR="release" fi # gdb flags if test "x${GCC}" = xyes; then GDB_FLAGS="-ggdb3" else GDB_FLAGS="-g" fi # --- ASAN/UBSAN/TSAN (see man gcc) --- # when using SANitizers, we need to pass the -fsanitize.. # to both CFLAGS and LDFLAGS. The CFLAGS/LDFLAGS must be # specified as first in the list or there will be runtime # issues (for example user has to LD_PRELOAD asan for it to work # properly). if test -n "${SANITIZERS}"; then SANITIZERS=$(echo $SANITIZERS | sed -e 's/,/ /g') for SANITIZER in $SANITIZERS; do case $SANITIZER in asan|ASAN) SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=address" SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=address -lasan" AC_CHECK_LIB([asan],[main],,AC_MSG_ERROR([Unable to find libasan])) ;; ubsan|UBSAN) SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=undefined" SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=undefined -lubsan" AC_CHECK_LIB([ubsan],[main],,AC_MSG_ERROR([Unable to find libubsan])) ;; tsan|TSAN) SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=thread" SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=thread -ltsan" AC_CHECK_LIB([tsan],[main],,AC_MSG_ERROR([Unable to find libtsan])) ;; esac done fi # Look for dbus-1 if test "x${enable_dbus}" = xyes; then PKG_CHECK_MODULES([DBUS],[dbus-1]) AC_DEFINE_UNQUOTED([HAVE_DBUS], 1, [have dbus]) PACKAGE_FEATURES="$PACKAGE_FEATURES dbus" WITH_LIST="$WITH_LIST --with dbus" fi if test "x${enable_monitoring}" = xyes; then PKG_CHECK_MODULES([statgrab], [libstatgrab]) PKG_CHECK_MODULES([statgrabge090], [libstatgrab >= 0.90], AC_DEFINE_UNQUOTED([HAVE_LIBSTATGRAB_GE_090], 1, [have libstatgrab >= 0.90]), TMP_VARIABLE=1) AC_DEFINE_UNQUOTED([HAVE_MONITORING], 1, [have resource monitoring]) PACKAGE_FEATURES="$PACKAGE_FEATURES monitoring" WITH_LIST="$WITH_LIST --with monitoring" fi if test "x${enable_watchdog}" = xyes; then AC_CHECK_HEADER([linux/watchdog.h], [], [AC_MSG_ERROR([watchdog requires linux/watchdog.h])]) AC_CHECK_HEADER([linux/reboot.h], [], [AC_MSG_ERROR([watchdog requires linux/reboot.h])]) AC_DEFINE_UNQUOTED([HAVE_WATCHDOG], 1, [have watchdog]) PACKAGE_FEATURES="$PACKAGE_FEATURES watchdog" WITH_LIST="$WITH_LIST --with watchdog" fi if test "x${enable_augeas}" = xyes; then PACKAGE_FEATURES="$PACKAGE_FEATURES augeas" fi if test "x${enable_systemd}" = xyes; then PKG_CHECK_MODULES([libsystemd], [libsystemd]) AC_DEFINE([HAVE_LIBSYSTEMD], [1], [have systemd interface library]) PACKAGE_FEATURES="$PACKAGE_FEATURES systemd" WITH_LIST="$WITH_LIST --with systemd" fi if test "x${enable_xmlconf}" = xyes; then PACKAGE_FEATURES="$PACKAGE_FEATURES xmlconf" WITH_LIST="$WITH_LIST --with xmlconf" fi if test "x${enable_vqsim}" = xyes; then vqsim_readline=no AC_CHECK_HEADERS([readline/readline.h readline/history.h], [], AC_MSG_WARN([vqsim will lack readline support])) PACKAGE_FEATURES="$PACKAGE_FEATURES vqsim" WITH_LIST="$WITH_LIST --with vqsim" fi AM_CONDITIONAL(VQSIM_READLINE, [test "x${ac_cv_header_readline_readline_h}" = xyes]) # Look for nozzle if test "x${enable_nozzle}" = xyes; then PKG_CHECK_MODULES([nozzle],[libnozzle]) AC_DEFINE_UNQUOTED([HAVE_LIBNOZZLE], 1, [have nozzle]) PACKAGE_FEATURES="$PACKAGE_FEATURES nozzle" WITH_LIST="$WITH_LIST --with nozzle" fi do_snmp=0 if test "x${enable_snmp}" = xyes; then AC_PATH_PROGS([SNMPCONFIG], [net-snmp-config]) if test "x${SNMPCONFIG}" != "x"; then AC_MSG_CHECKING([for snmp includes]) SNMP_PREFIX=`$SNMPCONFIG --prefix` SNMP_INCLUDES="-I$SNMP_PREFIX/include" AC_MSG_RESULT([$SNMP_INCLUDES]) AC_MSG_CHECKING([for snmp libraries]) SNMP_LIBS=`$SNMPCONFIG --libs` AC_MSG_RESULT([$SNMP_LIBS]) AC_SUBST([SNMP_LIBS]) saveCFLAGS="$CFLAGS" CFLAGS="$CFLAGS $SNMP_INCLUDES" AC_CHECK_HEADERS([net-snmp/net-snmp-config.h]) CFLAGS="$saveCFLAGS" if test "x${ac_cv_header_net_snmp_net_snmp_config_h}" != "xyes"; then AC_MSG_ERROR([Unable to use net-snmp/net-snmp-config.h]) fi savedLibs=$LIBS LIBS="$LIBS $SNMP_LIBS" AC_CHECK_FUNCS([netsnmp_transport_open_client]) if test $ac_cv_func_netsnmp_transport_open_client != yes; then AC_CHECK_FUNCS([netsnmp_tdomain_transport]) if test $ac_cv_func_netsnmp_tdomain_transport != yes; then AC_MSG_ERROR([No usable SNMP client transport implementation found]) fi else AC_DEFINE_UNQUOTED([NETSNMPV54], $NETSNMP_NEW_SUPPORT, [have net-snmp5.4 over]) fi LIBS=$savedLibs do_snmp=1 PACKAGE_FEATURES="$PACKAGE_FEATURES snmp" WITH_LIST="$WITH_LIST --with snmp" AC_DEFINE_UNQUOTED([ENABLE_SNMP], $do_snmp, [Build in support for sending SNMP traps]) else AC_MSG_ERROR([You need the net_snmp development package to continue.]) fi fi AM_CONDITIONAL(BUILD_SNMP, test "${do_snmp}" = "1") # extra warnings EXTRA_WARNINGS="" WARNLIST=" all shadow missing-prototypes missing-declarations strict-prototypes pointer-arith write-strings cast-align bad-function-cast missing-format-attribute format=2 format-security format-nonliteral no-long-long unsigned-char no-strict-aliasing " for j in $WARNLIST; do if cc_supports_flag -W$j; then EXTRA_WARNINGS="$EXTRA_WARNINGS -W$j"; fi done if test "x${enable_coverage}" = xyes && \ cc_supports_flag -ftest-coverage && \ cc_supports_flag -fprofile-arcs ; then AC_MSG_NOTICE([Enabling Coverage (enable -O0 by default)]) OPT_CFLAGS="-O0" COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs" COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs" PACKAGE_FEATURES="$PACKAGE_FEATURES coverage" else COVERAGE_CFLAGS="" COVERAGE_LDFLAGS="" fi if test "x${enable_small_memory_footprint}" = xyes ; then AC_DEFINE_UNQUOTED([HAVE_SMALL_MEMORY_FOOTPRINT], 1, [have small_memory_footprint]) PACKAGE_FEATURES="$PACKAGE_FEATURES small-memory-footprint" fi if test "x${enable_ansi}" = xyes && \ cc_supports_flag -std=iso9899:199409 ; then AC_MSG_NOTICE([Enabling ANSI Compatibility]) ANSI_CPPFLAGS="-ansi -DANSI_ONLY" PACKAGE_FEATURES="$PACKAGE_FEATURES ansi" else ANSI_CPPFLAGS="" fi if test "x${enable_fatal_warnings}" = xyes && \ cc_supports_flag -Werror ; then AC_MSG_NOTICE([Enabling Fatal Warnings (-Werror)]) WERROR_CFLAGS="-Werror" PACKAGE_FEATURES="$PACKAGE_FEATURES fatal-warnings" else WERROR_CFLAGS="" fi # don't add addtional cflags if test "x${enable_user_flags}" = xyes; then OPT_CFLAGS="" GDB_FLAGS="" EXTRA_WARNINGS="" fi if test "x${enable_secure_build}" = xyes; then # stolen from apache configure snippet AC_CACHE_CHECK([whether $CC accepts PIE flags], [ap_cv_cc_pie], [ save_CFLAGS=$CFLAGS save_LDFLAGS=$LDFLAGS CFLAGS="$CFLAGS -fPIE" LDFLAGS="$LDFLAGS -pie" AC_RUN_IFELSE([AC_LANG_SOURCE([[static int foo[30000]; int main () { return 0; }]])], [ap_cv_cc_pie=yes], [ap_cv_cc_pie=no], [ap_cv_cc_pie=yes]) CFLAGS=$save_CFLAGS LDFLAGS=$save_LDFLAGS ]) if test "$ap_cv_cc_pie" = "yes"; then SEC_FLAGS="$SEC_FLAGS -fPIE" SEC_LDFLAGS="$SEC_LDFLAGS -pie" PACKAGE_FEATURES="$PACKAGE_FEATURES pie" fi # similar to above AC_CACHE_CHECK([whether $CC accepts RELRO flags], [ap_cv_cc_relro], [ save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-z,relro" AC_RUN_IFELSE([AC_LANG_SOURCE([[static int foo[30000]; int main () { return 0; }]])], [ap_cv_cc_relro=yes], [ap_cv_cc_relro=no], [ap_cv_cc_relro=yes]) LDFLAGS=$save_LDFLAGS ]) if test "$ap_cv_cc_relro" = "yes"; then SEC_LDFLAGS="$SEC_LDFLAGS -Wl,-z,relro" PACKAGE_FEATURES="$PACKAGE_FEATURES relro" fi AC_CACHE_CHECK([whether $CC accepts BINDNOW flags], [ap_cv_cc_bindnow], [ save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-z,now" AC_RUN_IFELSE([AC_LANG_SOURCE([[static int foo[30000]; int main () { return 0; }]])], [ap_cv_cc_bindnow=yes], [ap_cv_cc_bindnow=no], [ap_cv_cc_bindnow=yes]) LDFLAGS=$save_LDFLAGS ]) if test "$ap_cv_cc_bindnow" = "yes"; then SEC_LDFLAGS="$SEC_LDFLAGS -Wl,-z,now" PACKAGE_FEATURES="$PACKAGE_FEATURES bindnow" fi fi AC_CACHE_CHECK([whether $CC accepts "--as-needed"], [ap_cv_cc_as_needed], [ save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,--as-needed" AC_RUN_IFELSE([AC_LANG_SOURCE([[static int foo[30000]; int main () { return 0; }]])], [ap_cv_cc_as_needed=yes], [ap_cv_cc_as_needed=no], [ap_cv_cc_as_needed=yes]) LDFLAGS=$save_LDFLAGS ]) AC_CACHE_CHECK([whether $CC accepts "--version-script"], [ap_cv_cc_version_script], [ save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,--version-script=conftest.versions" echo "CONFTEST { };" >conftest.versions AC_RUN_IFELSE([AC_LANG_SOURCE([[static int foo[30000]; int main () { return 0; }]])], [ap_cv_cc_version_script=yes], [ap_cv_cc_version_script=no], [ap_cv_cc_version_script=yes]) rm -f conftest.versions LDFLAGS=$save_LDFLAGS ]) if test "$ap_cv_cc_version_script" = "yes"; then AC_SUBST(VERSCRIPT_LDFLAGS, ["-Wl,--version-script=\$(srcdir)/lib\$(call get_libname,\$<).versions"]) else AC_SUBST(VERSCRIPT_LDFLAGS, [""]) fi # define global include dirs INCLUDE_DIRS="$INCLUDE_DIRS -I\$(top_builddir)/include -I\$(top_srcdir)/include" INCLUDE_DIRS="$INCLUDE_DIRS -I\$(top_builddir)/include/corosync -I\$(top_srcdir)/include/corosync" # final build of *FLAGS CFLAGS="$SANITIZERS_CFLAGS $ENV_CFLAGS $lt_prog_compiler_pic $SEC_FLAGS $OPT_CFLAGS $GDB_FLAGS \ $COVERAGE_CFLAGS $EXTRA_WARNINGS \ $WERROR_CFLAGS $LIBQB_CFLAGS \ $SNMP_INCLUDES" CPPFLAGS="$ENV_CPPFLAGS $ANSI_CPPFLAGS $INCLUDE_DIRS" LDFLAGS="$SANITIZERS_LDFLAGS $ENV_LDFLAGS $lt_prog_compiler_pic $SEC_LDFLAGS $COVERAGE_LDFLAGS" if test "$ap_cv_cc_as_needed" = "yes"; then LDFLAGS="$LDFLAGS -Wl,--as-needed" fi # substitute what we need: AC_SUBST([BASHPATH]) AC_SUBST([INITDDIR]) AC_SUBST([SYSTEMDDIR]) AC_SUBST([LOGDIR]) AC_SUBST([LOGROTATEDIR]) AC_SUBST([SOMAJOR]) AC_SUBST([SOMINOR]) AC_SUBST([SOMICRO]) AC_SUBST([SONAME]) +AC_SUBST([RUST_FLAGS]) +AC_SUBST([RUST_TARGET_DIR]) AM_CONDITIONAL(INSTALL_MIB, test "${do_snmp}" = "1") AM_CONDITIONAL(INSTALL_DBUSCONF, test "${enable_dbus}" = "yes") AM_CONDITIONAL(AUGTOOL, test -n "${AUGTOOL}") AM_CONDITIONAL(BUILD_HTML_DOCS, test -n "${GROFF}") AC_SUBST([LINT_FLAGS]) AC_DEFINE_UNQUOTED([LOCALSTATEDIR], "$(eval echo ${localstatedir})", [localstate directory]) COROSYSCONFDIR=${sysconfdir}/corosync AC_SUBST([COROSYSCONFDIR]) AC_DEFINE_UNQUOTED([COROSYSCONFDIR], "$(eval echo ${COROSYSCONFDIR})", [corosync config directory]) AC_DEFINE_UNQUOTED([PACKAGE_FEATURES], "${PACKAGE_FEATURES}", [corosync built-in features]) AC_OUTPUT AC_MSG_RESULT([]) AC_MSG_RESULT([$PACKAGE configuration:]) AC_MSG_RESULT([ Version = ${VERSION}]) AC_MSG_RESULT([ Prefix = ${prefix}]) AC_MSG_RESULT([ Executables = ${sbindir}]) AC_MSG_RESULT([ Man pages = ${mandir}]) AC_MSG_RESULT([ Doc dir = ${docdir}]) AC_MSG_RESULT([ Libraries = ${libdir}]) AC_MSG_RESULT([ Header files = ${includedir}]) AC_MSG_RESULT([ Arch-independent files = ${datadir}]) AC_MSG_RESULT([ State information = ${localstatedir}]) AC_MSG_RESULT([ System configuration = ${sysconfdir}]) AC_MSG_RESULT([ System init.d directory = ${INITDDIR}]) AC_MSG_RESULT([ System systemd directory = ${SYSTEMDDIR}]) AC_MSG_RESULT([ Log directory = ${LOGDIR}]) AC_MSG_RESULT([ Log rotate directory = ${LOGROTATEDIR}]) AC_MSG_RESULT([ corosync config dir = ${COROSYSCONFDIR}]) AC_MSG_RESULT([ init config directory = ${INITCONFIGDIR}]) AC_MSG_RESULT([ Features =${PACKAGE_FEATURES}]) +AC_MSG_RESULT([ Rust bindings = ${enable_rust_bindings}]) AC_MSG_RESULT([]) AC_MSG_RESULT([$PACKAGE build info:]) AC_MSG_RESULT([ Library SONAME = ${SONAME}]) LIB_MSG_RESULT(m4_shift(local_soname_list))dnl AC_MSG_RESULT([ Default optimization = ${OPT_CFLAGS}]) AC_MSG_RESULT([ Default debug options = ${GDB_FLAGS}]) AC_MSG_RESULT([ Extra compiler warnings = ${EXTRA_WARNING}]) AC_MSG_RESULT([ Env. defined CFLAG = ${ENV_CFLAGS}]) AC_MSG_RESULT([ Env. defined CPPFLAGS = ${ENV_CPPFLAGS}]) AC_MSG_RESULT([ Env. defined LDFLAGS = ${ENV_LDFLAGS}]) AC_MSG_RESULT([ ANSI defined CPPFLAGS = ${ANSI_CPPFLAGS}]) AC_MSG_RESULT([ Coverage CFLAGS = ${COVERAGE_CFLAGS}]) AC_MSG_RESULT([ Coverage LDFLAGS = ${COVERAGE_LDFLAGS}]) AC_MSG_RESULT([ Fatal War. CFLAGS = ${WERROR_CFLAGS}]) AC_MSG_RESULT([ Final CFLAGS = ${CFLAGS}]) AC_MSG_RESULT([ Final CPPFLAGS = ${CPPFLAGS}]) AC_MSG_RESULT([ Final LDFLAGS = ${LDFLAGS}])