// 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 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.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<()> { let res = unsafe { ffi::corosync_cfg_finalize(handle.cfg_handle) }; if res == ffi::CS_OK { HANDLE_HASH.lock().unwrap().remove(&handle.cfg_handle); Ok(()) } else { Err(CsError::from_c(res)) } } // not sure if an fd is the right thing to return here, but it will do for now. /// Returns a file descriptor to use for poll/select on the CFG handle pub fn fd_get(handle: &Handle) -> Result { let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int; let res = unsafe { ffi::corosync_cfg_fd_get(handle.cfg_handle, c_fd) }; if res == ffi::CS_OK { Ok(c_fd as i32) } else { Err(CsError::from_c(res)) } } /// Get the local [NodeId] pub fn local_get(handle: &Handle) -> Result { let mut nodeid: u32 = 0; let res = unsafe { ffi::corosync_cfg_local_get(handle.cfg_handle, &mut nodeid) }; if res == ffi::CS_OK { Ok(NodeId::from(nodeid)) } else { Err(CsError::from_c(res)) } } /// Reload the cluster configuration on all nodes pub fn reload_cnfig(handle: &Handle) -> Result<()> { let res = unsafe { ffi::corosync_cfg_reload_config(handle.cfg_handle) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } /// Re-open the cluster log files, on this node only pub fn reopen_log_files(handle: &Handle) -> Result<()> { let res = unsafe { ffi::corosync_cfg_reopen_log_files(handle.cfg_handle) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } /// Tell another cluster node to shutdown. reason is a string that /// will be written to the system log files. pub fn kill_node(handle: &Handle, nodeid: NodeId, reason: &str) -> Result<()> { let c_string = { match CString::new(reason) { Ok(cs) => cs, Err(_) => return Err(CsError::CsErrInvalidParam), } }; let res = unsafe { ffi::corosync_cfg_kill_node(handle.cfg_handle, u32::from(nodeid), c_string.as_ptr()) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } /// Ask this cluster node to shutdown. If [ShutdownFlags] is set to Request then ///it may be refused by other applications /// that have registered for shutdown callbacks. pub fn try_shutdown(handle: &Handle, flags: ShutdownFlags) -> Result<()> { let c_flags = match flags { ShutdownFlags::Request => 0, ShutdownFlags::Regardless => 1, ShutdownFlags::Immediate => 2, }; let res = unsafe { ffi::corosync_cfg_try_shutdown(handle.cfg_handle, c_flags) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } /// Reply to a shutdown request with Yes or No [ShutdownReply] pub fn reply_to_shutdown(handle: &Handle, flags: ShutdownReply) -> Result<()> { let c_flags = match flags { ShutdownReply::No => 0, ShutdownReply::Yes => 1, }; let res = unsafe { ffi::corosync_cfg_replyto_shutdown(handle.cfg_handle, c_flags) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } /// Call any/all active CFG callbacks for this [Handle] see [DispatchFlags] for details pub fn dispatch(handle: &Handle, flags: DispatchFlags) -> Result<()> { let res = unsafe { ffi::corosync_cfg_dispatch(handle.cfg_handle, flags as u32) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } // Quick & dirty u8 to boolean fn u8_to_bool(val: u8) -> bool { val != 0 } const CFG_MAX_LINKS: usize = 8; const CFG_MAX_HOST_LEN: usize = 256; fn unpack_nodestatus(c_nodestatus: ffi::corosync_cfg_node_status_v1) -> Result { let mut ns = NodeStatus { version: NodeStatusVersion::V1, nodeid: NodeId::from(c_nodestatus.nodeid), reachable: u8_to_bool(c_nodestatus.reachable), remote: u8_to_bool(c_nodestatus.remote), external: u8_to_bool(c_nodestatus.external), onwire_min: c_nodestatus.onwire_min, onwire_max: c_nodestatus.onwire_max, onwire_ver: c_nodestatus.onwire_min, link_status: Vec::::new(), }; for i in 0..CFG_MAX_LINKS { let ls = LinkStatus { enabled: u8_to_bool(c_nodestatus.link_status[i].enabled), connected: u8_to_bool(c_nodestatus.link_status[i].connected), dynconnected: u8_to_bool(c_nodestatus.link_status[i].dynconnected), mtu: c_nodestatus.link_status[i].mtu, src_ipaddr: string_from_bytes( &c_nodestatus.link_status[i].src_ipaddr[0], CFG_MAX_HOST_LEN, )?, dst_ipaddr: string_from_bytes( &c_nodestatus.link_status[i].dst_ipaddr[0], CFG_MAX_HOST_LEN, )?, }; ns.link_status.push(ls); } Ok(ns) } // Constructor for link status to make c_ndostatus initialization tidier. fn new_ls() -> ffi::corosync_knet_link_status_v1 { ffi::corosync_knet_link_status_v1 { enabled: 0, connected: 0, dynconnected: 0, mtu: 0, src_ipaddr: [0; 256], dst_ipaddr: [0; 256], } } /// Get the extended status of a node in the cluster (including active links) from its [NodeId]. /// Returns a filled in [NodeStatus] struct pub fn node_status_get( handle: &Handle, nodeid: NodeId, _version: NodeStatusVersion, ) -> Result { // Currently only supports V1 struct unsafe { // We need to initialize this even though it's all going to be overwritten. let mut c_nodestatus = ffi::corosync_cfg_node_status_v1 { version: 1, nodeid: 0, reachable: 0, remote: 0, external: 0, onwire_min: 0, onwire_max: 0, onwire_ver: 0, link_status: [new_ls(); 8], }; let res = ffi::corosync_cfg_node_status_get( handle.cfg_handle, u32::from(nodeid), 1, &mut c_nodestatus as *mut _ as *mut c_void, ); if res == ffi::CS_OK { unpack_nodestatus(c_nodestatus) } else { Err(CsError::from_c(res)) } } } /// Start tracking for shutdown notifications pub fn track_start(handle: &Handle, _flags: TrackFlags) -> Result<()> { let res = unsafe { ffi::corosync_cfg_trackstart(handle.cfg_handle, 0) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } } /// Stop tracking for shutdown notifications pub fn track_stop(handle: &Handle) -> Result<()> { let res = unsafe { ffi::corosync_cfg_trackstop(handle.cfg_handle) }; if res == ffi::CS_OK { Ok(()) } else { Err(CsError::from_c(res)) } }