bindings: Add Rust bindings

Signed-off-by: Christine Caulfield <ccaulfie@redhat.com>
Reviewed-by: Jan Friesse <jfriesse@redhat.com>
This commit is contained in:
Christine Caulfield 2022-10-13 12:42:31 +01:00 committed by Jan Friesse
parent 91348f8659
commit f34052d78e
26 changed files with 4104 additions and 2 deletions

11
.gitignore vendored
View File

@ -29,3 +29,14 @@ 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/

View File

@ -37,6 +37,8 @@ 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
@ -49,7 +51,7 @@ MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure depcomp \
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

15
bindings/Makefile.am Normal file
View File

@ -0,0 +1,15 @@
#
# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
#
# Author: Fabio M. Di Nitto <fabbione@kronosnet.org>
#
# This software licensed under GPL-2.0+
#
MAINTAINERCLEANFILES = Makefile.in
SUBDIRS = .
if BUILD_RUST_BINDINGS
SUBDIRS += rust
endif

View File

@ -0,0 +1,16 @@
[package]
name = "rust-corosync"
version = "@corosyncrustver@"
authors = ["Christine Caulfield <ccaulfie@redhat.com>"]
edition = "2021"
readme = "README.md"
license = "MIT OR Apache-2.0"
repository = "https://github.com/corosync/corosync/"
description = "Rust bindings for corosync libraries"
categories = ["api-bindings"]
keywords = ["cluster", "high-availability"]
[dependencies]
lazy_static = "1.4.0"
num_enum = "0.5.4"
bitflags = "1.3.2"

21
bindings/rust/LICENSE Normal file
View File

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

60
bindings/rust/Makefile.am Normal file
View File

@ -0,0 +1,60 @@
#
# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
#
# Author: Christine Caulfield <ccaulfie@redhat.com>
#
# This software licensed under GPL-2.0+
#
MAINTAINERCLEANFILES = Makefile.in
include $(top_srcdir)/build-aux/rust.mk
# required for make check
localver = $(corosyncrustver)
SUBDIRS = . tests
EXTRA_DIST = \
$(RUST_COMMON) \
$(RUST_SHIP_SRCS) \
README.md
RUST_SHIP_SRCS = \
src/cpg.rs \
src/cfg.rs \
src/quorum.rs \
src/votequorum.rs \
src/cmap.rs \
src/lib.rs \
src/sys/mod.rs
RUST_BUILT_SRCS = \
src/sys/cpg.rs \
src/sys/cfg.rs \
src/sys/quorum.rs \
src/sys/votequorum.rs \
src/sys/cmap.rs
src/sys/cpg.rs: ../../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

20
bindings/rust/README.md Normal file
View File

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

View File

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

348
bindings/rust/src/cfg.rs Normal file
View File

@ -0,0 +1,348 @@
// libcfg interface for Rust
// Copyright (c) 2021 Red Hat, Inc.
//
// All rights reserved.
//
// Author: Christine Caulfield (ccaulfi@redhat.com)
//
// For the code generated by bindgen
use crate::sys::cfg as ffi;
use std::collections::HashMap;
use std::ffi::CString;
use std::os::raw::{c_int, c_void};
use std::sync::Mutex;
use crate::string_from_bytes;
use crate::{CsError, DispatchFlags, NodeId, Result};
// Used to convert a CFG handle into one of ours
lazy_static! {
static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
}
/// Callback from [track_start]. Will be called if another process
/// requests to shut down corosync. [reply_to_shutdown] should be called
/// with a [ShutdownReply] of either Yes or No.
#[derive(Copy, Clone)]
pub struct Callbacks {
pub corosync_cfg_shutdown_callback_fn: Option<fn(handle: &Handle, flags: u32)>,
}
/// A handle into the cfg library. returned from [initialize] and needed for all other calls
#[derive(Copy, Clone)]
pub struct Handle {
cfg_handle: u64,
callbacks: Callbacks,
}
/// Flags for [try_shutdown]
pub enum ShutdownFlags {
/// Request shutdown (other daemons will be consulted)
Request,
/// Tells other daemons but ignore their opinions
Regardless,
/// Go down straight away (but still tell other nodes)
Immediate,
}
/// Responses for [reply_to_shutdown]
pub enum ShutdownReply {
Yes = 1,
No = 0,
}
/// Trackflags for [track_start]. None currently supported
pub enum TrackFlags {
None,
}
/// Version of the [NodeStatus] structure returned from [node_status_get]
#[derive(Debug, Copy, Clone)]
pub enum NodeStatusVersion {
V1,
}
/// Status of a link inside [NodeStatus] struct
#[derive(Debug)]
pub struct LinkStatus {
pub enabled: bool,
pub connected: bool,
pub dynconnected: bool,
pub mtu: u32,
pub src_ipaddr: String,
pub dst_ipaddr: String,
}
/// Structure returned from [node_status_get], shows all the details of a node
/// that is known to corosync, including all configured links
#[derive(Debug)]
pub struct NodeStatus {
pub version: NodeStatusVersion,
pub nodeid: NodeId,
pub reachable: bool,
pub remote: bool,
pub external: bool,
pub onwire_min: u8,
pub onwire_max: u8,
pub onwire_ver: u8,
pub link_status: Vec<LinkStatus>,
}
extern "C" fn rust_shutdown_notification_fn(handle: ffi::corosync_cfg_handle_t, flags: u32) {
if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
if let Some(cb) = h.callbacks.corosync_cfg_shutdown_callback_fn {
(cb)(h, flags);
}
}
}
/// Initialize a connection to the cfg library. You must call this before doing anything
/// else and use the passed back [Handle].
/// Remember to free the handle using [finalize] when finished.
pub fn initialize(callbacks: &Callbacks) -> Result<Handle> {
let mut handle: ffi::corosync_cfg_handle_t = 0;
let c_callbacks = ffi::corosync_cfg_callbacks_t {
corosync_cfg_shutdown_callback: Some(rust_shutdown_notification_fn),
};
unsafe {
let res = ffi::corosync_cfg_initialize(&mut handle, &c_callbacks);
if res == ffi::CS_OK {
let rhandle = Handle {
cfg_handle: handle,
callbacks: *callbacks,
};
HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
Ok(rhandle)
} else {
Err(CsError::from_c(res))
}
}
}
/// Finish with a connection to corosync, after calling this the [Handle] is invalid
pub fn finalize(handle: Handle) -> Result<()> {
let res = unsafe { ffi::corosync_cfg_finalize(handle.cfg_handle) };
if res == ffi::CS_OK {
HANDLE_HASH.lock().unwrap().remove(&handle.cfg_handle);
Ok(())
} else {
Err(CsError::from_c(res))
}
}
// not sure if an fd is the right thing to return here, but it will do for now.
/// Returns a file descriptor to use for poll/select on the CFG handle
pub fn fd_get(handle: Handle) -> Result<i32> {
let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
let res = unsafe { ffi::corosync_cfg_fd_get(handle.cfg_handle, c_fd) };
if res == ffi::CS_OK {
Ok(c_fd as i32)
} else {
Err(CsError::from_c(res))
}
}
/// Get the local [NodeId]
pub fn local_get(handle: Handle) -> Result<NodeId> {
let mut nodeid: u32 = 0;
let res = unsafe { ffi::corosync_cfg_local_get(handle.cfg_handle, &mut nodeid) };
if res == ffi::CS_OK {
Ok(NodeId::from(nodeid))
} else {
Err(CsError::from_c(res))
}
}
/// Reload the cluster configuration on all nodes
pub fn reload_cnfig(handle: Handle) -> Result<()> {
let res = unsafe { ffi::corosync_cfg_reload_config(handle.cfg_handle) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Re-open the cluster log files, on this node only
pub fn reopen_log_files(handle: Handle) -> Result<()> {
let res = unsafe { ffi::corosync_cfg_reopen_log_files(handle.cfg_handle) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Tell another cluster node to shutdown. reason is a string that
/// will be written to the system log files.
pub fn kill_node(handle: Handle, nodeid: NodeId, reason: &str) -> Result<()> {
let c_string = {
match CString::new(reason) {
Ok(cs) => cs,
Err(_) => return Err(CsError::CsErrInvalidParam),
}
};
let res = unsafe {
ffi::corosync_cfg_kill_node(handle.cfg_handle, u32::from(nodeid), c_string.as_ptr())
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Ask this cluster node to shutdown. If [ShutdownFlags] is set to Request then
///it may be refused by other applications
/// that have registered for shutdown callbacks.
pub fn try_shutdown(handle: Handle, flags: ShutdownFlags) -> Result<()> {
let c_flags = match flags {
ShutdownFlags::Request => 0,
ShutdownFlags::Regardless => 1,
ShutdownFlags::Immediate => 2,
};
let res = unsafe { ffi::corosync_cfg_try_shutdown(handle.cfg_handle, c_flags) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Reply to a shutdown request with Yes or No [ShutdownReply]
pub fn reply_to_shutdown(handle: Handle, flags: ShutdownReply) -> Result<()> {
let c_flags = match flags {
ShutdownReply::No => 0,
ShutdownReply::Yes => 1,
};
let res = unsafe { ffi::corosync_cfg_replyto_shutdown(handle.cfg_handle, c_flags) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Call any/all active CFG callbacks for this [Handle] see [DispatchFlags] for details
pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
let res = unsafe { ffi::corosync_cfg_dispatch(handle.cfg_handle, flags as u32) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
// Quick & dirty u8 to boolean
fn u8_to_bool(val: u8) -> bool {
val != 0
}
const CFG_MAX_LINKS: usize = 8;
const CFG_MAX_HOST_LEN: usize = 256;
fn unpack_nodestatus(c_nodestatus: ffi::corosync_cfg_node_status_v1) -> Result<NodeStatus> {
let mut ns = NodeStatus {
version: NodeStatusVersion::V1,
nodeid: NodeId::from(c_nodestatus.nodeid),
reachable: u8_to_bool(c_nodestatus.reachable),
remote: u8_to_bool(c_nodestatus.remote),
external: u8_to_bool(c_nodestatus.external),
onwire_min: c_nodestatus.onwire_min,
onwire_max: c_nodestatus.onwire_max,
onwire_ver: c_nodestatus.onwire_min,
link_status: Vec::<LinkStatus>::new(),
};
for i in 0..CFG_MAX_LINKS {
let ls = LinkStatus {
enabled: u8_to_bool(c_nodestatus.link_status[i].enabled),
connected: u8_to_bool(c_nodestatus.link_status[i].connected),
dynconnected: u8_to_bool(c_nodestatus.link_status[i].dynconnected),
mtu: c_nodestatus.link_status[i].mtu,
src_ipaddr: string_from_bytes(
&c_nodestatus.link_status[i].src_ipaddr[0],
CFG_MAX_HOST_LEN,
)?,
dst_ipaddr: string_from_bytes(
&c_nodestatus.link_status[i].dst_ipaddr[0],
CFG_MAX_HOST_LEN,
)?,
};
ns.link_status.push(ls);
}
Ok(ns)
}
// Constructor for link status to make c_ndostatus initialization tidier.
fn new_ls() -> ffi::corosync_knet_link_status_v1 {
ffi::corosync_knet_link_status_v1 {
enabled: 0,
connected: 0,
dynconnected: 0,
mtu: 0,
src_ipaddr: [0; 256],
dst_ipaddr: [0; 256],
}
}
/// Get the extended status of a node in the cluster (including active links) from its [NodeId].
/// Returns a filled in [NodeStatus] struct
pub fn node_status_get(
handle: Handle,
nodeid: NodeId,
_version: NodeStatusVersion,
) -> Result<NodeStatus> {
// Currently only supports V1 struct
unsafe {
// We need to initialize this even though it's all going to be overwritten.
let mut c_nodestatus = ffi::corosync_cfg_node_status_v1 {
version: 1,
nodeid: 0,
reachable: 0,
remote: 0,
external: 0,
onwire_min: 0,
onwire_max: 0,
onwire_ver: 0,
link_status: [new_ls(); 8],
};
let res = ffi::corosync_cfg_node_status_get(
handle.cfg_handle,
u32::from(nodeid),
1,
&mut c_nodestatus as *mut _ as *mut c_void,
);
if res == ffi::CS_OK {
unpack_nodestatus(c_nodestatus)
} else {
Err(CsError::from_c(res))
}
}
}
/// Start tracking for shutdown notifications
pub fn track_start(handle: Handle, _flags: TrackFlags) -> Result<()> {
let res = unsafe { ffi::corosync_cfg_trackstart(handle.cfg_handle, 0) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Stop tracking for shutdown notifications
pub fn track_stop(handle: Handle) -> Result<()> {
let res = unsafe { ffi::corosync_cfg_trackstop(handle.cfg_handle) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}

898
bindings/rust/src/cmap.rs Normal file
View File

@ -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<HashMap<u64, TrackHandle>> = Mutex::new(HashMap::new());
static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
}
/// Initialize a connection to the cmap subsystem.
/// map specifies which cmap "map" to use.
/// Returns a [Handle] into the cmap library
pub fn initialize(map: Map) -> Result<Handle> {
let mut handle: ffi::cmap_handle_t = 0;
let c_map = match map {
Map::Icmap => ffi::CMAP_MAP_ICMAP,
Map::Stats => ffi::CMAP_MAP_STATS,
};
unsafe {
let res = ffi::cmap_initialize_map(&mut handle, c_map);
if res == ffi::CS_OK {
let rhandle = Handle {
cmap_handle: handle,
};
HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
Ok(rhandle)
} else {
Err(CsError::from_c(res))
}
}
}
/// Finish with a connection to corosync.
/// Takes a [Handle] as returned from [initialize]
pub fn finalize(handle: Handle) -> Result<()> {
let res = unsafe { ffi::cmap_finalize(handle.cmap_handle) };
if res == ffi::CS_OK {
HANDLE_HASH.lock().unwrap().remove(&handle.cmap_handle);
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Return a file descriptor to use for poll/select on the CMAP handle.
/// Takes a [Handle] as returned from [initialize],
/// returns a C file descriptor as i32
pub fn fd_get(handle: Handle) -> Result<i32> {
let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
let res = unsafe { ffi::cmap_fd_get(handle.cmap_handle, c_fd) };
if res == ffi::CS_OK {
Ok(c_fd as i32)
} else {
Err(CsError::from_c(res))
}
}
/// Dispatch any/all active CMAP callbacks.
/// Takes a [Handle] as returned from [initialize],
/// flags [DispatchFlags] tells it how many items to dispatch before returning
pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
let res = unsafe { ffi::cmap_dispatch(handle.cmap_handle, flags as u32) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Get the current 'context' value for this handle
/// The context value is an arbitrary value that is always passed
/// back to callbacks to help identify the source
pub fn context_get(handle: Handle) -> Result<u64> {
let (res, context) = unsafe {
let mut context: u64 = 0;
let c_context: *mut c_void = &mut context as *mut _ as *mut c_void;
let r = ffi::cmap_context_get(handle.cmap_handle, c_context as *mut *const c_void);
(r, context)
};
if res == ffi::CS_OK {
Ok(context)
} else {
Err(CsError::from_c(res))
}
}
/// Set the current 'context' value for this handle
/// The context value is an arbitrary value that is always passed
/// back to callbacks to help identify the source.
/// Normally this is set in [initialize], but this allows it to be changed
pub fn context_set(handle: Handle, context: u64) -> Result<()> {
let res = unsafe {
let c_context = context as *mut c_void;
ffi::cmap_context_set(handle.cmap_handle, c_context)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// The type of data returned from [get] or in a
/// tracker callback or iterator, part of the [Data] struct
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u32)]
pub enum DataType {
Int8 = ffi::CMAP_VALUETYPE_INT8,
UInt8 = ffi::CMAP_VALUETYPE_UINT8,
Int16 = ffi::CMAP_VALUETYPE_INT16,
UInt16 = ffi::CMAP_VALUETYPE_UINT16,
Int32 = ffi::CMAP_VALUETYPE_INT32,
UInt32 = ffi::CMAP_VALUETYPE_UINT32,
Int64 = ffi::CMAP_VALUETYPE_INT64,
UInt64 = ffi::CMAP_VALUETYPE_UINT64,
Float = ffi::CMAP_VALUETYPE_FLOAT,
Double = ffi::CMAP_VALUETYPE_DOUBLE,
String = ffi::CMAP_VALUETYPE_STRING,
Binary = ffi::CMAP_VALUETYPE_BINARY,
Unknown = 999,
}
fn cmap_to_enum(cmap_type: u32) -> DataType {
match DataType::try_from(cmap_type) {
Ok(e) => e,
Err(_) => DataType::Unknown,
}
}
/// Data returned from the cmap::get() call and tracker & iterators.
/// Contains the data itself and the type of that data.
pub enum Data {
Int8(i8),
UInt8(u8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
Int64(i64),
UInt64(u64),
Float(f32),
Double(f64),
String(String),
Binary(Vec<u8>),
Unknown,
}
impl fmt::Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DataType::Int8 => write!(f, "Int8"),
DataType::UInt8 => write!(f, "UInt8"),
DataType::Int16 => write!(f, "Int16"),
DataType::UInt16 => write!(f, "UInt16"),
DataType::Int32 => write!(f, "Int32"),
DataType::UInt32 => write!(f, "UInt32"),
DataType::Int64 => write!(f, "Int64"),
DataType::UInt64 => write!(f, "UInt64"),
DataType::Float => write!(f, "Float"),
DataType::Double => write!(f, "Double"),
DataType::String => write!(f, "String"),
DataType::Binary => write!(f, "Binary"),
DataType::Unknown => write!(f, "Unknown"),
}
}
}
impl fmt::Display for Data {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Data::Int8(v) => write!(f, "{} (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<CString> {
if maxlen > 0 && key.chars().count() >= maxlen {
return Err(CsError::CsErrInvalidParam);
}
match CString::new(key) {
Ok(n) => Ok(n),
Err(_) => Err(CsError::CsErrLibrary),
}
}
fn set_value(
handle: Handle,
key_name: &str,
datatype: DataType,
value: *mut c_void,
length: usize,
) -> Result<()> {
let csname = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?;
let res = unsafe {
ffi::cmap_set(
handle.cmap_handle,
csname.as_ptr(),
value,
length,
datatype as u32,
)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
// Returns type and size
fn generic_to_cmap<T>(_value: T) -> (DataType, usize) {
match type_name::<T>() {
"u8" => (DataType::UInt8, 1),
"i8" => (DataType::Int8, 1),
"u16" => (DataType::UInt16, 2),
"i16" => (DataType::Int16, 2),
"u32" => (DataType::UInt32, 4),
"i32" => (DataType::Int32, 4),
"u64" => (DataType::UInt64, 4),
"f32" => (DataType::Float, 4),
"f64" => (DataType::Double, 8),
"&str" => (DataType::String, 0),
// Binary not currently supported here
_ => (DataType::Unknown, 0),
}
}
fn is_numeric_type(dtype: DataType) -> bool {
matches!(
dtype,
DataType::UInt8
| DataType::Int8
| DataType::UInt16
| DataType::Int16
| DataType::UInt32
| DataType::Int32
| DataType::UInt64
| DataType::Int64
| DataType::Float
| DataType::Double
)
}
/// Function to set a generic numeric value
/// This doesn't work for strings or binaries
pub fn set_number<T: Copy>(handle: Handle, key_name: &str, value: T) -> Result<()> {
let (c_type, c_size) = generic_to_cmap(value);
if is_numeric_type(c_type) {
let mut tmp = value;
let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
set_value(handle, key_name, c_type, c_value as *mut c_void, c_size)
} else {
Err(CsError::CsErrNotSupported)
}
}
pub fn set_u8(handle: Handle, key_name: &str, value: u8) -> Result<()> {
let mut tmp = value;
let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
set_value(handle, key_name, DataType::UInt8, c_value as *mut c_void, 1)
}
/// Sets an i8 value into cmap
pub fn set_i8(handle: Handle, key_name: &str, value: i8) -> Result<()> {
let mut tmp = value;
let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
set_value(handle, key_name, DataType::Int8, c_value as *mut c_void, 1)
}
/// Sets a u16 value into cmap
pub fn set_u16(handle: Handle, key_name: &str, value: u16) -> Result<()> {
let mut tmp = value;
let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
set_value(
handle,
key_name,
DataType::UInt16,
c_value as *mut c_void,
2,
)
}
/// Sets an i16 value into cmap
pub fn set_i16(handle: Handle, key_name: &str, value: i16) -> Result<()> {
let mut tmp = value;
let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
set_value(handle, key_name, DataType::Int16, c_value as *mut c_void, 2)
}
/// Sets a u32 value into cmap
pub fn set_u32(handle: Handle, key_name: &str, value: u32) -> Result<()> {
let mut tmp = value;
let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
set_value(handle, key_name, DataType::UInt32, c_value, 4)
}
/// Sets an i32 value into cmap
pub fn set_i132(handle: Handle, key_name: &str, value: i32) -> Result<()> {
let mut tmp = value;
let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
set_value(handle, key_name, DataType::Int32, c_value as *mut c_void, 4)
}
/// Sets a u64 value into cmap
pub fn set_u64(handle: Handle, key_name: &str, value: u64) -> Result<()> {
let mut tmp = value;
let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
set_value(
handle,
key_name,
DataType::UInt64,
c_value as *mut c_void,
8,
)
}
/// Sets an i64 value into cmap
pub fn set_i164(handle: Handle, key_name: &str, value: i64) -> Result<()> {
let mut tmp = value;
let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void;
set_value(handle, key_name, DataType::Int64, c_value as *mut c_void, 8)
}
/// Sets a string value into cmap
pub fn set_string(handle: Handle, key_name: &str, value: &str) -> Result<()> {
let v_string = string_to_cstring_validated(value, 0)?;
set_value(
handle,
key_name,
DataType::String,
v_string.as_ptr() as *mut c_void,
value.chars().count(),
)
}
/// Sets a binary value into cmap
pub fn set_binary(handle: Handle, key_name: &str, value: &[u8]) -> Result<()> {
set_value(
handle,
key_name,
DataType::Binary,
value.as_ptr() as *mut c_void,
value.len(),
)
}
/// Sets a [Data] type into cmap
pub fn set(handle: Handle, key_name: &str, data: &Data) -> Result<()> {
let (datatype, datalen, c_value) = match data {
Data::Int8(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::Int8, 1, cv)
}
Data::UInt8(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::UInt8, 1, cv)
}
Data::Int16(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::Int16, 2, cv)
}
Data::UInt16(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::UInt8, 2, cv)
}
Data::Int32(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::Int32, 4, cv)
}
Data::UInt32(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::UInt32, 4, cv)
}
Data::Int64(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::Int64, 8, cv)
}
Data::UInt64(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::UInt64, 8, cv)
}
Data::Float(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::Float, 4, cv)
}
Data::Double(v) => {
let mut tmp = *v;
let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void;
(DataType::Double, 8, cv)
}
Data::String(v) => {
let cv = string_to_cstring_validated(v, 0)?;
// Can't let cv go out of scope
return set_value(
handle,
key_name,
DataType::String,
cv.as_ptr() as *mut c_void,
v.chars().count(),
);
}
Data::Binary(v) => {
// Vec doesn't return quite the right types.
return set_value(
handle,
key_name,
DataType::Binary,
v.as_ptr() as *mut c_void,
v.len(),
);
}
Data::Unknown => return Err(CsError::CsErrInvalidParam),
};
set_value(handle, key_name, datatype, c_value, datalen)
}
// Local function to parse out values from the C mess
// Assumes the c_value is complete. So cmap::get() will need to check the size
// and re-get before calling us with a resized buffer
fn c_to_data(value_size: usize, c_key_type: u32, c_value: *const u8) -> Result<Data> {
unsafe {
match cmap_to_enum(c_key_type) {
DataType::UInt8 => {
let mut ints = [0u8; 1];
copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() 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::<u8>::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::<u8>::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<Data> {
let csname = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?;
let mut value_size: usize = 16;
let mut c_key_type: u32 = 0;
let mut c_value = Vec::<u8>::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<TrackHandle> {
let c_name = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?;
let mut c_trackhandle = 0u64;
let res = unsafe {
ffi::cmap_track_add(
handle.cmap_handle,
c_name.as_ptr(),
track_type.bits,
Some(rust_notify_fn),
user_data as *mut c_void,
&mut c_trackhandle,
)
};
if res == ffi::CS_OK {
let rhandle = TrackHandle {
track_handle: c_trackhandle,
notify_callback: *notify_callback,
};
TRACKHANDLE_HASH
.lock()
.unwrap()
.insert(c_trackhandle, rhandle);
Ok(rhandle)
} else {
Err(CsError::from_c(res))
}
}
/// Remove a tracker frm this [Handle]
pub fn track_delete(handle: Handle, track_handle: TrackHandle) -> Result<()> {
let res = unsafe { ffi::cmap_track_delete(handle.cmap_handle, track_handle.track_handle) };
if res == ffi::CS_OK {
TRACKHANDLE_HASH
.lock()
.unwrap()
.remove(&track_handle.track_handle);
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Create one of these to start iterating over cmap values.
pub struct CmapIterStart {
iter_handle: u64,
cmap_handle: u64,
}
pub struct CmapIntoIter {
cmap_handle: u64,
iter_handle: u64,
}
/// Value returned from the iterator. contains the key name and the [Data]
pub struct CmapIter {
key_name: String,
data: Data,
}
impl CmapIter {
pub fn key_name(&self) -> &str {
&self.key_name
}
pub fn data(&self) -> &Data {
&self.data
}
}
impl fmt::Debug for CmapIter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.key_name, self.data)
}
}
impl Iterator for CmapIntoIter {
type Item = CmapIter;
fn next(&mut self) -> Option<CmapIter> {
let mut c_key_name = [0u8; CMAP_KEYNAME_MAXLENGTH + 1];
let mut c_value_len = 0usize;
let mut c_value_type = 0u32;
let res = unsafe {
ffi::cmap_iter_next(
self.cmap_handle,
self.iter_handle,
c_key_name.as_mut_ptr() as *mut c_char,
&mut c_value_len,
&mut c_value_type,
)
};
if res == ffi::CS_OK {
// Return the Data for this iteration
let mut c_value = Vec::<u8>::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<CmapIterStart> {
let mut iter_handle: u64 = 0;
let res = unsafe {
let c_prefix = string_to_cstring_validated(prefix, CMAP_KEYNAME_MAXLENGTH)?;
ffi::cmap_iter_init(cmap_handle.cmap_handle, c_prefix.as_ptr(), &mut iter_handle)
};
if res == ffi::CS_OK {
Ok(CmapIterStart {
cmap_handle: cmap_handle.cmap_handle,
iter_handle,
})
} else {
Err(CsError::from_c(res))
}
}
}
impl IntoIterator for CmapIterStart {
type Item = CmapIter;
type IntoIter = CmapIntoIter;
fn into_iter(self) -> Self::IntoIter {
CmapIntoIter {
iter_handle: self.iter_handle,
cmap_handle: self.cmap_handle,
}
}
}

628
bindings/rust/src/cpg.rs Normal file
View File

@ -0,0 +1,628 @@
// libcpg interface for Rust
// Copyright (c) 2020 Red Hat, Inc.
//
// All rights reserved.
//
// Author: Christine Caulfield (ccaulfi@redhat.com)
//
#![allow(clippy::single_match)]
#![allow(clippy::needless_range_loop)]
#![allow(clippy::type_complexity)]
// For the code generated by bindgen
use crate::sys::cpg as ffi;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fmt;
use std::os::raw::{c_int, c_void};
use std::ptr::copy_nonoverlapping;
use std::slice;
use std::string::String;
use std::sync::Mutex;
// General corosync things
use crate::string_from_bytes;
use crate::{CsError, DispatchFlags, NodeId, Result};
const CPG_NAMELEN_MAX: usize = 128;
const CPG_MEMBERS_MAX: usize = 128;
/// RingId returned by totem_confchg_fn
#[derive(Copy, Clone)]
pub struct RingId {
pub nodeid: NodeId,
pub seq: u64,
}
/// Totem delivery guarantee options for [mcast_joined]
// The C enum doesn't have numbers in the code
// so don't assume we can match them
#[derive(Copy, Clone)]
pub enum Guarantee {
TypeUnordered,
TypeFifo,
TypeAgreed,
TypeSafe,
}
// Convert internal to cpg.h values.
impl Guarantee {
pub fn to_c(&self) -> u32 {
match self {
Guarantee::TypeUnordered => ffi::CPG_TYPE_UNORDERED,
Guarantee::TypeFifo => ffi::CPG_TYPE_FIFO,
Guarantee::TypeAgreed => ffi::CPG_TYPE_AGREED,
Guarantee::TypeSafe => ffi::CPG_TYPE_SAFE,
}
}
}
/// Flow control state returned from [flow_control_state_get]
#[derive(Copy, Clone)]
pub enum FlowControlState {
Disabled,
Enabled,
}
/// No flags current specified for model1 so leave this at None
#[derive(Copy, Clone)]
pub enum Model1Flags {
None,
}
/// Reason for cpg item callback
#[derive(Copy, Clone)]
pub enum Reason {
Undefined = 0,
Join = 1,
Leave = 2,
NodeDown = 3,
NodeUp = 4,
ProcDown = 5,
}
// Convert to cpg.h values
impl Reason {
pub fn new(r: u32) -> Reason {
match r {
0 => Reason::Undefined,
1 => Reason::Join,
2 => Reason::Leave,
3 => Reason::NodeDown,
4 => Reason::NodeUp,
5 => Reason::ProcDown,
_ => Reason::Undefined,
}
}
}
impl fmt::Display for Reason {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Reason::Undefined => write!(f, "Undefined"),
Reason::Join => write!(f, "Join"),
Reason::Leave => write!(f, "Leave"),
Reason::NodeDown => write!(f, "NodeDown"),
Reason::NodeUp => write!(f, "NodeUp"),
Reason::ProcDown => write!(f, "ProcDown"),
}
}
}
/// A CPG address entry returned in the callbacks
pub struct Address {
pub nodeid: NodeId,
pub pid: u32,
pub reason: Reason,
}
impl fmt::Debug for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"[nodeid: {}, pid: {}, reason: {}]",
self.nodeid, self.pid, self.reason
)
}
}
/// Data for model1 [initialize]
#[derive(Copy, Clone)]
pub struct Model1Data {
pub flags: Model1Flags,
pub deliver_fn: Option<
fn(
handle: &Handle,
group_name: String,
nodeid: NodeId,
pid: u32,
msg: &[u8],
msg_len: usize,
),
>,
pub confchg_fn: Option<
fn(
handle: &Handle,
group_name: &str,
member_list: Vec<Address>,
left_list: Vec<Address>,
joined_list: Vec<Address>,
),
>,
pub totem_confchg_fn: Option<fn(handle: &Handle, ring_id: RingId, member_list: Vec<NodeId>)>,
}
/// Modeldata for [initialize], only v1 supported at the moment
#[derive(Copy, Clone)]
pub enum ModelData {
ModelNone,
ModelV1(Model1Data),
}
/// A handle into the cpg library. Returned from [initialize] and needed for all other calls
#[derive(Copy, Clone)]
pub struct Handle {
cpg_handle: u64, // Corosync library handle
model_data: ModelData,
}
// Used to convert a CPG handle into one of ours
lazy_static! {
static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
}
// Convert a Rust String into a cpg_name struct for libcpg
fn string_to_cpg_name(group: &str) -> Result<ffi::cpg_name> {
if group.len() > CPG_NAMELEN_MAX - 1 {
return Err(CsError::CsErrInvalidParam);
}
let c_name = match CString::new(group) {
Ok(n) => n,
Err(_) => return Err(CsError::CsErrLibrary),
};
let mut c_group = ffi::cpg_name {
length: group.len() as u32,
value: [0; CPG_NAMELEN_MAX],
};
unsafe {
// NOTE param order is 'wrong-way round' from C
copy_nonoverlapping(c_name.as_ptr(), c_group.value.as_mut_ptr(), group.len());
}
Ok(c_group)
}
// Convert an array of cpg_addresses to a Vec<cpg::Address> - used in callbacks
fn cpg_array_to_vec(list: *const ffi::cpg_address, list_entries: usize) -> Vec<Address> {
let temp: &[ffi::cpg_address] = unsafe { slice::from_raw_parts(list, list_entries) };
let mut r_vec = Vec::<Address>::new();
for i in 0..list_entries {
let a: Address = Address {
nodeid: NodeId::from(temp[i].nodeid),
pid: temp[i].pid,
reason: Reason::new(temp[i].reason),
};
r_vec.push(a);
}
r_vec
}
// Called from CPG callback function - munge params back to Rust from C
extern "C" fn rust_deliver_fn(
handle: ffi::cpg_handle_t,
group_name: *const ffi::cpg_name,
nodeid: u32,
pid: u32,
msg: *mut ::std::os::raw::c_void,
msg_len: usize,
) {
if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
// Convert group_name into a Rust str.
let r_group_name = unsafe {
CStr::from_ptr(&(*group_name).value[0])
.to_string_lossy()
.into_owned()
};
let data: &[u8] = unsafe { std::slice::from_raw_parts(msg as *const u8, msg_len) };
match h.model_data {
ModelData::ModelV1(md) => {
if let Some(cb) = md.deliver_fn {
(cb)(h, r_group_name, NodeId::from(nodeid), pid, data, msg_len);
}
}
_ => {}
}
}
}
// Called from CPG callback function - munge params back to Rust from C
extern "C" fn rust_confchg_fn(
handle: ffi::cpg_handle_t,
group_name: *const ffi::cpg_name,
member_list: *const ffi::cpg_address,
member_list_entries: usize,
left_list: *const ffi::cpg_address,
left_list_entries: usize,
joined_list: *const ffi::cpg_address,
joined_list_entries: usize,
) {
if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
let r_group_name = unsafe {
CStr::from_ptr(&(*group_name).value[0])
.to_string_lossy()
.into_owned()
};
let r_member_list = cpg_array_to_vec(member_list, member_list_entries);
let r_left_list = cpg_array_to_vec(left_list, left_list_entries);
let r_joined_list = cpg_array_to_vec(joined_list, joined_list_entries);
match h.model_data {
ModelData::ModelV1(md) => {
if let Some(cb) = md.confchg_fn {
(cb)(h, &r_group_name, r_member_list, r_left_list, r_joined_list);
}
}
_ => {}
}
}
}
// Called from CPG callback function - munge params back to Rust from C
extern "C" fn rust_totem_confchg_fn(
handle: ffi::cpg_handle_t,
ring_id: ffi::cpg_ring_id,
member_list_entries: u32,
member_list: *const u32,
) {
if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
let r_ring_id = RingId {
nodeid: NodeId::from(ring_id.nodeid),
seq: ring_id.seq,
};
let mut r_member_list = Vec::<NodeId>::new();
let temp_members: &[u32] =
unsafe { slice::from_raw_parts(member_list, member_list_entries as usize) };
for i in 0..member_list_entries as usize {
r_member_list.push(NodeId::from(temp_members[i]));
}
match h.model_data {
ModelData::ModelV1(md) => {
if let Some(cb) = md.totem_confchg_fn {
(cb)(h, r_ring_id, r_member_list);
}
}
_ => {}
}
}
}
/// Initialize a connection to the cpg library. You must call this before doing anything
/// else and use the passed back [Handle].
/// Remember to free the handle using [finalize] when finished.
pub fn initialize(model_data: &ModelData, context: u64) -> Result<Handle> {
let mut handle: ffi::cpg_handle_t = 0;
let mut m = match model_data {
ModelData::ModelV1(_v1) => {
ffi::cpg_model_v1_data_t {
model: ffi::CPG_MODEL_V1,
cpg_deliver_fn: Some(rust_deliver_fn),
cpg_confchg_fn: Some(rust_confchg_fn),
cpg_totem_confchg_fn: Some(rust_totem_confchg_fn),
flags: 0, // No supported flags (yet)
}
}
_ => return Err(CsError::CsErrInvalidParam),
};
unsafe {
let c_context: *mut c_void = &mut &context as *mut _ as *mut c_void;
let c_model: *mut ffi::cpg_model_data_t = &mut m as *mut _ as *mut ffi::cpg_model_data_t;
let res = ffi::cpg_model_initialize(&mut handle, m.model, c_model, c_context);
if res == ffi::CS_OK {
let rhandle = Handle {
cpg_handle: handle,
model_data: *model_data,
};
HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
Ok(rhandle)
} else {
Err(CsError::from_c(res))
}
}
}
/// Finish with a connection to corosync
pub fn finalize(handle: Handle) -> Result<()> {
let res = unsafe { ffi::cpg_finalize(handle.cpg_handle) };
if res == ffi::CS_OK {
HANDLE_HASH.lock().unwrap().remove(&handle.cpg_handle);
Ok(())
} else {
Err(CsError::from_c(res))
}
}
// Not sure if an FD is the right thing to return here, but it will do for now.
/// Returns a file descriptor to use for poll/select on the CPG handle
pub fn fd_get(handle: Handle) -> Result<i32> {
let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
let res = unsafe { ffi::cpg_fd_get(handle.cpg_handle, c_fd) };
if res == ffi::CS_OK {
Ok(c_fd as i32)
} else {
Err(CsError::from_c(res))
}
}
/// Call any/all active CPG callbacks for this [Handle] see [DispatchFlags] for details
pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
let res = unsafe { ffi::cpg_dispatch(handle.cpg_handle, flags as u32) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Joins a CPG group for sending and receiving messages
pub fn join(handle: Handle, group: &str) -> Result<()> {
let res = unsafe {
let c_group = string_to_cpg_name(group)?;
ffi::cpg_join(handle.cpg_handle, &c_group)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Leave the currently joined CPG group, another group can now be joined on
/// the same [Handle] or [finalize] can be called to finish using CPG
pub fn leave(handle: Handle, group: &str) -> Result<()> {
let res = unsafe {
let c_group = string_to_cpg_name(group)?;
ffi::cpg_leave(handle.cpg_handle, &c_group)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Get the local node ID
pub fn local_get(handle: Handle) -> Result<NodeId> {
let mut nodeid: u32 = 0;
let res = unsafe { ffi::cpg_local_get(handle.cpg_handle, &mut nodeid) };
if res == ffi::CS_OK {
Ok(NodeId::from(nodeid))
} else {
Err(CsError::from_c(res))
}
}
/// Get a list of members of a CPG group as a vector of [Address] structs
pub fn membership_get(handle: Handle, group: &str) -> Result<Vec<Address>> {
let mut member_list_entries: i32 = 0;
let member_list = [ffi::cpg_address {
nodeid: 0,
pid: 0,
reason: 0,
}; CPG_MEMBERS_MAX];
let res = unsafe {
let mut c_group = string_to_cpg_name(group)?;
let c_memlist = member_list.as_ptr() as *mut ffi::cpg_address;
ffi::cpg_membership_get(
handle.cpg_handle,
&mut c_group,
&mut *c_memlist,
&mut member_list_entries,
)
};
if res == ffi::CS_OK {
Ok(cpg_array_to_vec(
member_list.as_ptr(),
member_list_entries as usize,
))
} else {
Err(CsError::from_c(res))
}
}
/// Get the maximum size that CPG can send in one corosync message,
/// any messages sent via [mcast_joined] that are larger than this
/// will be fragmented
pub fn max_atomic_msgsize_get(handle: Handle) -> Result<u32> {
let mut asize: u32 = 0;
let res = unsafe { ffi::cpg_max_atomic_msgsize_get(handle.cpg_handle, &mut asize) };
if res == ffi::CS_OK {
Ok(asize)
} else {
Err(CsError::from_c(res))
}
}
/// Get the current 'context' value for this handle.
/// The context value is an arbitrary value that is always passed
/// back to callbacks to help identify the source
pub fn context_get(handle: Handle) -> Result<u64> {
let mut c_context: *mut c_void = &mut 0u64 as *mut _ as *mut c_void;
let (res, context) = unsafe {
let r = ffi::cpg_context_get(handle.cpg_handle, &mut c_context);
let context: u64 = c_context as u64;
(r, context)
};
if res == ffi::CS_OK {
Ok(context)
} else {
Err(CsError::from_c(res))
}
}
/// Set the current 'context' value for this handle.
/// The context value is an arbitrary value that is always passed
/// back to callbacks to help identify the source.
/// Normally this is set in [initialize], but this allows it to be changed
pub fn context_set(handle: Handle, context: u64) -> Result<()> {
let res = unsafe {
let c_context = context as *mut c_void;
ffi::cpg_context_set(handle.cpg_handle, c_context)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Get the flow control state of corosync CPG
pub fn flow_control_state_get(handle: Handle) -> Result<bool> {
let mut fc_state: u32 = 0;
let res = unsafe { ffi::cpg_flow_control_state_get(handle.cpg_handle, &mut fc_state) };
if res == ffi::CS_OK {
if fc_state == 1 {
Ok(true)
} else {
Ok(false)
}
} else {
Err(CsError::from_c(res))
}
}
/// Send a message to the currently joined CPG group
pub fn mcast_joined(handle: Handle, guarantee: Guarantee, msg: &[u8]) -> Result<()> {
let c_iovec = ffi::iovec {
iov_base: msg.as_ptr() as *mut c_void,
iov_len: msg.len(),
};
let res = unsafe { ffi::cpg_mcast_joined(handle.cpg_handle, guarantee.to_c(), &c_iovec, 1) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Type of iteration for [CpgIterStart]
#[derive(Copy, Clone)]
pub enum CpgIterType {
NameOnly = 1,
OneGroup = 2,
All = 3,
}
// Iterator based on information on this page. thank you!
// https://stackoverflow.com/questions/30218886/how-to-implement-iterator-and-intoiterator-for-a-simple-struct
// Object to iterate over
/// An object to iterate over a list of CPG groups, create one of these and then use 'for' over it
pub struct CpgIterStart {
iter_handle: u64,
}
/// struct returned from iterating over a [CpgIterStart]
pub struct CpgIter {
pub group: String,
pub nodeid: NodeId,
pub pid: u32,
}
pub struct CpgIntoIter {
iter_handle: u64,
}
impl fmt::Debug for CpgIter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"[group: {}, nodeid: {}, pid: {}]",
self.group, self.nodeid, self.pid
)
}
}
impl Iterator for CpgIntoIter {
type Item = CpgIter;
fn next(&mut self) -> Option<CpgIter> {
let mut c_iter_description = ffi::cpg_iteration_description_t {
nodeid: 0,
pid: 0,
group: ffi::cpg_name {
length: 0_u32,
value: [0; CPG_NAMELEN_MAX],
},
};
let res = unsafe { ffi::cpg_iteration_next(self.iter_handle, &mut c_iter_description) };
if res == ffi::CS_OK {
let r_group =
match string_from_bytes(c_iter_description.group.value.as_ptr(), CPG_NAMELEN_MAX) {
Ok(groupname) => groupname,
Err(_) => return None,
};
Some(CpgIter {
group: r_group,
nodeid: NodeId::from(c_iter_description.nodeid),
pid: c_iter_description.pid,
})
} else if res == ffi::CS_ERR_NO_SECTIONS {
// End of list
unsafe {
// Yeah, we don't check this return code. There's nowhere to report it.
ffi::cpg_iteration_finalize(self.iter_handle)
};
None
} else {
None
}
}
}
impl CpgIterStart {
/// Create a new [CpgIterStart] object for iterating over a list of active CPG groups
pub fn new(cpg_handle: Handle, group: &str, iter_type: CpgIterType) -> Result<CpgIterStart> {
let mut iter_handle: u64 = 0;
let res = unsafe {
let mut c_group = string_to_cpg_name(group)?;
let c_itertype = iter_type as u32;
// IterType 'All' requires that the group pointer is passed in as NULL
let c_group_ptr = {
match iter_type {
CpgIterType::All => std::ptr::null_mut(),
_ => &mut c_group,
}
};
ffi::cpg_iteration_initialize(
cpg_handle.cpg_handle,
c_itertype,
c_group_ptr,
&mut iter_handle,
)
};
if res == ffi::CS_OK {
Ok(CpgIterStart { iter_handle })
} else {
Err(CsError::from_c(res))
}
}
}
impl IntoIterator for CpgIterStart {
type Item = CpgIter;
type IntoIter = CpgIntoIter;
fn into_iter(self) -> Self::IntoIter {
CpgIntoIter {
iter_handle: self.iter_handle,
}
}
}

297
bindings/rust/src/lib.rs Normal file
View File

@ -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 <https://corosync.github.io/corosync/>
//!
//! No more information about corosync itself will be provided here, it is expected that if
//! you feel you need access to the Corosync API calls, you know what they do :)
//!
//! # Example
//! ```
//! extern crate rust_corosync as corosync;
//! use corosync::cmap;
//!
//! fn main()
//! {
//! // Open connection to corosync libcmap
//! let handle =
//! match cmap::initialize(cmap::Map::Icmap) {
//! Ok(h) => {
//! println!("cmap initialized.");
//! h
//! }
//! Err(e) => {
//! println!("Error in CMAP (Icmap) init: {}", e);
//! return;
//! }
//! };
//!
//! // Set a numeric value (this is a generic fn)
//! match cmap::set_number(handle, "test.test_uint32", 456)
//! {
//! Ok(_) => {}
//! Err(e) => {
//! println!("Error in CMAP set_u32: {}", e);
//! return;
//! }
//! };
//!
//! // Get a value - this will be a Data struct
//! match cmap::get(handle, "test.test_uint32")
//! {
//! Ok(v) => {
//! println!("GOT value {}", v);
//! }
//! Err(e) => {
//! println!("Error in CMAP get: {}", e);
//! return;
//! }
//! };
//!
//! // Use an iterator
//! match cmap::CmapIterStart::new(handle, "totem.") {
//! Ok(cmap_iter) => {
//! for i in cmap_iter {
//! println!("ITER: {:?}", i);
//! }
//! println!("");
//! }
//! Err(e) => {
//! println!("Error in CMAP iter start: {}", e);
//! }
//! }
//!
//! // Close this connection
//! match cmap::finalize(handle)
//! {
//! Ok(_) => {}
//! Err(e) => {
//! println!("Error in CMAP get: {}", e);
//! return;
//! }
//! };
//! }
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate bitflags;
/// cfg is the internal configuration and information library for corosync, it is
/// mainly used by internal tools but may also contain API calls useful to some applications
/// that need detailed information about or control of the operation of corosync and the cluster.
pub mod cfg;
/// cmap is the internal 'database' of corosync - though it is NOT replicated. Mostly it contains
/// a copy of the corosync.conf file and information about the running state of the daemon.
/// The cmap API provides two 'maps'. Icmap, which is as above, and Stats, which contains very detailed
/// statistics on the running system, this includes network and IPC calls.
pub mod cmap;
/// cpg is the Control Process Groups subsystem of corosync and is usually used for sending
/// messages around the cluster. All processes using CPG belong to a named group (whose members
/// they can query) and all messages are sent with delivery guarantees.
pub mod cpg;
/// Quorum provides basic information about the quorate state of the cluster with callbacks
/// when nodelists change.
pub mod quorum;
///votequorum is the main quorum provider for corosync, using this API, users can query the state
/// of nodes in the cluster, request callbacks when the nodelists change, and set up a quorum device.
pub mod votequorum;
mod sys;
use num_enum::TryFromPrimitive;
use std::convert::TryFrom;
use std::error::Error;
use std::ffi::CString;
use std::fmt;
use std::ptr::copy_nonoverlapping;
// This needs to be kept up-to-date!
/// Error codes returned from the corosync libraries
#[derive(Debug, Eq, PartialEq, Copy, Clone, TryFromPrimitive)]
#[repr(u32)]
pub enum CsError {
CsOk = 1,
CsErrLibrary = 2,
CsErrVersion = 3,
CsErrInit = 4,
CsErrTimeout = 5,
CsErrTryAgain = 6,
CsErrInvalidParam = 7,
CsErrNoMemory = 8,
CsErrBadHandle = 9,
CsErrBusy = 10,
CsErrAccess = 11,
CsErrNotExist = 12,
CsErrNameTooLong = 13,
CsErrExist = 14,
CsErrNoSpace = 15,
CsErrInterrupt = 16,
CsErrNameNotFound = 17,
CsErrNoResources = 18,
CsErrNotSupported = 19,
CsErrBadOperation = 20,
CsErrFailedOperation = 21,
CsErrMessageError = 22,
CsErrQueueFull = 23,
CsErrQueueNotAvailable = 24,
CsErrBadFlags = 25,
CsErrTooBig = 26,
CsErrNoSection = 27,
CsErrContextNotFound = 28,
CsErrTooManyGroups = 30,
CsErrSecurity = 100,
#[num_enum(default)]
CsErrRustCompat = 998, // Set if we get a unknown return from corosync
CsErrRustString = 999, // Set if we get a string conversion error
}
/// Result type returned from most corosync library calls.
/// Contains a [CsError] and possibly other data as required
pub type Result<T> = ::std::result::Result<T, CsError>;
impl fmt::Display for CsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CsError::CsOk => write!(f, "OK"),
CsError::CsErrLibrary => write!(f, "ErrLibrary"),
CsError::CsErrVersion => write!(f, "ErrVersion"),
CsError::CsErrInit => write!(f, "ErrInit"),
CsError::CsErrTimeout => write!(f, "ErrTimeout"),
CsError::CsErrTryAgain => write!(f, "ErrTryAgain"),
CsError::CsErrInvalidParam => write!(f, "ErrInvalidParam"),
CsError::CsErrNoMemory => write!(f, "ErrNoMemory"),
CsError::CsErrBadHandle => write!(f, "ErrbadHandle"),
CsError::CsErrBusy => write!(f, "ErrBusy"),
CsError::CsErrAccess => write!(f, "ErrAccess"),
CsError::CsErrNotExist => write!(f, "ErrNotExist"),
CsError::CsErrNameTooLong => write!(f, "ErrNameTooLong"),
CsError::CsErrExist => write!(f, "ErrExist"),
CsError::CsErrNoSpace => write!(f, "ErrNoSpace"),
CsError::CsErrInterrupt => write!(f, "ErrInterrupt"),
CsError::CsErrNameNotFound => write!(f, "ErrNameNotFound"),
CsError::CsErrNoResources => write!(f, "ErrNoResources"),
CsError::CsErrNotSupported => write!(f, "ErrNotSupported"),
CsError::CsErrBadOperation => write!(f, "ErrBadOperation"),
CsError::CsErrFailedOperation => write!(f, "ErrFailedOperation"),
CsError::CsErrMessageError => write!(f, "ErrMEssageError"),
CsError::CsErrQueueFull => write!(f, "ErrQueueFull"),
CsError::CsErrQueueNotAvailable => write!(f, "ErrQueueNotAvailable"),
CsError::CsErrBadFlags => write!(f, "ErrBadFlags"),
CsError::CsErrTooBig => write!(f, "ErrTooBig"),
CsError::CsErrNoSection => write!(f, "ErrNoSection"),
CsError::CsErrContextNotFound => write!(f, "ErrContextNotFound"),
CsError::CsErrTooManyGroups => write!(f, "ErrTooManyGroups"),
CsError::CsErrSecurity => write!(f, "ErrSecurity"),
CsError::CsErrRustCompat => write!(f, "ErrRustCompat"),
CsError::CsErrRustString => write!(f, "ErrRustString"),
}
}
}
impl Error for CsError {}
// This is dependant on the num_enum crate, converts a C cs_error_t into the Rust enum
// There seems to be some debate as to whether this should be part of the language:
// https://internals.rust-lang.org/t/pre-rfc-enum-from-integer/6348/25
impl CsError {
fn from_c(cserr: u32) -> CsError {
match CsError::try_from(cserr) {
Ok(e) => e,
Err(_) => CsError::CsErrRustCompat,
}
}
}
/// Flags to use with dispatch functions, eg [cpg::dispatch]
/// One will dispatch a single callback (blocking) and return.
/// All will loop trying to dispatch all possible callbacks.
/// Blocking is like All but will block between callbacks.
/// OneNonBlocking will dispatch a single callback only if one is available,
/// otherwise it will return even if no callback is available.
#[derive(Copy, Clone)]
// The numbers match the C enum, of course.
pub enum DispatchFlags {
One = 1,
All = 2,
Blocking = 3,
OneNonblocking = 4,
}
/// Flags to use with (most) tracking API calls
#[derive(Copy, Clone)]
// Same here
pub enum TrackFlags {
Current = 1,
Changes = 2,
ChangesOnly = 4,
}
/// A corosync nodeid
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct NodeId {
id: u32,
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.id)
}
}
// Conversion from a NodeId to and from u32
impl From<u32> for NodeId {
fn from(id: u32) -> NodeId {
NodeId { id }
}
}
impl From<NodeId> for u32 {
fn from(nodeid: NodeId) -> u32 {
nodeid.id
}
}
// General internal routine to copy bytes from a C array into a Rust String
fn string_from_bytes(bytes: *const ::std::os::raw::c_char, max_length: usize) -> Result<String> {
let mut newbytes = Vec::<u8>::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),
}
}

298
bindings/rust/src/quorum.rs Normal file
View File

@ -0,0 +1,298 @@
// libquorum interface for Rust
// Copyright (c) 2021 Red Hat, Inc.
//
// All rights reserved.
//
// Author: Christine Caulfield (ccaulfi@redhat.com)
//
#![allow(clippy::type_complexity)]
#![allow(clippy::needless_range_loop)]
#![allow(clippy::single_match)]
// For the code generated by bindgen
use crate::sys::quorum as ffi;
use crate::{CsError, DispatchFlags, NodeId, Result, TrackFlags};
use std::collections::HashMap;
use std::os::raw::{c_int, c_void};
use std::slice;
use std::sync::Mutex;
/// Data for model1 [initialize]
#[derive(Copy, Clone)]
pub enum ModelData {
ModelNone,
ModelV1(Model1Data),
}
/// Value returned from [initialize]. Indicates whether quorum is currently active on this cluster.
pub enum QuorumType {
Free,
Set,
}
/// Flags for [initialize], none currently supported
#[derive(Copy, Clone)]
pub enum Model1Flags {
None,
}
/// RingId returned in quorum_notification_fn
pub struct RingId {
pub nodeid: NodeId,
pub seq: u64,
}
// Used to convert a QUORUM handle into one of ours
lazy_static! {
static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
}
fn list_to_vec(list_entries: u32, list: *const u32) -> Vec<NodeId> {
let mut r_member_list = Vec::<NodeId>::new();
let temp_members: &[u32] = unsafe { slice::from_raw_parts(list, list_entries as usize) };
for i in 0..list_entries as usize {
r_member_list.push(NodeId::from(temp_members[i]));
}
r_member_list
}
// Called from quorum callback function - munge params back to Rust from C
extern "C" fn rust_quorum_notification_fn(
handle: ffi::quorum_handle_t,
quorate: u32,
ring_id: ffi::quorum_ring_id,
member_list_entries: u32,
member_list: *const u32,
) {
if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
let r_ring_id = RingId {
nodeid: NodeId::from(ring_id.nodeid),
seq: ring_id.seq,
};
let r_member_list = list_to_vec(member_list_entries, member_list);
let r_quorate = match quorate {
0 => false,
1 => true,
_ => false,
};
match &h.model_data {
ModelData::ModelV1(md) => {
if let Some(cb) = md.quorum_notification_fn {
(cb)(h, r_quorate, r_ring_id, r_member_list);
}
}
_ => {}
}
}
}
extern "C" fn rust_nodelist_notification_fn(
handle: ffi::quorum_handle_t,
ring_id: ffi::quorum_ring_id,
member_list_entries: u32,
member_list: *const u32,
joined_list_entries: u32,
joined_list: *const u32,
left_list_entries: u32,
left_list: *const u32,
) {
if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
let r_ring_id = RingId {
nodeid: NodeId::from(ring_id.nodeid),
seq: ring_id.seq,
};
let r_member_list = list_to_vec(member_list_entries, member_list);
let r_joined_list = list_to_vec(joined_list_entries, joined_list);
let r_left_list = list_to_vec(left_list_entries, left_list);
match &h.model_data {
ModelData::ModelV1(md) => {
if let Some(cb) = md.nodelist_notification_fn {
(cb)(h, r_ring_id, r_member_list, r_joined_list, r_left_list);
}
}
_ => {}
}
}
}
#[derive(Copy, Clone)]
/// Data for model1 [initialize]
pub struct Model1Data {
pub flags: Model1Flags,
pub quorum_notification_fn:
Option<fn(hande: &Handle, quorate: bool, ring_id: RingId, member_list: Vec<NodeId>)>,
pub nodelist_notification_fn: Option<
fn(
hande: &Handle,
ring_id: RingId,
member_list: Vec<NodeId>,
joined_list: Vec<NodeId>,
left_list: Vec<NodeId>,
),
>,
}
/// A handle into the quorum library. Returned from [initialize] and needed for all other calls
#[derive(Copy, Clone)]
pub struct Handle {
quorum_handle: u64,
model_data: ModelData,
}
/// Initialize a connection to the quorum library. You must call this before doing anything
/// else and use the passed back [Handle].
/// Remember to free the handle using [finalize] when finished.
pub fn initialize(model_data: &ModelData, context: u64) -> Result<(Handle, QuorumType)> {
let mut handle: ffi::quorum_handle_t = 0;
let mut quorum_type: u32 = 0;
let mut m = match model_data {
ModelData::ModelV1(_v1) => ffi::quorum_model_v1_data_t {
model: ffi::QUORUM_MODEL_V1,
quorum_notify_fn: Some(rust_quorum_notification_fn),
nodelist_notify_fn: Some(rust_nodelist_notification_fn),
},
// Only V1 supported. No point in doing legacy stuff in a new binding
_ => return Err(CsError::CsErrInvalidParam),
};
handle = unsafe {
let c_context: *mut c_void = &mut &context as *mut _ as *mut c_void;
let c_model: *mut ffi::quorum_model_data_t =
&mut m as *mut _ as *mut ffi::quorum_model_data_t;
let res = ffi::quorum_model_initialize(
&mut handle,
m.model,
c_model,
&mut quorum_type,
c_context,
);
if res == ffi::CS_OK {
handle
} else {
return Err(CsError::from_c(res));
}
};
let quorum_type = match quorum_type {
0 => QuorumType::Free,
1 => QuorumType::Set,
_ => QuorumType::Set,
};
let rhandle = Handle {
quorum_handle: handle,
model_data: *model_data,
};
HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
Ok((rhandle, quorum_type))
}
/// Finish with a connection to corosync
pub fn finalize(handle: Handle) -> Result<()> {
let res = unsafe { ffi::quorum_finalize(handle.quorum_handle) };
if res == ffi::CS_OK {
HANDLE_HASH.lock().unwrap().remove(&handle.quorum_handle);
Ok(())
} else {
Err(CsError::from_c(res))
}
}
// Not sure if an FD is the right thing to return here, but it will do for now.
/// Return a file descriptor to use for poll/select on the QUORUM handle
pub fn fd_get(handle: Handle) -> Result<i32> {
let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
let res = unsafe { ffi::quorum_fd_get(handle.quorum_handle, c_fd) };
if res == ffi::CS_OK {
Ok(c_fd as i32)
} else {
Err(CsError::from_c(res))
}
}
/// Display any/all active QUORUM callbacks for this [Handle], see [DispatchFlags] for details
pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
let res = unsafe { ffi::quorum_dispatch(handle.quorum_handle, flags as u32) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Return the quorate status of the cluster
pub fn getquorate(handle: Handle) -> Result<bool> {
let c_quorate: *mut c_int = &mut 0 as *mut _ as *mut c_int;
let (res, r_quorate) = unsafe {
let res = ffi::quorum_getquorate(handle.quorum_handle, c_quorate);
let r_quorate: i32 = *c_quorate;
(res, r_quorate)
};
if res == ffi::CS_OK {
match r_quorate {
0 => Ok(false),
1 => Ok(true),
_ => Err(CsError::CsErrLibrary),
}
} else {
Err(CsError::from_c(res))
}
}
/// Track node and quorum changes
pub fn trackstart(handle: Handle, flags: TrackFlags) -> Result<()> {
let res = unsafe { ffi::quorum_trackstart(handle.quorum_handle, flags as u32) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Stop tracking node and quorum changes
pub fn trackstop(handle: Handle) -> Result<()> {
let res = unsafe { ffi::quorum_trackstop(handle.quorum_handle) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Get the current 'context' value for this handle.
/// The context value is an arbitrary value that is always passed
/// back to callbacks to help identify the source
pub fn context_get(handle: Handle) -> Result<u64> {
let (res, context) = unsafe {
let mut context: u64 = 0;
let c_context: *mut c_void = &mut context as *mut _ as *mut c_void;
let r = ffi::quorum_context_get(handle.quorum_handle, c_context as *mut *const c_void);
(r, context)
};
if res == ffi::CS_OK {
Ok(context)
} else {
Err(CsError::from_c(res))
}
}
/// Set the current 'context' value for this handle.
/// The context value is an arbitrary value that is always passed
/// back to callbacks to help identify the source.
/// Normally this is set in [initialize], but this allows it to be changed
pub fn context_set(handle: Handle, context: u64) -> Result<()> {
let res = unsafe {
let c_context = context as *mut c_void;
ffi::quorum_context_set(handle.quorum_handle, c_context)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}

View File

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

View File

@ -0,0 +1,501 @@
// libvotequorum interface for Rust
// Copyright (c) 2021 Red Hat, Inc.
//
// All rights reserved.
//
// Author: Christine Caulfield (ccaulfi@redhat.com)
//
#![allow(clippy::type_complexity)]
#![allow(clippy::needless_range_loop)]
#![allow(clippy::single_match)]
// For the code generated by bindgen
use crate::sys::votequorum as ffi;
use std::collections::HashMap;
use std::ffi::CString;
use std::fmt;
use std::os::raw::{c_int, c_void};
use std::slice;
use std::sync::Mutex;
use crate::string_from_bytes;
use crate::{CsError, DispatchFlags, NodeId, Result, TrackFlags};
/// RingId returned by votequorum_notification_fn
pub struct RingId {
pub nodeid: NodeId,
pub seq: u64,
}
// Used to convert a VOTEQUORUM handle into one of ours
lazy_static! {
static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
}
/// Current state of a node in the cluster, part of the [NodeInfo] and [Node] structs
pub enum NodeState {
Member,
Dead,
Leaving,
Unknown,
}
impl NodeState {
pub fn new(state: u32) -> NodeState {
match state {
1 => NodeState::Member,
2 => NodeState::Dead,
3 => NodeState::Leaving,
_ => NodeState::Unknown,
}
}
}
impl fmt::Debug for NodeState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
NodeState::Member => write!(f, "Member"),
NodeState::Dead => write!(f, "Dead"),
NodeState::Leaving => write!(f, "Leaving"),
_ => write!(f, "Unknown"),
}
}
}
/// Basic information about a node in the cluster. Contains [NodeId], and [NodeState]
pub struct Node {
nodeid: NodeId,
state: NodeState,
}
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "nodeid: {}, state: {:?}", self.nodeid, self.state)
}
}
bitflags! {
/// Flags in the [NodeInfo] struct
pub struct NodeInfoFlags: u32
{
const VOTEQUORUM_INFO_TWONODE = 1;
const VOTEQUORUM_INFO_QUORATE = 2;
const VOTEQUORUM_INFO_WAIT_FOR_ALL = 4;
const VOTEQUORUM_INFO_LAST_MAN_STANDING = 8;
const VOTEQUORUM_INFO_AUTO_TIE_BREAKER = 16;
const VOTEQUORUM_INFO_ALLOW_DOWNSCALE = 32;
const VOTEQUORUM_INFO_QDEVICE_REGISTERED = 64;
const VOTEQUORUM_INFO_QDEVICE_ALIVE = 128;
const VOTEQUORUM_INFO_QDEVICE_CAST_VOTE = 256;
const VOTEQUORUM_INFO_QDEVICE_MASTER_WINS = 512;
}
}
/// Detailed information about a node in the cluster, returned from [get_info]
pub struct NodeInfo {
pub node_id: NodeId,
pub node_state: NodeState,
pub node_votes: u32,
pub node_expected_votes: u32,
pub highest_expected: u32,
pub quorum: u32,
pub flags: NodeInfoFlags,
pub qdevice_votes: u32,
pub qdevice_name: String,
}
// Turn a C nodeID list into a vec of NodeIds
fn list_to_vec(list_entries: u32, list: *const u32) -> Vec<NodeId> {
let mut r_member_list = Vec::<NodeId>::new();
let temp_members: &[u32] = unsafe { slice::from_raw_parts(list, list_entries as usize) };
for i in 0..list_entries as usize {
r_member_list.push(NodeId::from(temp_members[i]));
}
r_member_list
}
// Called from votequorum callback function - munge params back to Rust from C
extern "C" fn rust_expectedvotes_notification_fn(
handle: ffi::votequorum_handle_t,
context: u64,
expected_votes: u32,
) {
if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
if let Some(cb) = h.callbacks.expectedvotes_notification_fn {
(cb)(h, context, expected_votes);
}
}
}
// Called from votequorum callback function - munge params back to Rust from C
extern "C" fn rust_quorum_notification_fn(
handle: ffi::votequorum_handle_t,
context: u64,
quorate: u32,
node_list_entries: u32,
node_list: *mut ffi::votequorum_node_t,
) {
if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
let r_quorate = match quorate {
0 => false,
1 => true,
_ => false,
};
let mut r_node_list = Vec::<Node>::new();
let temp_members: &[ffi::votequorum_node_t] =
unsafe { slice::from_raw_parts(node_list, node_list_entries as usize) };
for i in 0..node_list_entries as usize {
r_node_list.push(Node {
nodeid: NodeId::from(temp_members[i].nodeid),
state: NodeState::new(temp_members[i].state),
});
}
if let Some(cb) = h.callbacks.quorum_notification_fn {
(cb)(h, context, r_quorate, r_node_list);
}
}
}
// Called from votequorum callback function - munge params back to Rust from C
extern "C" fn rust_nodelist_notification_fn(
handle: ffi::votequorum_handle_t,
context: u64,
ring_id: ffi::votequorum_ring_id_t,
node_list_entries: u32,
node_list: *mut u32,
) {
if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
let r_ring_id = RingId {
nodeid: NodeId::from(ring_id.nodeid),
seq: ring_id.seq,
};
let r_node_list = list_to_vec(node_list_entries, node_list);
if let Some(cb) = h.callbacks.nodelist_notification_fn {
(cb)(h, context, r_ring_id, r_node_list);
}
}
}
/// Callbacks that can be called from votequorum, pass these in to [initialize]
#[derive(Copy, Clone)]
pub struct Callbacks {
pub quorum_notification_fn:
Option<fn(hande: &Handle, context: u64, quorate: bool, node_list: Vec<Node>)>,
pub nodelist_notification_fn:
Option<fn(hande: &Handle, context: u64, ring_id: RingId, node_list: Vec<NodeId>)>,
pub expectedvotes_notification_fn:
Option<fn(handle: &Handle, context: u64, expected_votes: u32)>,
}
/// A handle into the votequorum library. Returned from [initialize] and needed for all other calls
#[derive(Copy, Clone)]
pub struct Handle {
votequorum_handle: u64,
callbacks: Callbacks,
}
/// Initialize a connection to the votequorum library. You must call this before doing anything
/// else and use the passed back [Handle].
/// Remember to free the handle using [finalize] when finished.
pub fn initialize(callbacks: &Callbacks) -> Result<Handle> {
let mut handle: ffi::votequorum_handle_t = 0;
let mut c_callbacks = ffi::votequorum_callbacks_t {
votequorum_quorum_notify_fn: Some(rust_quorum_notification_fn),
votequorum_nodelist_notify_fn: Some(rust_nodelist_notification_fn),
votequorum_expectedvotes_notify_fn: Some(rust_expectedvotes_notification_fn),
};
unsafe {
let res = ffi::votequorum_initialize(&mut handle, &mut c_callbacks);
if res == ffi::CS_OK {
let rhandle = Handle {
votequorum_handle: handle,
callbacks: *callbacks,
};
HANDLE_HASH.lock().unwrap().insert(handle, rhandle);
Ok(rhandle)
} else {
Err(CsError::from_c(res))
}
}
}
/// Finish with a connection to corosync
pub fn finalize(handle: Handle) -> Result<()> {
let res = unsafe { ffi::votequorum_finalize(handle.votequorum_handle) };
if res == ffi::CS_OK {
HANDLE_HASH
.lock()
.unwrap()
.remove(&handle.votequorum_handle);
Ok(())
} else {
Err(CsError::from_c(res))
}
}
// Not sure if an FD is the right thing to return here, but it will do for now.
/// Return a file descriptor to use for poll/select on the VOTEQUORUM handle
pub fn fd_get(handle: Handle) -> Result<i32> {
let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
let res = unsafe { ffi::votequorum_fd_get(handle.votequorum_handle, c_fd) };
if res == ffi::CS_OK {
Ok(c_fd as i32)
} else {
Err(CsError::from_c(res))
}
}
const VOTEQUORUM_QDEVICE_MAX_NAME_LEN: usize = 255;
/// Returns detailed information about a node in a [NodeInfo] structure
pub fn get_info(handle: Handle, nodeid: NodeId) -> Result<NodeInfo> {
let mut c_info = ffi::votequorum_info {
node_id: 0,
node_state: 0,
node_votes: 0,
node_expected_votes: 0,
highest_expected: 0,
total_votes: 0,
quorum: 0,
flags: 0,
qdevice_votes: 0,
qdevice_name: [0; 255usize],
};
let res = unsafe {
ffi::votequorum_getinfo(handle.votequorum_handle, u32::from(nodeid), &mut c_info)
};
if res == ffi::CS_OK {
let info = NodeInfo {
node_id: NodeId::from(c_info.node_id),
node_state: NodeState::new(c_info.node_state),
node_votes: c_info.node_votes,
node_expected_votes: c_info.node_expected_votes,
highest_expected: c_info.highest_expected,
quorum: c_info.quorum,
flags: NodeInfoFlags { bits: c_info.flags },
qdevice_votes: c_info.qdevice_votes,
qdevice_name: match string_from_bytes(
c_info.qdevice_name.as_ptr(),
VOTEQUORUM_QDEVICE_MAX_NAME_LEN,
) {
Ok(s) => s,
Err(_) => String::new(),
},
};
Ok(info)
} else {
Err(CsError::from_c(res))
}
}
/// Call any/all active votequorum callbacks for this [Handle]. see [DispatchFlags] for details
pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> {
let res = unsafe { ffi::votequorum_dispatch(handle.votequorum_handle, flags as u32) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Track node and votequorum changes
pub fn trackstart(handle: Handle, context: u64, flags: TrackFlags) -> Result<()> {
let res =
unsafe { ffi::votequorum_trackstart(handle.votequorum_handle, context, flags as u32) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Stop tracking node and votequorum changes
pub fn trackstop(handle: Handle) -> Result<()> {
let res = unsafe { ffi::votequorum_trackstop(handle.votequorum_handle) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Get the current 'context' value for this handle.
/// The context value is an arbitrary value that is always passed
/// back to callbacks to help identify the source
pub fn context_get(handle: Handle) -> Result<u64> {
let (res, context) = unsafe {
let mut c_context: *mut c_void = &mut 0u64 as *mut _ as *mut c_void;
let r = ffi::votequorum_context_get(handle.votequorum_handle, &mut c_context);
let context: u64 = c_context as u64;
(r, context)
};
if res == ffi::CS_OK {
Ok(context)
} else {
Err(CsError::from_c(res))
}
}
/// Set the current 'context' value for this handle.
/// The context value is an arbitrary value that is always passed
/// back to callbacks to help identify the source.
/// Normally this is set in [trackstart], but this allows it to be changed
pub fn context_set(handle: Handle, context: u64) -> Result<()> {
let res = unsafe {
let c_context = context as *mut c_void;
ffi::votequorum_context_set(handle.votequorum_handle, c_context)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Set the current expected_votes for the cluster, this value must
/// be valid and not result in an inquorate cluster.
pub fn set_expected(handle: Handle, expected_votes: u32) -> Result<()> {
let res = unsafe { ffi::votequorum_setexpected(handle.votequorum_handle, expected_votes) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Set the current votes for a node
pub fn set_votes(handle: Handle, nodeid: NodeId, votes: u32) -> Result<()> {
let res =
unsafe { ffi::votequorum_setvotes(handle.votequorum_handle, u32::from(nodeid), votes) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Register a quorum device
pub fn qdevice_register(handle: Handle, name: &str) -> Result<()> {
let c_string = {
match CString::new(name) {
Ok(cs) => cs,
Err(_) => return Err(CsError::CsErrInvalidParam),
}
};
let res =
unsafe { ffi::votequorum_qdevice_register(handle.votequorum_handle, c_string.as_ptr()) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Unregister a quorum device
pub fn qdevice_unregister(handle: Handle, name: &str) -> Result<()> {
let c_string = {
match CString::new(name) {
Ok(cs) => cs,
Err(_) => return Err(CsError::CsErrInvalidParam),
}
};
let res =
unsafe { ffi::votequorum_qdevice_unregister(handle.votequorum_handle, c_string.as_ptr()) };
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Update the name of a quorum device
pub fn qdevice_update(handle: Handle, oldname: &str, newname: &str) -> Result<()> {
let on_string = {
match CString::new(oldname) {
Ok(cs) => cs,
Err(_) => return Err(CsError::CsErrInvalidParam),
}
};
let nn_string = {
match CString::new(newname) {
Ok(cs) => cs,
Err(_) => return Err(CsError::CsErrInvalidParam),
}
};
let res = unsafe {
ffi::votequorum_qdevice_update(
handle.votequorum_handle,
on_string.as_ptr(),
nn_string.as_ptr(),
)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Poll a quorum device
/// This must be done more often than the qdevice timeout (default 10s) while the device is active
/// and the [RingId] must match the current value returned from the callbacks for it to be accepted.
pub fn qdevice_poll(handle: Handle, name: &str, cast_vote: bool, ring_id: &RingId) -> Result<()> {
let c_string = {
match CString::new(name) {
Ok(cs) => cs,
Err(_) => return Err(CsError::CsErrInvalidParam),
}
};
let c_cast_vote: u32 = u32::from(cast_vote);
let c_ring_id = ffi::votequorum_ring_id_t {
nodeid: u32::from(ring_id.nodeid),
seq: ring_id.seq,
};
let res = unsafe {
ffi::votequorum_qdevice_poll(
handle.votequorum_handle,
c_string.as_ptr(),
c_cast_vote,
c_ring_id,
)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}
/// Allow qdevice to tell votequorum if master_wins can be enabled or not
pub fn qdevice_master_wins(handle: Handle, name: &str, master_wins: bool) -> Result<()> {
let c_string = {
match CString::new(name) {
Ok(cs) => cs,
Err(_) => return Err(CsError::CsErrInvalidParam),
}
};
let c_master_wins: u32 = u32::from(master_wins);
let res = unsafe {
ffi::votequorum_qdevice_master_wins(
handle.votequorum_handle,
c_string.as_ptr(),
c_master_wins,
)
};
if res == ffi::CS_OK {
Ok(())
} else {
Err(CsError::from_c(res))
}
}

View File

@ -0,0 +1,36 @@
[package]
name = "rust-corosync-tests"
version = "@corosyncrustver@"
authors = ["Christine Caulfield <ccaulfie@redhat.com>"]
edition = "2021"
[dependencies]
rust-corosync = { path = ".." }
[build-dependencies]
pkg-config = "0.3"
[[bin]]
name = "cpg-test"
test = false
bench = false
[[bin]]
name = "quorum-test"
test = false
bench = false
[[bin]]
name = "votequorum-test"
test = false
bench = false
[[bin]]
name = "cfg-test"
test = false
bench = false
[[bin]]
name = "cmap-test"
test = false
bench = false

View File

@ -0,0 +1,32 @@
#
# Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved.
#
# Author: Christine Caulfield <ccaulfie@redhat.com>
#
# This software licensed under GPL-2.0+
#
MAINTAINERCLEANFILES = Makefile.in
include $(top_srcdir)/build-aux/rust.mk
EXTRA_DIST = \
$(RUST_COMMON) \
$(RUST_SHIP_SRCS)
RUST_SHIP_SRCS = src/bin/cpg-test.rs \
src/bin/cfg-test.rs \
src/bin/cmap-test.rs \
src/bin/quorum-test.rs \
src/bin/votequorum-test.rs
# This will build all of the tests
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

View File

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

View File

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

View File

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

View File

@ -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<cpg::Address>,
left_list: Vec<cpg::Address>,
joined_list: Vec<cpg::Address>,
) {
println!("TEST confchg_fn called for {}", group_name);
println!(" members: {:?}", member_list);
println!(" left: {:?}", left_list);
println!(" joined: {:?}", joined_list);
}
fn totem_confchg_fn(_handle: &cpg::Handle, ring_id: cpg::RingId, member_list: Vec<NodeId>) {
println!(
"TEST totem_confchg_fn called for {}/{}",
ring_id.nodeid, ring_id.seq
);
println!(" members: {:?}", member_list);
}
fn main() {
// Initialise the model data
let md = cpg::ModelData::ModelV1(cpg::Model1Data {
flags: cpg::Model1Flags::None,
deliver_fn: Some(deliver_fn),
confchg_fn: Some(confchg_fn),
totem_confchg_fn: Some(totem_confchg_fn),
});
let handle = match cpg::initialize(&md, 99_u64) {
Ok(h) => h,
Err(e) => {
println!("Error in CPG init: {}", e);
return;
}
};
if let Err(e) = cpg::join(handle, "TEST") {
println!("Error in CPG join: {}", e);
return;
}
match cpg::local_get(handle) {
Ok(n) => {
println!("Local nodeid is {}", n);
}
Err(e) => {
println!("Error in CPG local_get: {}", e);
}
}
// Test membership_get()
match cpg::membership_get(handle, "TEST") {
Ok(m) => {
println!(" members: {:?}", m);
println!();
}
Err(e) => {
println!("Error in CPG membership_get: {}", e);
}
}
// Test context APIs
let set_context: u64 = 0xabcdbeefcafe;
if let Err(e) = cpg::context_set(handle, set_context) {
println!("Error in CPG context_set: {}", e);
return;
}
// NOTE This will fail on 32 bit systems because void* is not u64
match cpg::context_get(handle) {
Ok(c) => {
if c != set_context {
println!(
"Error: context_get() returned {: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");
}

View File

@ -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<NodeId>,
) {
println!("TEST quorum_fn called. quorate = {}", quorate);
println!(" ring_id: {}/{}", ring_id.nodeid, ring_id.seq);
println!(" members: {:?}", member_list);
}
fn nodelist_fn(
_handle: &quorum::Handle,
ring_id: quorum::RingId,
member_list: Vec<NodeId>,
joined_list: Vec<NodeId>,
left_list: Vec<NodeId>,
) {
println!(
"TEST nodelist_fn called for {}/{}",
ring_id.nodeid, ring_id.seq
);
println!(" members: {:?}", member_list);
println!(" joined: {:?}", joined_list);
println!(" left: {:?}", left_list);
}
fn main() {
// Initialise the model data
let md = quorum::ModelData::ModelV1(quorum::Model1Data {
flags: quorum::Model1Flags::None,
quorum_notification_fn: Some(quorum_fn),
nodelist_notification_fn: Some(nodelist_fn),
});
let handle = match quorum::initialize(&md, 99_u64) {
Ok((h, t)) => {
println!("Quorum initialized; type = {}", t as u32);
h
}
Err(e) => {
println!("Error in QUORUM init: {}", e);
return;
}
};
// Test context APIs
let set_context: u64 = 0xabcdbeefcafe;
if let Err(e) = quorum::context_set(handle, set_context) {
println!("Error in QUORUM context_set: {}", e);
return;
}
// NOTE This will fail on 32 bit systems because void* is not u64
match quorum::context_get(handle) {
Ok(c) => {
if c != set_context {
println!(
"Error: context_get() returned {: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");
}

View File

@ -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<votequorum::Node>,
) {
println!("TEST votequorum_quorum_fn called. quorate = {}", quorate);
println!(" members: {:?}", member_list);
}
fn nodelist_fn(
_handle: &votequorum::Handle,
_context: u64,
ring_id: votequorum::RingId,
member_list: Vec<corosync::NodeId>,
) {
println!(
"TEST nodelist_fn called for {}/{}",
ring_id.nodeid, ring_id.seq
);
println!(" members: {:?}", member_list);
}
fn expectedvotes_fn(_handle: &votequorum::Handle, _context: u64, expected_votes: u32) {
println!("TEST expected_votes_fn called: value is {}", expected_votes);
}
fn main() {
// Initialise the model data
let cb = votequorum::Callbacks {
quorum_notification_fn: Some(quorum_fn),
nodelist_notification_fn: Some(nodelist_fn),
expectedvotes_notification_fn: Some(expectedvotes_fn),
};
let handle = match votequorum::initialize(&cb) {
Ok(h) => {
println!("Votequorum initialized.");
h
}
Err(e) => {
println!("Error in VOTEQUORUM init: {}", e);
return;
}
};
// Test context APIs
let set_context: u64 = 0xabcdbeefcafe;
if let Err(e) = votequorum::context_set(handle, set_context) {
println!("Error in VOTEQUORUM context_set: {}", e);
}
// NOTE This will fail on 32 bit systems because void* is not u64
match votequorum::context_get(handle) {
Ok(c) => {
if c != set_context {
println!(
"Error: context_get() returned {: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");
}

30
build-aux/rust-regen.sh Executable file
View File

@ -0,0 +1,30 @@
#!/bin/sh
#
# Copyright (C) 2021-2023 Red Hat, Inc. All rights reserved.
#
# Authors: Christine Caulfield <ccaulfie@redhat.com>
# Fabio M. Di Nitto <fabbione@kronosnet.org>
#
# 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 "$@"

113
build-aux/rust.mk Normal file
View File

@ -0,0 +1,113 @@
#
# Copyright (C) 2021-2022 Red Hat, Inc. All rights reserved.
#
# Author: Fabio M. Di Nitto <fabbione@kronosnet.org>
#
# This software licensed under GPL-2.0+
#
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

View File

@ -42,6 +42,13 @@ else
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
@ -229,10 +236,48 @@ AC_CONFIG_FILES([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 ===============================================
@ -423,8 +468,12 @@ 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
@ -733,6 +782,8 @@ 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")
@ -771,6 +822,7 @@ 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}])