diff --git a/bindings/rust/src/cfg.rs b/bindings/rust/src/cfg.rs index b4eecacc..5e1a0442 100644 --- a/bindings/rust/src/cfg.rs +++ b/bindings/rust/src/cfg.rs @@ -1,348 +1,374 @@ // libcfg interface for Rust // Copyright (c) 2021 Red Hat, Inc. // // All rights reserved. // // Author: Christine Caulfield (ccaulfi@redhat.com) // // For the code generated by bindgen use crate::sys::cfg as ffi; use std::collections::HashMap; use std::ffi::CString; use std::os::raw::{c_int, c_void}; use std::sync::Mutex; use crate::string_from_bytes; use crate::{CsError, DispatchFlags, NodeId, Result}; // Used to convert a CFG handle into one of ours lazy_static! { static ref HANDLE_HASH: Mutex> = Mutex::new(HashMap::new()); } /// Callback from [track_start]. Will be called if another process /// requests to shut down corosync. [reply_to_shutdown] should be called /// with a [ShutdownReply] of either Yes or No. #[derive(Copy, Clone)] pub struct Callbacks { pub corosync_cfg_shutdown_callback_fn: Option, } /// A handle into the cfg library. returned from [initialize] and needed for all other calls -#[derive(Copy, Clone)] pub struct Handle { cfg_handle: u64, callbacks: Callbacks, + clone: bool, +} + +impl Clone for Handle { + fn clone(&self) -> Handle { + Handle { + cfg_handle: self.cfg_handle, + callbacks: self.callbacks, + clone: true, + } + } +} + +impl Drop for Handle { + fn drop(self: &mut Handle) { + if !self.clone { + let _e = finalize(self); + } + } +} + +// Clones count as equivalent +impl PartialEq for Handle { + fn eq(&self, other: &Handle) -> bool { + self.cfg_handle == other.cfg_handle + } } /// Flags for [try_shutdown] pub enum ShutdownFlags { /// Request shutdown (other daemons will be consulted) Request, /// Tells other daemons but ignore their opinions Regardless, /// Go down straight away (but still tell other nodes) Immediate, } /// Responses for [reply_to_shutdown] pub enum ShutdownReply { Yes = 1, No = 0, } /// Trackflags for [track_start]. None currently supported pub enum TrackFlags { None, } /// Version of the [NodeStatus] structure returned from [node_status_get] #[derive(Debug, Copy, Clone)] pub enum NodeStatusVersion { V1, } /// Status of a link inside [NodeStatus] struct #[derive(Debug)] pub struct LinkStatus { pub enabled: bool, pub connected: bool, pub dynconnected: bool, pub mtu: u32, pub src_ipaddr: String, pub dst_ipaddr: String, } /// Structure returned from [node_status_get], shows all the details of a node /// that is known to corosync, including all configured links #[derive(Debug)] pub struct NodeStatus { pub version: NodeStatusVersion, pub nodeid: NodeId, pub reachable: bool, pub remote: bool, pub external: bool, pub onwire_min: u8, pub onwire_max: u8, pub onwire_ver: u8, pub link_status: Vec, } extern "C" fn rust_shutdown_notification_fn(handle: ffi::corosync_cfg_handle_t, flags: u32) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) { if let Some(cb) = h.callbacks.corosync_cfg_shutdown_callback_fn { (cb)(h, flags); } } } /// Initialize a connection to the cfg library. You must call this before doing anything /// else and use the passed back [Handle]. /// Remember to free the handle using [finalize] when finished. pub fn initialize(callbacks: &Callbacks) -> Result { let mut handle: ffi::corosync_cfg_handle_t = 0; let c_callbacks = ffi::corosync_cfg_callbacks_t { corosync_cfg_shutdown_callback: Some(rust_shutdown_notification_fn), }; unsafe { let res = ffi::corosync_cfg_initialize(&mut handle, &c_callbacks); if res == ffi::CS_OK { let rhandle = Handle { cfg_handle: handle, callbacks: *callbacks, + clone: false, }; - HANDLE_HASH.lock().unwrap().insert(handle, rhandle); + HANDLE_HASH.lock().unwrap().insert(handle, rhandle.clone()); 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<()> { +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 { +pub fn fd_get(handle: &Handle) -> Result { let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int; let res = unsafe { ffi::corosync_cfg_fd_get(handle.cfg_handle, c_fd) }; if res == ffi::CS_OK { Ok(c_fd as i32) } else { Err(CsError::from_c(res)) } } /// Get the local [NodeId] -pub fn local_get(handle: Handle) -> Result { +pub fn local_get(handle: &Handle) -> Result { let mut nodeid: u32 = 0; let res = unsafe { ffi::corosync_cfg_local_get(handle.cfg_handle, &mut nodeid) }; if res == ffi::CS_OK { Ok(NodeId::from(nodeid)) } else { Err(CsError::from_c(res)) } } /// Reload the cluster configuration on all nodes -pub fn reload_cnfig(handle: Handle) -> Result<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +pub fn dispatch(handle: &Handle, flags: DispatchFlags) -> Result<()> { let res = unsafe { ffi::corosync_cfg_dispatch(handle.cfg_handle, flags as u32) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } // Quick & dirty u8 to boolean fn u8_to_bool(val: u8) -> bool { val != 0 } const CFG_MAX_LINKS: usize = 8; const CFG_MAX_HOST_LEN: usize = 256; fn unpack_nodestatus(c_nodestatus: ffi::corosync_cfg_node_status_v1) -> Result { let mut ns = NodeStatus { version: NodeStatusVersion::V1, nodeid: NodeId::from(c_nodestatus.nodeid), reachable: u8_to_bool(c_nodestatus.reachable), remote: u8_to_bool(c_nodestatus.remote), external: u8_to_bool(c_nodestatus.external), onwire_min: c_nodestatus.onwire_min, onwire_max: c_nodestatus.onwire_max, onwire_ver: c_nodestatus.onwire_min, link_status: Vec::::new(), }; for i in 0..CFG_MAX_LINKS { let ls = LinkStatus { enabled: u8_to_bool(c_nodestatus.link_status[i].enabled), connected: u8_to_bool(c_nodestatus.link_status[i].connected), dynconnected: u8_to_bool(c_nodestatus.link_status[i].dynconnected), mtu: c_nodestatus.link_status[i].mtu, src_ipaddr: string_from_bytes( &c_nodestatus.link_status[i].src_ipaddr[0], CFG_MAX_HOST_LEN, )?, dst_ipaddr: string_from_bytes( &c_nodestatus.link_status[i].dst_ipaddr[0], CFG_MAX_HOST_LEN, )?, }; ns.link_status.push(ls); } Ok(ns) } // Constructor for link status to make c_ndostatus initialization tidier. fn new_ls() -> ffi::corosync_knet_link_status_v1 { ffi::corosync_knet_link_status_v1 { enabled: 0, connected: 0, dynconnected: 0, mtu: 0, src_ipaddr: [0; 256], dst_ipaddr: [0; 256], } } /// Get the extended status of a node in the cluster (including active links) from its [NodeId]. /// Returns a filled in [NodeStatus] struct pub fn node_status_get( - handle: Handle, + handle: &Handle, nodeid: NodeId, _version: NodeStatusVersion, ) -> Result { // Currently only supports V1 struct unsafe { // We need to initialize this even though it's all going to be overwritten. let mut c_nodestatus = ffi::corosync_cfg_node_status_v1 { version: 1, nodeid: 0, reachable: 0, remote: 0, external: 0, onwire_min: 0, onwire_max: 0, onwire_ver: 0, link_status: [new_ls(); 8], }; let res = ffi::corosync_cfg_node_status_get( handle.cfg_handle, u32::from(nodeid), 1, &mut c_nodestatus as *mut _ as *mut c_void, ); if res == ffi::CS_OK { unpack_nodestatus(c_nodestatus) } else { Err(CsError::from_c(res)) } } } /// Start tracking for shutdown notifications -pub fn track_start(handle: Handle, _flags: TrackFlags) -> Result<()> { +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<()> { +pub fn track_stop(handle: &Handle) -> Result<()> { let res = unsafe { ffi::corosync_cfg_trackstop(handle.cfg_handle) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } diff --git a/bindings/rust/src/cmap.rs b/bindings/rust/src/cmap.rs index 454fbee2..4f3651aa 100644 --- a/bindings/rust/src/cmap.rs +++ b/bindings/rust/src/cmap.rs @@ -1,894 +1,918 @@ // 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, + clone: bool, +} + +impl Clone for Handle { + fn clone(&self) -> Handle { + Handle { + cmap_handle: self.cmap_handle, + clone: true, + } + } +} + +impl Drop for Handle { + fn drop(self: &mut Handle) { + if !self.clone { + let _e = finalize(self); + } + } +} +// Clones count as equivalent +impl PartialEq for Handle { + fn eq(&self, other: &Handle) -> bool { + self.cmap_handle == other.cmap_handle + } } #[derive(Copy, Clone)] /// A handle for a specific CMAP tracker. returned from [track_add]. /// There may be multiple TrackHandles per [Handle] pub struct TrackHandle { track_handle: u64, notify_callback: NotifyCallback, } // Used to convert CMAP handles into one of ours, for callbacks lazy_static! { static ref TRACKHANDLE_HASH: Mutex> = Mutex::new(HashMap::new()); static ref HANDLE_HASH: Mutex> = Mutex::new(HashMap::new()); } /// Initialize a connection to the cmap subsystem. /// map specifies which cmap "map" to use. /// Returns a [Handle] into the cmap library pub fn initialize(map: Map) -> Result { let mut handle: ffi::cmap_handle_t = 0; let c_map = match map { Map::Icmap => ffi::CMAP_MAP_ICMAP, Map::Stats => ffi::CMAP_MAP_STATS, }; unsafe { let res = ffi::cmap_initialize_map(&mut handle, c_map); if res == ffi::CS_OK { let rhandle = Handle { cmap_handle: handle, + clone: false, }; - HANDLE_HASH.lock().unwrap().insert(handle, rhandle); + HANDLE_HASH.lock().unwrap().insert(handle, rhandle.clone()); 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<()> { +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 { +pub fn fd_get(handle: &Handle) -> Result { let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int; let res = unsafe { ffi::cmap_fd_get(handle.cmap_handle, c_fd) }; if res == ffi::CS_OK { Ok(c_fd as i32) } else { Err(CsError::from_c(res)) } } /// Dispatch any/all active CMAP callbacks. /// Takes a [Handle] as returned from [initialize], /// flags [DispatchFlags] tells it how many items to dispatch before returning -pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> { +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 { +pub fn context_get(handle: &Handle) -> Result { let (res, context) = unsafe { let mut context: u64 = 0; let c_context: *mut c_void = &mut context as *mut _ as *mut c_void; let r = ffi::cmap_context_get(handle.cmap_handle, c_context as *mut *const c_void); (r, context) }; if res == ffi::CS_OK { Ok(context) } else { Err(CsError::from_c(res)) } } /// Set the current 'context' value for this handle /// The context value is an arbitrary value that is always passed /// back to callbacks to help identify the source. /// Normally this is set in [initialize], but this allows it to be changed -pub fn context_set(handle: Handle, context: u64) -> Result<()> { +pub fn context_set(handle: &Handle, context: u64) -> Result<()> { let res = unsafe { let c_context = context as *mut c_void; ffi::cmap_context_set(handle.cmap_handle, c_context) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } /// The type of data returned from [get] or in a /// tracker callback or iterator, part of the [Data] struct #[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] #[repr(u32)] pub enum DataType { Int8 = ffi::CMAP_VALUETYPE_INT8, UInt8 = ffi::CMAP_VALUETYPE_UINT8, Int16 = ffi::CMAP_VALUETYPE_INT16, UInt16 = ffi::CMAP_VALUETYPE_UINT16, Int32 = ffi::CMAP_VALUETYPE_INT32, UInt32 = ffi::CMAP_VALUETYPE_UINT32, Int64 = ffi::CMAP_VALUETYPE_INT64, UInt64 = ffi::CMAP_VALUETYPE_UINT64, Float = ffi::CMAP_VALUETYPE_FLOAT, Double = ffi::CMAP_VALUETYPE_DOUBLE, String = ffi::CMAP_VALUETYPE_STRING, Binary = ffi::CMAP_VALUETYPE_BINARY, Unknown = 999, } fn cmap_to_enum(cmap_type: u32) -> DataType { match DataType::try_from(cmap_type) { Ok(e) => e, Err(_) => DataType::Unknown, } } /// Data returned from the cmap::get() call and tracker & iterators. /// Contains the data itself and the type of that data. pub enum Data { Int8(i8), UInt8(u8), Int16(i16), UInt16(u16), Int32(i32), UInt32(u32), Int64(i64), UInt64(u64), Float(f32), Double(f64), String(String), Binary(Vec), Unknown, } impl fmt::Display for DataType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { DataType::Int8 => write!(f, "Int8"), DataType::UInt8 => write!(f, "UInt8"), DataType::Int16 => write!(f, "Int16"), DataType::UInt16 => write!(f, "UInt16"), DataType::Int32 => write!(f, "Int32"), DataType::UInt32 => write!(f, "UInt32"), DataType::Int64 => write!(f, "Int64"), DataType::UInt64 => write!(f, "UInt64"), DataType::Float => write!(f, "Float"), DataType::Double => write!(f, "Double"), DataType::String => write!(f, "String"), DataType::Binary => write!(f, "Binary"), DataType::Unknown => write!(f, "Unknown"), } } } impl fmt::Display for Data { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Data::Int8(v) => write!(f, "{v} (Int8)"), Data::UInt8(v) => write!(f, "{v} (UInt8)"), Data::Int16(v) => write!(f, "{v} (Int16)"), Data::UInt16(v) => write!(f, "{v} (UInt16)"), Data::Int32(v) => write!(f, "{v} (Int32)"), Data::UInt32(v) => write!(f, "{v} (UInt32)"), Data::Int64(v) => write!(f, "{v} (Int64)"), Data::UInt64(v) => write!(f, "{v} (UInt64)"), Data::Float(v) => write!(f, "{v} (Float)"), Data::Double(v) => write!(f, "{v} (Double)"), Data::String(v) => write!(f, "{v} (String)"), Data::Binary(v) => write!(f, "{v:?} (Binary)"), Data::Unknown => write!(f, "Unknown)"), } } } const CMAP_KEYNAME_MAXLENGTH: usize = 255; fn string_to_cstring_validated(key: &str, maxlen: usize) -> Result { 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, + handle: &Handle, key_name: &str, datatype: DataType, value: *mut c_void, length: usize, ) -> Result<()> { let csname = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?; let res = unsafe { ffi::cmap_set( handle.cmap_handle, csname.as_ptr(), value, length, datatype as u32, ) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } // Returns type and size fn generic_to_cmap(_value: T) -> (DataType, usize) { match type_name::() { "u8" => (DataType::UInt8, 1), "i8" => (DataType::Int8, 1), "u16" => (DataType::UInt16, 2), "i16" => (DataType::Int16, 2), "u32" => (DataType::UInt32, 4), "i32" => (DataType::Int32, 4), "u64" => (DataType::UInt64, 4), "f32" => (DataType::Float, 4), "f64" => (DataType::Double, 8), "&str" => (DataType::String, 0), // Binary not currently supported here _ => (DataType::Unknown, 0), } } fn is_numeric_type(dtype: DataType) -> bool { matches!( dtype, DataType::UInt8 | DataType::Int8 | DataType::UInt16 | DataType::Int16 | DataType::UInt32 | DataType::Int32 | DataType::UInt64 | DataType::Int64 | DataType::Float | DataType::Double ) } /// Function to set a generic numeric value /// This doesn't work for strings or binaries -pub fn set_number(handle: Handle, key_name: &str, value: T) -> Result<()> { +pub fn set_number(handle: &Handle, key_name: &str, value: T) -> Result<()> { let (c_type, c_size) = generic_to_cmap(value); if is_numeric_type(c_type) { let mut tmp = value; let c_value: *mut c_void = &mut tmp as *mut _ as *mut c_void; set_value(handle, key_name, c_type, c_value as *mut c_void, c_size) } else { Err(CsError::CsErrNotSupported) } } -pub fn set_u8(handle: Handle, key_name: &str, value: u8) -> Result<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +pub fn set(handle: &Handle, key_name: &str, data: &Data) -> Result<()> { let (datatype, datalen, c_value) = match data { Data::Int8(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::Int8, 1, cv) } Data::UInt8(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::UInt8, 1, cv) } Data::Int16(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::Int16, 2, cv) } Data::UInt16(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::UInt8, 2, cv) } Data::Int32(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::Int32, 4, cv) } Data::UInt32(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::UInt32, 4, cv) } Data::Int64(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::Int64, 8, cv) } Data::UInt64(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::UInt64, 8, cv) } Data::Float(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::Float, 4, cv) } Data::Double(v) => { let mut tmp = *v; let cv: *mut c_void = &mut tmp as *mut _ as *mut c_void; (DataType::Double, 8, cv) } Data::String(v) => { let cv = string_to_cstring_validated(v, 0)?; // Can't let cv go out of scope return set_value( handle, key_name, DataType::String, cv.as_ptr() as *mut c_void, v.chars().count(), ); } Data::Binary(v) => { // Vec doesn't return quite the right types. return set_value( handle, key_name, DataType::Binary, v.as_ptr() as *mut c_void, v.len(), ); } Data::Unknown => return Err(CsError::CsErrInvalidParam), }; set_value(handle, key_name, datatype, c_value, datalen) } // Local function to parse out values from the C mess // Assumes the c_value is complete. So cmap::get() will need to check the size // and re-get before calling us with a resized buffer fn c_to_data(value_size: usize, c_key_type: u32, c_value: *const u8) -> Result { unsafe { match cmap_to_enum(c_key_type) { DataType::UInt8 => { let mut ints = [0u8; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr(), value_size); Ok(Data::UInt8(ints[0])) } DataType::Int8 => { let mut ints = [0i8; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size); Ok(Data::Int8(ints[0])) } DataType::UInt16 => { let mut ints = [0u16; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size); Ok(Data::UInt16(ints[0])) } DataType::Int16 => { let mut ints = [0i16; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size); Ok(Data::Int16(ints[0])) } DataType::UInt32 => { let mut ints = [0u32; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size); Ok(Data::UInt32(ints[0])) } DataType::Int32 => { let mut ints = [0i32; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size); Ok(Data::Int32(ints[0])) } DataType::UInt64 => { let mut ints = [0u64; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size); Ok(Data::UInt64(ints[0])) } DataType::Int64 => { let mut ints = [0i64; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size); Ok(Data::Int64(ints[0])) } DataType::Float => { let mut ints = [0f32; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size); Ok(Data::Float(ints[0])) } DataType::Double => { let mut ints = [0f64; 1]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr() as *mut u8, value_size); Ok(Data::Double(ints[0])) } DataType::String => { let mut ints = vec![0u8; value_size]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr(), value_size); // -1 here so CString doesn't see the NUL let cs = match CString::new(&ints[0..value_size - 1_usize]) { Ok(c1) => c1, Err(_) => return Err(CsError::CsErrLibrary), }; match cs.into_string() { Ok(s) => Ok(Data::String(s)), Err(_) => Err(CsError::CsErrLibrary), } } DataType::Binary => { let mut ints = vec![0u8; value_size]; copy_nonoverlapping(c_value as *mut u8, ints.as_mut_ptr(), value_size); Ok(Data::Binary(ints)) } DataType::Unknown => Ok(Data::Unknown), } } } const INITIAL_SIZE: usize = 256; /// Get a value from cmap, returned as a [Data] struct, so could be anything -pub fn get(handle: Handle, key_name: &str) -> Result { +pub fn get(handle: &Handle, key_name: &str) -> Result { let csname = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?; let mut value_size: usize = 16; let mut c_key_type: u32 = 0; // First guess at a size for Strings and Binaries. Expand if needed let mut c_value = vec![0u8; INITIAL_SIZE]; unsafe { let res = ffi::cmap_get( handle.cmap_handle, csname.as_ptr(), c_value.as_mut_ptr() as *mut c_void, &mut value_size, &mut c_key_type, ); if res == ffi::CS_OK { if value_size > INITIAL_SIZE { // Need to try again with a bigger buffer c_value.resize(value_size, 0u8); let res2 = ffi::cmap_get( handle.cmap_handle, csname.as_ptr(), c_value.as_mut_ptr() as *mut c_void, &mut value_size, &mut c_key_type, ); if res2 != ffi::CS_OK { return Err(CsError::from_c(res2)); } } // Convert to Rust type and return as a Data enum c_to_data(value_size, c_key_type, c_value.as_ptr()) } else { Err(CsError::from_c(res)) } } } /// increment the value in a cmap key (must be a numeric type) -pub fn inc(handle: Handle, key_name: &str) -> Result<()> { +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<()> { +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, + handle: &Handle, key_name: &str, track_type: TrackType, notify_callback: &NotifyCallback, user_data: u64, ) -> Result { let c_name = string_to_cstring_validated(key_name, CMAP_KEYNAME_MAXLENGTH)?; let mut c_trackhandle = 0u64; let res = unsafe { ffi::cmap_track_add( handle.cmap_handle, c_name.as_ptr(), track_type.bits, Some(rust_notify_fn), user_data as *mut c_void, &mut c_trackhandle, ) }; if res == ffi::CS_OK { let rhandle = TrackHandle { track_handle: c_trackhandle, notify_callback: *notify_callback, }; TRACKHANDLE_HASH .lock() .unwrap() .insert(c_trackhandle, rhandle); Ok(rhandle) } else { Err(CsError::from_c(res)) } } /// Remove a tracker frm this [Handle] -pub fn track_delete(handle: Handle, track_handle: TrackHandle) -> Result<()> { +pub fn track_delete(handle: &Handle, track_handle: TrackHandle) -> Result<()> { let res = unsafe { ffi::cmap_track_delete(handle.cmap_handle, track_handle.track_handle) }; if res == ffi::CS_OK { TRACKHANDLE_HASH .lock() .unwrap() .remove(&track_handle.track_handle); Ok(()) } else { Err(CsError::from_c(res)) } } /// Create one of these to start iterating over cmap values. pub struct CmapIterStart { iter_handle: u64, cmap_handle: u64, } pub struct CmapIntoIter { cmap_handle: u64, iter_handle: u64, } /// Value returned from the iterator. contains the key name and the [Data] pub struct CmapIter { key_name: String, data: Data, } impl CmapIter { pub fn key_name(&self) -> &str { &self.key_name } pub fn data(&self) -> &Data { &self.data } } impl fmt::Debug for CmapIter { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}: {}", self.key_name, self.data) } } impl Iterator for CmapIntoIter { type Item = CmapIter; fn next(&mut self) -> Option { let mut c_key_name = [0u8; CMAP_KEYNAME_MAXLENGTH + 1]; let mut c_value_len = 0usize; let mut c_value_type = 0u32; let res = unsafe { ffi::cmap_iter_next( self.cmap_handle, self.iter_handle, c_key_name.as_mut_ptr() as *mut c_char, &mut c_value_len, &mut c_value_type, ) }; if res == ffi::CS_OK { // Return the Data for this iteration let mut c_value = vec![0u8; c_value_len]; let res = unsafe { ffi::cmap_get( self.cmap_handle, c_key_name.as_ptr() as *mut c_char, c_value.as_mut_ptr() as *mut c_void, &mut c_value_len, &mut c_value_type, ) }; if res == ffi::CS_OK { match c_to_data(c_value_len, c_value_type, c_value.as_ptr()) { Ok(d) => { let r_keyname = match string_from_bytes( c_key_name.as_ptr() as *mut c_char, CMAP_KEYNAME_MAXLENGTH, ) { Ok(s) => s, Err(_) => return None, }; Some(CmapIter { key_name: r_keyname, data: d, }) } Err(_) => None, } } else { // cmap_get returned error None } } else if res == ffi::CS_ERR_NO_SECTIONS { // End of list unsafe { // Yeah, we don't check this return code. There's nowhere to report it. ffi::cmap_iter_finalize(self.cmap_handle, self.iter_handle) }; None } else { None } } } impl CmapIterStart { /// Create a new [CmapIterStart] object for iterating over a list of cmap keys - pub fn new(cmap_handle: Handle, prefix: &str) -> Result { + pub fn new(cmap_handle: &Handle, prefix: &str) -> Result { let mut iter_handle: u64 = 0; let res = unsafe { let c_prefix = string_to_cstring_validated(prefix, CMAP_KEYNAME_MAXLENGTH)?; ffi::cmap_iter_init(cmap_handle.cmap_handle, c_prefix.as_ptr(), &mut iter_handle) }; if res == ffi::CS_OK { Ok(CmapIterStart { cmap_handle: cmap_handle.cmap_handle, iter_handle, }) } else { Err(CsError::from_c(res)) } } } impl IntoIterator for CmapIterStart { type Item = CmapIter; type IntoIter = CmapIntoIter; fn into_iter(self) -> Self::IntoIter { CmapIntoIter { iter_handle: self.iter_handle, cmap_handle: self.cmap_handle, } } } diff --git a/bindings/rust/src/cpg.rs b/bindings/rust/src/cpg.rs index 12464975..36346453 100644 --- a/bindings/rust/src/cpg.rs +++ b/bindings/rust/src/cpg.rs @@ -1,628 +1,654 @@ // libcpg interface for Rust // Copyright (c) 2020 Red Hat, Inc. // // All rights reserved. // // Author: Christine Caulfield (ccaulfi@redhat.com) // #![allow(clippy::single_match)] #![allow(clippy::needless_range_loop)] #![allow(clippy::type_complexity)] // For the code generated by bindgen use crate::sys::cpg as ffi; use std::collections::HashMap; use std::ffi::{CStr, CString}; use std::fmt; use std::os::raw::{c_int, c_void}; use std::ptr::copy_nonoverlapping; use std::slice; use std::string::String; use std::sync::Mutex; // General corosync things use crate::string_from_bytes; use crate::{CsError, DispatchFlags, NodeId, Result}; const CPG_NAMELEN_MAX: usize = 128; const CPG_MEMBERS_MAX: usize = 128; /// RingId returned by totem_confchg_fn #[derive(Copy, Clone)] pub struct RingId { pub nodeid: NodeId, pub seq: u64, } /// Totem delivery guarantee options for [mcast_joined] // The C enum doesn't have numbers in the code // so don't assume we can match them #[derive(Copy, Clone)] pub enum Guarantee { TypeUnordered, TypeFifo, TypeAgreed, TypeSafe, } // Convert internal to cpg.h values. impl Guarantee { pub fn to_c(&self) -> u32 { match self { Guarantee::TypeUnordered => ffi::CPG_TYPE_UNORDERED, Guarantee::TypeFifo => ffi::CPG_TYPE_FIFO, Guarantee::TypeAgreed => ffi::CPG_TYPE_AGREED, Guarantee::TypeSafe => ffi::CPG_TYPE_SAFE, } } } /// Flow control state returned from [flow_control_state_get] #[derive(Copy, Clone)] pub enum FlowControlState { Disabled, Enabled, } /// No flags current specified for model1 so leave this at None #[derive(Copy, Clone)] pub enum Model1Flags { None, } /// Reason for cpg item callback #[derive(Copy, Clone)] pub enum Reason { Undefined = 0, Join = 1, Leave = 2, NodeDown = 3, NodeUp = 4, ProcDown = 5, } // Convert to cpg.h values impl Reason { pub fn new(r: u32) -> Reason { match r { 0 => Reason::Undefined, 1 => Reason::Join, 2 => Reason::Leave, 3 => Reason::NodeDown, 4 => Reason::NodeUp, 5 => Reason::ProcDown, _ => Reason::Undefined, } } } impl fmt::Display for Reason { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Reason::Undefined => write!(f, "Undefined"), Reason::Join => write!(f, "Join"), Reason::Leave => write!(f, "Leave"), Reason::NodeDown => write!(f, "NodeDown"), Reason::NodeUp => write!(f, "NodeUp"), Reason::ProcDown => write!(f, "ProcDown"), } } } /// A CPG address entry returned in the callbacks pub struct Address { pub nodeid: NodeId, pub pid: u32, pub reason: Reason, } impl fmt::Debug for Address { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "[nodeid: {}, pid: {}, reason: {}]", self.nodeid, self.pid, self.reason ) } } /// Data for model1 [initialize] #[derive(Copy, Clone)] pub struct Model1Data { pub flags: Model1Flags, pub deliver_fn: Option< fn( handle: &Handle, group_name: String, nodeid: NodeId, pid: u32, msg: &[u8], msg_len: usize, ), >, pub confchg_fn: Option< fn( handle: &Handle, group_name: &str, member_list: Vec
, left_list: Vec
, joined_list: Vec
, ), >, pub totem_confchg_fn: Option)>, } /// Modeldata for [initialize], only v1 supported at the moment #[derive(Copy, Clone)] pub enum ModelData { ModelNone, ModelV1(Model1Data), } /// A handle into the cpg library. Returned from [initialize] and needed for all other calls -#[derive(Copy, Clone)] pub struct Handle { cpg_handle: u64, // Corosync library handle model_data: ModelData, + clone: bool, +} + +impl Clone for Handle { + fn clone(&self) -> Handle { + Handle { + cpg_handle: self.cpg_handle, + model_data: self.model_data, + clone: true, + } + } +} + +impl Drop for Handle { + fn drop(self: &mut Handle) { + if !self.clone { + let _e = finalize(self); + } + } +} + +// Clones count as equivalent +impl PartialEq for Handle { + fn eq(&self, other: &Handle) -> bool { + self.cpg_handle == other.cpg_handle + } } // Used to convert a CPG handle into one of ours lazy_static! { static ref HANDLE_HASH: Mutex> = Mutex::new(HashMap::new()); } // Convert a Rust String into a cpg_name struct for libcpg fn string_to_cpg_name(group: &str) -> Result { if group.len() > CPG_NAMELEN_MAX - 1 { return Err(CsError::CsErrInvalidParam); } let c_name = match CString::new(group) { Ok(n) => n, Err(_) => return Err(CsError::CsErrLibrary), }; let mut c_group = ffi::cpg_name { length: group.len() as u32, value: [0; CPG_NAMELEN_MAX], }; unsafe { // NOTE param order is 'wrong-way round' from C copy_nonoverlapping(c_name.as_ptr(), c_group.value.as_mut_ptr(), group.len()); } Ok(c_group) } // Convert an array of cpg_addresses to a Vec - used in callbacks fn cpg_array_to_vec(list: *const ffi::cpg_address, list_entries: usize) -> Vec
{ let temp: &[ffi::cpg_address] = unsafe { slice::from_raw_parts(list, list_entries) }; let mut r_vec = Vec::
::new(); for i in 0..list_entries { let a: Address = Address { nodeid: NodeId::from(temp[i].nodeid), pid: temp[i].pid, reason: Reason::new(temp[i].reason), }; r_vec.push(a); } r_vec } // Called from CPG callback function - munge params back to Rust from C extern "C" fn rust_deliver_fn( handle: ffi::cpg_handle_t, group_name: *const ffi::cpg_name, nodeid: u32, pid: u32, msg: *mut ::std::os::raw::c_void, msg_len: usize, ) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) { // Convert group_name into a Rust str. let r_group_name = unsafe { CStr::from_ptr(&(*group_name).value[0]) .to_string_lossy() .into_owned() }; let data: &[u8] = unsafe { std::slice::from_raw_parts(msg as *const u8, msg_len) }; match h.model_data { ModelData::ModelV1(md) => { if let Some(cb) = md.deliver_fn { (cb)(h, r_group_name, NodeId::from(nodeid), pid, data, msg_len); } } _ => {} } } } // Called from CPG callback function - munge params back to Rust from C extern "C" fn rust_confchg_fn( handle: ffi::cpg_handle_t, group_name: *const ffi::cpg_name, member_list: *const ffi::cpg_address, member_list_entries: usize, left_list: *const ffi::cpg_address, left_list_entries: usize, joined_list: *const ffi::cpg_address, joined_list_entries: usize, ) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) { let r_group_name = unsafe { CStr::from_ptr(&(*group_name).value[0]) .to_string_lossy() .into_owned() }; let r_member_list = cpg_array_to_vec(member_list, member_list_entries); let r_left_list = cpg_array_to_vec(left_list, left_list_entries); let r_joined_list = cpg_array_to_vec(joined_list, joined_list_entries); match h.model_data { ModelData::ModelV1(md) => { if let Some(cb) = md.confchg_fn { (cb)(h, &r_group_name, r_member_list, r_left_list, r_joined_list); } } _ => {} } } } // Called from CPG callback function - munge params back to Rust from C extern "C" fn rust_totem_confchg_fn( handle: ffi::cpg_handle_t, ring_id: ffi::cpg_ring_id, member_list_entries: u32, member_list: *const u32, ) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) { let r_ring_id = RingId { nodeid: NodeId::from(ring_id.nodeid), seq: ring_id.seq, }; let mut r_member_list = Vec::::new(); let temp_members: &[u32] = unsafe { slice::from_raw_parts(member_list, member_list_entries as usize) }; for i in 0..member_list_entries as usize { r_member_list.push(NodeId::from(temp_members[i])); } match h.model_data { ModelData::ModelV1(md) => { if let Some(cb) = md.totem_confchg_fn { (cb)(h, r_ring_id, r_member_list); } } _ => {} } } } /// Initialize a connection to the cpg library. You must call this before doing anything /// else and use the passed back [Handle]. /// Remember to free the handle using [finalize] when finished. pub fn initialize(model_data: &ModelData, context: u64) -> Result { let mut handle: ffi::cpg_handle_t = 0; let mut m = match model_data { ModelData::ModelV1(_v1) => { ffi::cpg_model_v1_data_t { model: ffi::CPG_MODEL_V1, cpg_deliver_fn: Some(rust_deliver_fn), cpg_confchg_fn: Some(rust_confchg_fn), cpg_totem_confchg_fn: Some(rust_totem_confchg_fn), flags: 0, // No supported flags (yet) } } _ => return Err(CsError::CsErrInvalidParam), }; unsafe { let c_context: *mut c_void = &mut &context as *mut _ as *mut c_void; let c_model: *mut ffi::cpg_model_data_t = &mut m as *mut _ as *mut ffi::cpg_model_data_t; let res = ffi::cpg_model_initialize(&mut handle, m.model, c_model, c_context); if res == ffi::CS_OK { let rhandle = Handle { cpg_handle: handle, model_data: *model_data, + clone: false, }; - HANDLE_HASH.lock().unwrap().insert(handle, rhandle); + HANDLE_HASH.lock().unwrap().insert(handle, rhandle.clone()); Ok(rhandle) } else { Err(CsError::from_c(res)) } } } /// Finish with a connection to corosync -pub fn finalize(handle: Handle) -> Result<()> { +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 { +pub fn fd_get(handle: &Handle) -> Result { let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int; let res = unsafe { ffi::cpg_fd_get(handle.cpg_handle, c_fd) }; if res == ffi::CS_OK { Ok(c_fd as i32) } else { Err(CsError::from_c(res)) } } /// Call any/all active CPG callbacks for this [Handle] see [DispatchFlags] for details -pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> { +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<()> { +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<()> { +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 { +pub fn local_get(handle: &Handle) -> Result { let mut nodeid: u32 = 0; let res = unsafe { ffi::cpg_local_get(handle.cpg_handle, &mut nodeid) }; if res == ffi::CS_OK { Ok(NodeId::from(nodeid)) } else { Err(CsError::from_c(res)) } } /// Get a list of members of a CPG group as a vector of [Address] structs -pub fn membership_get(handle: Handle, group: &str) -> Result> { +pub fn membership_get(handle: &Handle, group: &str) -> Result> { let mut member_list_entries: i32 = 0; let member_list = [ffi::cpg_address { nodeid: 0, pid: 0, reason: 0, }; CPG_MEMBERS_MAX]; let res = unsafe { let mut c_group = string_to_cpg_name(group)?; let c_memlist = member_list.as_ptr() as *mut ffi::cpg_address; ffi::cpg_membership_get( handle.cpg_handle, &mut c_group, &mut *c_memlist, &mut member_list_entries, ) }; if res == ffi::CS_OK { Ok(cpg_array_to_vec( member_list.as_ptr(), member_list_entries as usize, )) } else { Err(CsError::from_c(res)) } } /// Get the maximum size that CPG can send in one corosync message, /// any messages sent via [mcast_joined] that are larger than this /// will be fragmented -pub fn max_atomic_msgsize_get(handle: Handle) -> Result { +pub fn max_atomic_msgsize_get(handle: &Handle) -> Result { let mut asize: u32 = 0; let res = unsafe { ffi::cpg_max_atomic_msgsize_get(handle.cpg_handle, &mut asize) }; if res == ffi::CS_OK { Ok(asize) } else { Err(CsError::from_c(res)) } } /// Get the current 'context' value for this handle. /// The context value is an arbitrary value that is always passed /// back to callbacks to help identify the source -pub fn context_get(handle: Handle) -> Result { +pub fn context_get(handle: &Handle) -> Result { let mut c_context: *mut c_void = &mut 0u64 as *mut _ as *mut c_void; let (res, context) = unsafe { let r = ffi::cpg_context_get(handle.cpg_handle, &mut c_context); let context: u64 = c_context as u64; (r, context) }; if res == ffi::CS_OK { Ok(context) } else { Err(CsError::from_c(res)) } } /// Set the current 'context' value for this handle. /// The context value is an arbitrary value that is always passed /// back to callbacks to help identify the source. /// Normally this is set in [initialize], but this allows it to be changed -pub fn context_set(handle: Handle, context: u64) -> Result<()> { +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 { +pub fn flow_control_state_get(handle: &Handle) -> Result { let mut fc_state: u32 = 0; let res = unsafe { ffi::cpg_flow_control_state_get(handle.cpg_handle, &mut fc_state) }; if res == ffi::CS_OK { if fc_state == 1 { Ok(true) } else { Ok(false) } } else { Err(CsError::from_c(res)) } } /// Send a message to the currently joined CPG group -pub fn mcast_joined(handle: Handle, guarantee: Guarantee, msg: &[u8]) -> Result<()> { +pub fn mcast_joined(handle: &Handle, guarantee: Guarantee, msg: &[u8]) -> Result<()> { let c_iovec = ffi::iovec { iov_base: msg.as_ptr() as *mut c_void, iov_len: msg.len(), }; let res = unsafe { ffi::cpg_mcast_joined(handle.cpg_handle, guarantee.to_c(), &c_iovec, 1) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } /// Type of iteration for [CpgIterStart] #[derive(Copy, Clone)] pub enum CpgIterType { NameOnly = 1, OneGroup = 2, All = 3, } // Iterator based on information on this page. thank you! // https://stackoverflow.com/questions/30218886/how-to-implement-iterator-and-intoiterator-for-a-simple-struct // Object to iterate over /// An object to iterate over a list of CPG groups, create one of these and then use 'for' over it pub struct CpgIterStart { iter_handle: u64, } /// struct returned from iterating over a [CpgIterStart] pub struct CpgIter { pub group: String, pub nodeid: NodeId, pub pid: u32, } pub struct CpgIntoIter { iter_handle: u64, } impl fmt::Debug for CpgIter { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "[group: {}, nodeid: {}, pid: {}]", self.group, self.nodeid, self.pid ) } } impl Iterator for CpgIntoIter { type Item = CpgIter; fn next(&mut self) -> Option { let mut c_iter_description = ffi::cpg_iteration_description_t { nodeid: 0, pid: 0, group: ffi::cpg_name { length: 0_u32, value: [0; CPG_NAMELEN_MAX], }, }; let res = unsafe { ffi::cpg_iteration_next(self.iter_handle, &mut c_iter_description) }; if res == ffi::CS_OK { let r_group = match string_from_bytes(c_iter_description.group.value.as_ptr(), CPG_NAMELEN_MAX) { Ok(groupname) => groupname, Err(_) => return None, }; Some(CpgIter { group: r_group, nodeid: NodeId::from(c_iter_description.nodeid), pid: c_iter_description.pid, }) } else if res == ffi::CS_ERR_NO_SECTIONS { // End of list unsafe { // Yeah, we don't check this return code. There's nowhere to report it. ffi::cpg_iteration_finalize(self.iter_handle) }; None } else { None } } } impl CpgIterStart { /// Create a new [CpgIterStart] object for iterating over a list of active CPG groups - pub fn new(cpg_handle: Handle, group: &str, iter_type: CpgIterType) -> Result { + pub fn new(cpg_handle: &Handle, group: &str, iter_type: CpgIterType) -> Result { let mut iter_handle: u64 = 0; let res = unsafe { let mut c_group = string_to_cpg_name(group)?; let c_itertype = iter_type as u32; // IterType 'All' requires that the group pointer is passed in as NULL let c_group_ptr = { match iter_type { CpgIterType::All => std::ptr::null_mut(), _ => &mut c_group, } }; ffi::cpg_iteration_initialize( cpg_handle.cpg_handle, c_itertype, c_group_ptr, &mut iter_handle, ) }; if res == ffi::CS_OK { Ok(CpgIterStart { iter_handle }) } else { Err(CsError::from_c(res)) } } } impl IntoIterator for CpgIterStart { type Item = CpgIter; type IntoIter = CpgIntoIter; fn into_iter(self) -> Self::IntoIter { CpgIntoIter { iter_handle: self.iter_handle, } } } diff --git a/bindings/rust/src/quorum.rs b/bindings/rust/src/quorum.rs index 25c2fe62..d20f1d64 100644 --- a/bindings/rust/src/quorum.rs +++ b/bindings/rust/src/quorum.rs @@ -1,298 +1,324 @@ // libquorum interface for Rust // Copyright (c) 2021 Red Hat, Inc. // // All rights reserved. // // Author: Christine Caulfield (ccaulfi@redhat.com) // #![allow(clippy::type_complexity)] #![allow(clippy::needless_range_loop)] #![allow(clippy::single_match)] // For the code generated by bindgen use crate::sys::quorum as ffi; use crate::{CsError, DispatchFlags, NodeId, Result, TrackFlags}; use std::collections::HashMap; use std::os::raw::{c_int, c_void}; use std::slice; use std::sync::Mutex; /// Data for model1 [initialize] #[derive(Copy, Clone)] pub enum ModelData { ModelNone, ModelV1(Model1Data), } /// Value returned from [initialize]. Indicates whether quorum is currently active on this cluster. pub enum QuorumType { Free, Set, } /// Flags for [initialize], none currently supported #[derive(Copy, Clone)] pub enum Model1Flags { None, } /// RingId returned in quorum_notification_fn pub struct RingId { pub nodeid: NodeId, pub seq: u64, } // Used to convert a QUORUM handle into one of ours lazy_static! { static ref HANDLE_HASH: Mutex> = Mutex::new(HashMap::new()); } fn list_to_vec(list_entries: u32, list: *const u32) -> Vec { let mut r_member_list = Vec::::new(); let temp_members: &[u32] = unsafe { slice::from_raw_parts(list, list_entries as usize) }; for i in 0..list_entries as usize { r_member_list.push(NodeId::from(temp_members[i])); } r_member_list } // Called from quorum callback function - munge params back to Rust from C extern "C" fn rust_quorum_notification_fn( handle: ffi::quorum_handle_t, quorate: u32, ring_id: ffi::quorum_ring_id, member_list_entries: u32, member_list: *const u32, ) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) { let r_ring_id = RingId { nodeid: NodeId::from(ring_id.nodeid), seq: ring_id.seq, }; let r_member_list = list_to_vec(member_list_entries, member_list); let r_quorate = match quorate { 0 => false, 1 => true, _ => false, }; match &h.model_data { ModelData::ModelV1(md) => { if let Some(cb) = md.quorum_notification_fn { (cb)(h, r_quorate, r_ring_id, r_member_list); } } _ => {} } } } extern "C" fn rust_nodelist_notification_fn( handle: ffi::quorum_handle_t, ring_id: ffi::quorum_ring_id, member_list_entries: u32, member_list: *const u32, joined_list_entries: u32, joined_list: *const u32, left_list_entries: u32, left_list: *const u32, ) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) { let r_ring_id = RingId { nodeid: NodeId::from(ring_id.nodeid), seq: ring_id.seq, }; let r_member_list = list_to_vec(member_list_entries, member_list); let r_joined_list = list_to_vec(joined_list_entries, joined_list); let r_left_list = list_to_vec(left_list_entries, left_list); match &h.model_data { ModelData::ModelV1(md) => { if let Some(cb) = md.nodelist_notification_fn { (cb)(h, r_ring_id, r_member_list, r_joined_list, r_left_list); } } _ => {} } } } #[derive(Copy, Clone)] /// Data for model1 [initialize] pub struct Model1Data { pub flags: Model1Flags, pub quorum_notification_fn: Option)>, pub nodelist_notification_fn: Option< fn( hande: &Handle, ring_id: RingId, member_list: Vec, joined_list: Vec, left_list: Vec, ), >, } /// A handle into the quorum library. Returned from [initialize] and needed for all other calls -#[derive(Copy, Clone)] pub struct Handle { quorum_handle: u64, model_data: ModelData, + clone: bool, +} + +impl Clone for Handle { + fn clone(&self) -> Handle { + Handle { + quorum_handle: self.quorum_handle, + model_data: self.model_data, + clone: true, + } + } +} + +impl Drop for Handle { + fn drop(self: &mut Handle) { + if !self.clone { + let _e = finalize(self); + } + } +} + +// Clones count as equivalent +impl PartialEq for Handle { + fn eq(&self, other: &Handle) -> bool { + self.quorum_handle == other.quorum_handle + } } /// 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, + clone: false, }; - HANDLE_HASH.lock().unwrap().insert(handle, rhandle); + HANDLE_HASH.lock().unwrap().insert(handle, rhandle.clone()); Ok((rhandle, quorum_type)) } /// Finish with a connection to corosync -pub fn finalize(handle: Handle) -> Result<()> { +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 { +pub fn fd_get(handle: &Handle) -> Result { let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int; let res = unsafe { ffi::quorum_fd_get(handle.quorum_handle, c_fd) }; if res == ffi::CS_OK { Ok(c_fd as i32) } else { Err(CsError::from_c(res)) } } /// Display any/all active QUORUM callbacks for this [Handle], see [DispatchFlags] for details -pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> { +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 { +pub fn getquorate(handle: &Handle) -> Result { let c_quorate: *mut c_int = &mut 0 as *mut _ as *mut c_int; let (res, r_quorate) = unsafe { let res = ffi::quorum_getquorate(handle.quorum_handle, c_quorate); let r_quorate: i32 = *c_quorate; (res, r_quorate) }; if res == ffi::CS_OK { match r_quorate { 0 => Ok(false), 1 => Ok(true), _ => Err(CsError::CsErrLibrary), } } else { Err(CsError::from_c(res)) } } /// Track node and quorum changes -pub fn trackstart(handle: Handle, flags: TrackFlags) -> Result<()> { +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<()> { +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 { +pub fn context_get(handle: &Handle) -> Result { let (res, context) = unsafe { let mut context: u64 = 0; let c_context: *mut c_void = &mut context as *mut _ as *mut c_void; let r = ffi::quorum_context_get(handle.quorum_handle, c_context as *mut *const c_void); (r, context) }; if res == ffi::CS_OK { Ok(context) } else { Err(CsError::from_c(res)) } } /// Set the current 'context' value for this handle. /// The context value is an arbitrary value that is always passed /// back to callbacks to help identify the source. /// Normally this is set in [initialize], but this allows it to be changed -pub fn context_set(handle: Handle, context: u64) -> Result<()> { +pub fn context_set(handle: &Handle, context: u64) -> Result<()> { let res = unsafe { let c_context = context as *mut c_void; ffi::quorum_context_set(handle.quorum_handle, c_context) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } diff --git a/bindings/rust/src/votequorum.rs b/bindings/rust/src/votequorum.rs index 4718b586..1e34fcac 100644 --- a/bindings/rust/src/votequorum.rs +++ b/bindings/rust/src/votequorum.rs @@ -1,501 +1,527 @@ // libvotequorum interface for Rust // Copyright (c) 2021 Red Hat, Inc. // // All rights reserved. // // Author: Christine Caulfield (ccaulfi@redhat.com) // #![allow(clippy::type_complexity)] #![allow(clippy::needless_range_loop)] #![allow(clippy::single_match)] // For the code generated by bindgen use crate::sys::votequorum as ffi; use std::collections::HashMap; use std::ffi::CString; use std::fmt; use std::os::raw::{c_int, c_void}; use std::slice; use std::sync::Mutex; use crate::string_from_bytes; use crate::{CsError, DispatchFlags, NodeId, Result, TrackFlags}; /// RingId returned by votequorum_notification_fn pub struct RingId { pub nodeid: NodeId, pub seq: u64, } // Used to convert a VOTEQUORUM handle into one of ours lazy_static! { static ref HANDLE_HASH: Mutex> = Mutex::new(HashMap::new()); } /// Current state of a node in the cluster, part of the [NodeInfo] and [Node] structs pub enum NodeState { Member, Dead, Leaving, Unknown, } impl NodeState { pub fn new(state: u32) -> NodeState { match state { 1 => NodeState::Member, 2 => NodeState::Dead, 3 => NodeState::Leaving, _ => NodeState::Unknown, } } } impl fmt::Debug for NodeState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { NodeState::Member => write!(f, "Member"), NodeState::Dead => write!(f, "Dead"), NodeState::Leaving => write!(f, "Leaving"), _ => write!(f, "Unknown"), } } } /// Basic information about a node in the cluster. Contains [NodeId], and [NodeState] pub struct Node { nodeid: NodeId, state: NodeState, } impl fmt::Debug for Node { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "nodeid: {}, state: {:?}", self.nodeid, self.state) } } bitflags! { /// Flags in the [NodeInfo] struct pub struct NodeInfoFlags: u32 { const VOTEQUORUM_INFO_TWONODE = 1; const VOTEQUORUM_INFO_QUORATE = 2; const VOTEQUORUM_INFO_WAIT_FOR_ALL = 4; const VOTEQUORUM_INFO_LAST_MAN_STANDING = 8; const VOTEQUORUM_INFO_AUTO_TIE_BREAKER = 16; const VOTEQUORUM_INFO_ALLOW_DOWNSCALE = 32; const VOTEQUORUM_INFO_QDEVICE_REGISTERED = 64; const VOTEQUORUM_INFO_QDEVICE_ALIVE = 128; const VOTEQUORUM_INFO_QDEVICE_CAST_VOTE = 256; const VOTEQUORUM_INFO_QDEVICE_MASTER_WINS = 512; } } /// Detailed information about a node in the cluster, returned from [get_info] pub struct NodeInfo { pub node_id: NodeId, pub node_state: NodeState, pub node_votes: u32, pub node_expected_votes: u32, pub highest_expected: u32, pub quorum: u32, pub flags: NodeInfoFlags, pub qdevice_votes: u32, pub qdevice_name: String, } // Turn a C nodeID list into a vec of NodeIds fn list_to_vec(list_entries: u32, list: *const u32) -> Vec { let mut r_member_list = Vec::::new(); let temp_members: &[u32] = unsafe { slice::from_raw_parts(list, list_entries as usize) }; for i in 0..list_entries as usize { r_member_list.push(NodeId::from(temp_members[i])); } r_member_list } // Called from votequorum callback function - munge params back to Rust from C extern "C" fn rust_expectedvotes_notification_fn( handle: ffi::votequorum_handle_t, context: u64, expected_votes: u32, ) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) { if let Some(cb) = h.callbacks.expectedvotes_notification_fn { (cb)(h, context, expected_votes); } } } // Called from votequorum callback function - munge params back to Rust from C extern "C" fn rust_quorum_notification_fn( handle: ffi::votequorum_handle_t, context: u64, quorate: u32, node_list_entries: u32, node_list: *mut ffi::votequorum_node_t, ) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) { let r_quorate = match quorate { 0 => false, 1 => true, _ => false, }; let mut r_node_list = Vec::::new(); let temp_members: &[ffi::votequorum_node_t] = unsafe { slice::from_raw_parts(node_list, node_list_entries as usize) }; for i in 0..node_list_entries as usize { r_node_list.push(Node { nodeid: NodeId::from(temp_members[i].nodeid), state: NodeState::new(temp_members[i].state), }); } if let Some(cb) = h.callbacks.quorum_notification_fn { (cb)(h, context, r_quorate, r_node_list); } } } // Called from votequorum callback function - munge params back to Rust from C extern "C" fn rust_nodelist_notification_fn( handle: ffi::votequorum_handle_t, context: u64, ring_id: ffi::votequorum_ring_id_t, node_list_entries: u32, node_list: *mut u32, ) { if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) { let r_ring_id = RingId { nodeid: NodeId::from(ring_id.nodeid), seq: ring_id.seq, }; let r_node_list = list_to_vec(node_list_entries, node_list); if let Some(cb) = h.callbacks.nodelist_notification_fn { (cb)(h, context, r_ring_id, r_node_list); } } } /// Callbacks that can be called from votequorum, pass these in to [initialize] #[derive(Copy, Clone)] pub struct Callbacks { pub quorum_notification_fn: Option)>, pub nodelist_notification_fn: Option)>, pub expectedvotes_notification_fn: Option, } /// A handle into the votequorum library. Returned from [initialize] and needed for all other calls -#[derive(Copy, Clone)] pub struct Handle { votequorum_handle: u64, callbacks: Callbacks, + clone: bool, +} + +impl Clone for Handle { + fn clone(&self) -> Handle { + Handle { + votequorum_handle: self.votequorum_handle, + callbacks: self.callbacks, + clone: true, + } + } +} + +impl Drop for Handle { + fn drop(self: &mut Handle) { + if !self.clone { + let _e = finalize(self); + } + } +} + +// Clones count as equivalent +impl PartialEq for Handle { + fn eq(&self, other: &Handle) -> bool { + self.votequorum_handle == other.votequorum_handle + } } /// Initialize a connection to the votequorum library. You must call this before doing anything /// else and use the passed back [Handle]. /// Remember to free the handle using [finalize] when finished. pub fn initialize(callbacks: &Callbacks) -> Result { let mut handle: ffi::votequorum_handle_t = 0; let mut c_callbacks = ffi::votequorum_callbacks_t { votequorum_quorum_notify_fn: Some(rust_quorum_notification_fn), votequorum_nodelist_notify_fn: Some(rust_nodelist_notification_fn), votequorum_expectedvotes_notify_fn: Some(rust_expectedvotes_notification_fn), }; unsafe { let res = ffi::votequorum_initialize(&mut handle, &mut c_callbacks); if res == ffi::CS_OK { let rhandle = Handle { votequorum_handle: handle, callbacks: *callbacks, + clone: false, }; - HANDLE_HASH.lock().unwrap().insert(handle, rhandle); + HANDLE_HASH.lock().unwrap().insert(handle, rhandle.clone()); Ok(rhandle) } else { Err(CsError::from_c(res)) } } } /// Finish with a connection to corosync -pub fn finalize(handle: Handle) -> Result<()> { +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 { +pub fn fd_get(handle: &Handle) -> Result { let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int; let res = unsafe { ffi::votequorum_fd_get(handle.votequorum_handle, c_fd) }; if res == ffi::CS_OK { Ok(c_fd as i32) } else { Err(CsError::from_c(res)) } } const VOTEQUORUM_QDEVICE_MAX_NAME_LEN: usize = 255; /// Returns detailed information about a node in a [NodeInfo] structure -pub fn get_info(handle: Handle, nodeid: NodeId) -> Result { +pub fn get_info(handle: &Handle, nodeid: NodeId) -> Result { let mut c_info = ffi::votequorum_info { node_id: 0, node_state: 0, node_votes: 0, node_expected_votes: 0, highest_expected: 0, total_votes: 0, quorum: 0, flags: 0, qdevice_votes: 0, qdevice_name: [0; 255usize], }; let res = unsafe { ffi::votequorum_getinfo(handle.votequorum_handle, u32::from(nodeid), &mut c_info) }; if res == ffi::CS_OK { let info = NodeInfo { node_id: NodeId::from(c_info.node_id), node_state: NodeState::new(c_info.node_state), node_votes: c_info.node_votes, node_expected_votes: c_info.node_expected_votes, highest_expected: c_info.highest_expected, quorum: c_info.quorum, flags: NodeInfoFlags { bits: c_info.flags }, qdevice_votes: c_info.qdevice_votes, qdevice_name: match string_from_bytes( c_info.qdevice_name.as_ptr(), VOTEQUORUM_QDEVICE_MAX_NAME_LEN, ) { Ok(s) => s, Err(_) => String::new(), }, }; Ok(info) } else { Err(CsError::from_c(res)) } } /// Call any/all active votequorum callbacks for this [Handle]. see [DispatchFlags] for details -pub fn dispatch(handle: Handle, flags: DispatchFlags) -> Result<()> { +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<()> { +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<()> { +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 { +pub fn context_get(handle: &Handle) -> Result { let (res, context) = unsafe { let mut c_context: *mut c_void = &mut 0u64 as *mut _ as *mut c_void; let r = ffi::votequorum_context_get(handle.votequorum_handle, &mut c_context); let context: u64 = c_context as u64; (r, context) }; if res == ffi::CS_OK { Ok(context) } else { Err(CsError::from_c(res)) } } /// Set the current 'context' value for this handle. /// The context value is an arbitrary value that is always passed /// back to callbacks to help identify the source. /// Normally this is set in [trackstart], but this allows it to be changed -pub fn context_set(handle: Handle, context: u64) -> Result<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +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<()> { +pub fn qdevice_master_wins(handle: &Handle, name: &str, master_wins: bool) -> Result<()> { let c_string = { match CString::new(name) { Ok(cs) => cs, Err(_) => return Err(CsError::CsErrInvalidParam), } }; let c_master_wins: u32 = u32::from(master_wins); let res = unsafe { ffi::votequorum_qdevice_master_wins( handle.votequorum_handle, c_string.as_ptr(), c_master_wins, ) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } diff --git a/bindings/rust/tests/src/bin/cfg-test.rs b/bindings/rust/tests/src/bin/cfg-test.rs index cd70d388..1b245251 100644 --- a/bindings/rust/tests/src/bin/cfg-test.rs +++ b/bindings/rust/tests/src/bin/cfg-test.rs @@ -1,135 +1,135 @@ // Test the CFG library. Requires that corosync is running and that we are root. extern crate rust_corosync as corosync; use corosync::{cfg, NodeId}; use std::thread::spawn; -fn dispatch_thread(handle: cfg::Handle) { +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) { + 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) { + match cfg::track_start(&handle2, cfg::TrackFlags::None) { Ok(_) => { // Run handle2 dispatch in its own thread - spawn(move || dispatch_thread(handle2)); + spawn(move || dispatch_thread(&handle2)); } Err(e) => { println!("Error in CFG track_start: {e}"); } }; let local_nodeid = { - match cfg::local_get(handle) { + 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); + 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); + res = cfg::node_status_get(&handle, us_less1, cfg::NodeStatusVersion::V1); }; match res { Ok(ns) => { println!("Node Status for nodeid {}", ns.nodeid); println!(" reachable: {}", ns.reachable); println!(" remote: {}", ns.remote); println!(" onwire_min: {}", ns.onwire_min); println!(" onwire_max: {}", ns.onwire_max); println!(" onwire_ver: {}", ns.onwire_ver); for (ls_num, ls) in ns.link_status.iter().enumerate() { if ls.enabled { println!(" Link {ls_num}"); println!(" connected: {}", ls.connected); println!(" mtu: {}", ls.mtu); println!(" src: {}", ls.src_ipaddr); println!(" dst: {}", ls.dst_ipaddr); } } } Err(e) => { println!( "Error in CFG node_status get: {e} (tried nodeids {us_plus1} & {us_less1})" ); } } } - // This should not shutdown corosync because the callback on handle2 will refuse it. - match cfg::try_shutdown(handle, cfg::ShutdownFlags::Request) { + // 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() { + if cfg::dispatch(&handle, corosync::DispatchFlags::One).is_err() { break; } } println!("ERROR: Corosync quit"); } diff --git a/bindings/rust/tests/src/bin/cmap-test.rs b/bindings/rust/tests/src/bin/cmap-test.rs index f4356536..25ec722a 100644 --- a/bindings/rust/tests/src/bin/cmap-test.rs +++ b/bindings/rust/tests/src/bin/cmap-test.rs @@ -1,195 +1,195 @@ // Test the CMAP library. Requires that corosync is running and that we are root. extern crate rust_corosync as corosync; use corosync::cmap; fn track_notify_fn( _handle: &cmap::Handle, _track_handle: &cmap::TrackHandle, event: cmap::TrackType, key_name: &str, old_value: &cmap::Data, new_value: &cmap::Data, user_data: u64, ) { println!("Track notify callback"); println!("Key: {key_name}, event: {event}, user_data: {user_data}"); println!(" Old value: {old_value}"); println!(" New value: {new_value}"); } fn main() { let handle = match cmap::initialize(cmap::Map::Icmap) { Ok(h) => { println!("cmap initialized."); h } Err(e) => { println!("Error in CMAP (Icmap) init: {e}"); return; } }; // Test some SETs - if let Err(e) = cmap::set_u32(handle, "test.test_uint32", 456) { + 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) { + 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) { + 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) { + 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") { + 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) { + 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) { + 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") { + 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") { + 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") { + 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") { + 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") { + 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") { + match cmap::get(&handle, "test.test_data") { Ok(v) => match v { cmap::Data::UInt64(u) => println!("GOT data value {u:x}"), _ => println!("ERROR type was not UInt64, got {v}"), }, Err(e) => { println!("Error in CMAP get: {e}"); return; } }; // Test an iterator - match cmap::CmapIterStart::new(handle, "totem.") { + 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) { + 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, + &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) { + if let Err(e) = cmap::dispatch(&handle, corosync::DispatchFlags::One) { println!("Error from CMAP dispatch: {e}"); } // Just do 5 event_num += 1; if event_num > 5 { break; } } } diff --git a/bindings/rust/tests/src/bin/cpg-test.rs b/bindings/rust/tests/src/bin/cpg-test.rs index df83c2d5..c5a58edc 100644 --- a/bindings/rust/tests/src/bin/cpg-test.rs +++ b/bindings/rust/tests/src/bin/cpg-test.rs @@ -1,142 +1,142 @@ // Test the CPG library. Requires that corosync is running and that we are root. extern crate rust_corosync as corosync; use corosync::{cpg, NodeId}; use std::str; fn deliver_fn( _handle: &cpg::Handle, group_name: String, nodeid: NodeId, pid: u32, msg: &[u8], msg_len: usize, ) { println!( "TEST deliver_fn called for {group_name}, from nodeid/pid {nodeid}/{pid}. len={msg_len}" ); // Print as text if it's valid UTF8 match str::from_utf8(msg) { Ok(s) => println!(" {s}"), Err(_) => { for i in msg { print!("{i:02x} "); } println!(); } } } fn confchg_fn( _handle: &cpg::Handle, group_name: &str, member_list: Vec, left_list: Vec, joined_list: Vec, ) { println!("TEST confchg_fn called for {group_name}"); println!(" members: {member_list:?}"); println!(" left: {left_list:?}"); println!(" joined: {joined_list:?}"); } fn totem_confchg_fn(_handle: &cpg::Handle, ring_id: cpg::RingId, member_list: Vec) { println!( "TEST totem_confchg_fn called for {}/{}", ring_id.nodeid, ring_id.seq ); println!(" members: {member_list:?}"); } fn main() { // Initialise the model data let md = cpg::ModelData::ModelV1(cpg::Model1Data { flags: cpg::Model1Flags::None, deliver_fn: Some(deliver_fn), confchg_fn: Some(confchg_fn), totem_confchg_fn: Some(totem_confchg_fn), }); let handle = match cpg::initialize(&md, 99_u64) { Ok(h) => h, Err(e) => { println!("Error in CPG init: {e}"); return; } }; - if let Err(e) = cpg::join(handle, "TEST") { + if let Err(e) = cpg::join(&handle, "TEST") { println!("Error in CPG join: {e}"); return; } - match cpg::local_get(handle) { + 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") { + 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) { + 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) { + match cpg::context_get(&handle) { Ok(c) => { if c != set_context { println!("Error: context_get() returned {c:x}, context should be {set_context:x}"); } } Err(e) => { println!("Error in CPG context_get: {e}"); } } // Test iterator - match cpg::CpgIterStart::new(handle, "", cpg::CpgIterType::All) { + 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, + &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() { + if cpg::dispatch(&handle, corosync::DispatchFlags::One).is_err() { break; } } println!("ERROR: Corosync quit"); } diff --git a/bindings/rust/tests/src/bin/quorum-test.rs b/bindings/rust/tests/src/bin/quorum-test.rs index 5797b7d0..9436b392 100644 --- a/bindings/rust/tests/src/bin/quorum-test.rs +++ b/bindings/rust/tests/src/bin/quorum-test.rs @@ -1,83 +1,83 @@ // Test the QUORUM library. Requires that corosync is running and that we are root. extern crate rust_corosync as corosync; use corosync::{quorum, NodeId}; fn quorum_fn( _handle: &quorum::Handle, quorate: bool, ring_id: quorum::RingId, member_list: Vec, ) { println!("TEST quorum_fn called. quorate = {quorate}"); println!(" ring_id: {}/{}", ring_id.nodeid, ring_id.seq); println!(" members: {member_list:?}"); } fn nodelist_fn( _handle: &quorum::Handle, ring_id: quorum::RingId, member_list: Vec, joined_list: Vec, left_list: Vec, ) { println!( "TEST nodelist_fn called for {}/{}", ring_id.nodeid, ring_id.seq ); println!(" members: {member_list:?}"); println!(" joined: {joined_list:?}"); println!(" left: {left_list:?}"); } fn main() { // Initialise the model data let md = quorum::ModelData::ModelV1(quorum::Model1Data { flags: quorum::Model1Flags::None, quorum_notification_fn: Some(quorum_fn), nodelist_notification_fn: Some(nodelist_fn), }); let handle = match quorum::initialize(&md, 99_u64) { Ok((h, t)) => { println!("Quorum initialized; type = {}", t as u32); h } Err(e) => { println!("Error in QUORUM init: {e}"); return; } }; // Test context APIs let set_context: u64 = 0xabcdbeefcafe; - if let Err(e) = quorum::context_set(handle, set_context) { + 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) { + match quorum::context_get(&handle) { Ok(c) => { if c != set_context { println!("Error: context_get() returned {c:x}, context should be {set_context:x}"); } } Err(e) => { println!("Error in QUORUM context_get: {e}"); } } - if let Err(e) = quorum::trackstart(handle, corosync::TrackFlags::Changes) { + 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() { + if quorum::dispatch(&handle, corosync::DispatchFlags::One).is_err() { break; } } println!("ERROR: Corosync quit"); } diff --git a/bindings/rust/tests/src/bin/votequorum-test.rs b/bindings/rust/tests/src/bin/votequorum-test.rs index cf9746b6..eebc9f0d 100644 --- a/bindings/rust/tests/src/bin/votequorum-test.rs +++ b/bindings/rust/tests/src/bin/votequorum-test.rs @@ -1,117 +1,117 @@ // Test the VOTEQUORUM library. Requires that corosync is running and that we are root. extern crate rust_corosync as corosync; use corosync::votequorum; fn quorum_fn( _handle: &votequorum::Handle, _context: u64, quorate: bool, member_list: Vec, ) { println!("TEST votequorum_quorum_fn called. quorate = {quorate}"); println!(" members: {member_list:?}"); } fn nodelist_fn( _handle: &votequorum::Handle, _context: u64, ring_id: votequorum::RingId, member_list: Vec, ) { println!( "TEST nodelist_fn called for {}/{}", ring_id.nodeid, ring_id.seq ); println!(" members: {member_list:?}"); } fn expectedvotes_fn(_handle: &votequorum::Handle, _context: u64, expected_votes: u32) { println!("TEST expected_votes_fn called: value is {expected_votes}"); } fn main() { // Initialise the model data let cb = votequorum::Callbacks { quorum_notification_fn: Some(quorum_fn), nodelist_notification_fn: Some(nodelist_fn), expectedvotes_notification_fn: Some(expectedvotes_fn), }; let handle = match votequorum::initialize(&cb) { Ok(h) => { println!("Votequorum initialized."); h } Err(e) => { println!("Error in VOTEQUORUM init: {e}"); return; } }; // Test context APIs let set_context: u64 = 0xabcdbeefcafe; - if let Err(e) = votequorum::context_set(handle, set_context) { + 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) { + match votequorum::context_get(&handle) { Ok(c) => { if c != set_context { println!("Error: context_get() returned {c:x}, context should be {set_context:x}"); } } Err(e) => { println!("Error in VOTEQUORUM context_get: {e}"); } } const QDEVICE_NAME: &str = "RustQdevice"; - if let Err(e) = votequorum::qdevice_register(handle, QDEVICE_NAME) { + 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)) { + match votequorum::get_info(&handle, corosync::NodeId::from(1u32)) { Ok(i) => { println!("Node info for nodeid 1"); println!(" nodeid: {}", i.node_id); println!(" node_state: {:?}", i.node_state); println!(" node_votes: {}", i.node_votes); println!(" node_expected: {}", i.node_expected_votes); println!(" highest_expected: {}", i.highest_expected); println!(" quorum: {}", i.quorum); println!(" flags: {:x}", i.flags); println!(" qdevice_votes: {}", i.qdevice_votes); println!(" qdevice_name: {}", i.qdevice_name); if i.qdevice_name != QDEVICE_NAME { println!( "qdevice names do not match: s/b: \"{}\" is: \"{}\"", QDEVICE_NAME, i.qdevice_name ); } } Err(e) => { println!("Error in VOTEQUORUM get_info: {e} (check nodeid 1 has been online)"); } } - if let Err(e) = votequorum::qdevice_unregister(handle, QDEVICE_NAME) { + 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) { + 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() { + if votequorum::dispatch(&handle, corosync::DispatchFlags::One).is_err() { break; } } println!("ERROR: Corosync quit"); }