Skip to main content

rust_corosync/
cfg.rs

1// libcfg interface for Rust
2// Copyright (c) 2021 Red Hat, Inc.
3//
4// All rights reserved.
5//
6// Author: Christine Caulfield (ccaulfi@redhat.com)
7//
8
9// For the code generated by bindgen
10use crate::sys::cfg as ffi;
11
12use std::collections::HashMap;
13use std::ffi::CString;
14use std::os::raw::{c_int, c_void};
15use std::sync::Mutex;
16
17use crate::string_from_bytes;
18use crate::{CsError, DispatchFlags, NodeId, Result};
19
20// Used to convert a CFG handle into one of ours
21lazy_static! {
22    static ref HANDLE_HASH: Mutex<HashMap<u64, Handle>> = Mutex::new(HashMap::new());
23}
24
25/// Callback from [track_start]. Will be called if another process
26/// requests to shut down corosync. [reply_to_shutdown] should be called
27/// with a [ShutdownReply] of either Yes or No.
28#[derive(Copy, Clone)]
29pub struct Callbacks {
30    pub corosync_cfg_shutdown_callback_fn: Option<fn(handle: &Handle, flags: u32)>,
31}
32
33/// A handle into the cfg library. returned from [initialize] and needed for all other calls
34pub struct Handle {
35    cfg_handle: u64,
36    callbacks: Callbacks,
37    clone: bool,
38}
39
40impl Clone for Handle {
41    fn clone(&self) -> Handle {
42        Handle {
43            cfg_handle: self.cfg_handle,
44            callbacks: self.callbacks,
45            clone: true,
46        }
47    }
48}
49
50impl Drop for Handle {
51    fn drop(self: &mut Handle) {
52        if !self.clone {
53            let _e = finalize(self);
54        }
55    }
56}
57
58// Clones count as equivalent
59impl PartialEq for Handle {
60    fn eq(&self, other: &Handle) -> bool {
61        self.cfg_handle == other.cfg_handle
62    }
63}
64
65/// Flags for [try_shutdown]
66pub enum ShutdownFlags {
67    /// Request shutdown (other daemons will be consulted)
68    Request,
69    /// Tells other daemons but ignore their opinions
70    Regardless,
71    /// Go down straight away (but still tell other nodes)
72    Immediate,
73}
74
75/// Responses for [reply_to_shutdown]
76pub enum ShutdownReply {
77    Yes = 1,
78    No = 0,
79}
80
81/// Trackflags for [track_start]. None currently supported
82pub enum TrackFlags {
83    None,
84}
85
86/// Version of the [NodeStatus] structure returned from [node_status_get]
87#[derive(Debug, Copy, Clone)]
88pub enum NodeStatusVersion {
89    V1,
90}
91
92/// Status of a link inside [NodeStatus] struct
93#[derive(Debug)]
94pub struct LinkStatus {
95    pub enabled: bool,
96    pub connected: bool,
97    pub dynconnected: bool,
98    pub mtu: u32,
99    pub src_ipaddr: String,
100    pub dst_ipaddr: String,
101}
102
103/// Structure returned from [node_status_get], shows all the details of a node
104/// that is known to corosync, including all configured links
105#[derive(Debug)]
106pub struct NodeStatus {
107    pub version: NodeStatusVersion,
108    pub nodeid: NodeId,
109    pub reachable: bool,
110    pub remote: bool,
111    pub external: bool,
112    pub onwire_min: u8,
113    pub onwire_max: u8,
114    pub onwire_ver: u8,
115    pub link_status: Vec<LinkStatus>,
116}
117
118extern "C" fn rust_shutdown_notification_fn(handle: ffi::corosync_cfg_handle_t, flags: u32) {
119    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&handle) {
120        if let Some(cb) = h.callbacks.corosync_cfg_shutdown_callback_fn {
121            (cb)(h, flags);
122        }
123    }
124}
125
126/// Initialize a connection to the cfg library. You must call this before doing anything
127/// else and use the passed back [Handle].
128/// Remember to free the handle using [finalize] when finished.
129pub fn initialize(callbacks: &Callbacks) -> Result<Handle> {
130    let mut handle: ffi::corosync_cfg_handle_t = 0;
131
132    let c_callbacks = ffi::corosync_cfg_callbacks_t {
133        corosync_cfg_shutdown_callback: Some(rust_shutdown_notification_fn),
134    };
135
136    unsafe {
137        let res = ffi::corosync_cfg_initialize(&mut handle, &c_callbacks);
138        if res == ffi::CS_OK {
139            let rhandle = Handle {
140                cfg_handle: handle,
141                callbacks: *callbacks,
142                clone: false,
143            };
144            HANDLE_HASH.lock().unwrap().insert(handle, rhandle.clone());
145            Ok(rhandle)
146        } else {
147            Err(CsError::from_c(res))
148        }
149    }
150}
151
152/// Finish with a connection to corosync, after calling this the [Handle] is invalid
153pub fn finalize(handle: &Handle) -> Result<()> {
154    let res = unsafe { ffi::corosync_cfg_finalize(handle.cfg_handle) };
155    if res == ffi::CS_OK {
156        HANDLE_HASH.lock().unwrap().remove(&handle.cfg_handle);
157        Ok(())
158    } else {
159        Err(CsError::from_c(res))
160    }
161}
162
163// not sure if an fd is the right thing to return here, but it will do for now.
164/// Returns a file descriptor to use for poll/select on the CFG handle
165pub fn fd_get(handle: &Handle) -> Result<i32> {
166    let c_fd: *mut c_int = &mut 0 as *mut _ as *mut c_int;
167    let res = unsafe { ffi::corosync_cfg_fd_get(handle.cfg_handle, c_fd) };
168    if res == ffi::CS_OK {
169        Ok(unsafe { *c_fd })
170    } else {
171        Err(CsError::from_c(res))
172    }
173}
174
175/// Get the local [NodeId]
176pub fn local_get(handle: &Handle) -> Result<NodeId> {
177    let mut nodeid: u32 = 0;
178    let res = unsafe { ffi::corosync_cfg_local_get(handle.cfg_handle, &mut nodeid) };
179    if res == ffi::CS_OK {
180        Ok(NodeId::from(nodeid))
181    } else {
182        Err(CsError::from_c(res))
183    }
184}
185
186/// Reload the cluster configuration on all nodes
187pub fn reload_cnfig(handle: &Handle) -> Result<()> {
188    let res = unsafe { ffi::corosync_cfg_reload_config(handle.cfg_handle) };
189    if res == ffi::CS_OK {
190        Ok(())
191    } else {
192        Err(CsError::from_c(res))
193    }
194}
195
196/// Re-open the cluster log files, on this node only
197pub fn reopen_log_files(handle: &Handle) -> Result<()> {
198    let res = unsafe { ffi::corosync_cfg_reopen_log_files(handle.cfg_handle) };
199    if res == ffi::CS_OK {
200        Ok(())
201    } else {
202        Err(CsError::from_c(res))
203    }
204}
205
206/// Tell another cluster node to shutdown. reason is a string that
207/// will be written to the system log files.
208pub fn kill_node(handle: &Handle, nodeid: NodeId, reason: &str) -> Result<()> {
209    let c_string = {
210        match CString::new(reason) {
211            Ok(cs) => cs,
212            Err(_) => return Err(CsError::CsErrInvalidParam),
213        }
214    };
215
216    let res = unsafe {
217        ffi::corosync_cfg_kill_node(handle.cfg_handle, u32::from(nodeid), c_string.as_ptr())
218    };
219    if res == ffi::CS_OK {
220        Ok(())
221    } else {
222        Err(CsError::from_c(res))
223    }
224}
225
226/// Ask this cluster node to shutdown. If [ShutdownFlags] is set to Request then
227///it may be refused by other applications
228/// that have registered for shutdown callbacks.
229pub fn try_shutdown(handle: &Handle, flags: ShutdownFlags) -> Result<()> {
230    let c_flags = match flags {
231        ShutdownFlags::Request => 0,
232        ShutdownFlags::Regardless => 1,
233        ShutdownFlags::Immediate => 2,
234    };
235    let res = unsafe { ffi::corosync_cfg_try_shutdown(handle.cfg_handle, c_flags) };
236    if res == ffi::CS_OK {
237        Ok(())
238    } else {
239        Err(CsError::from_c(res))
240    }
241}
242
243/// Reply to a shutdown request with Yes or No [ShutdownReply]
244pub fn reply_to_shutdown(handle: &Handle, flags: ShutdownReply) -> Result<()> {
245    let c_flags = match flags {
246        ShutdownReply::No => 0,
247        ShutdownReply::Yes => 1,
248    };
249    let res = unsafe { ffi::corosync_cfg_replyto_shutdown(handle.cfg_handle, c_flags) };
250    if res == ffi::CS_OK {
251        Ok(())
252    } else {
253        Err(CsError::from_c(res))
254    }
255}
256
257/// Call any/all active CFG callbacks for this [Handle] see [DispatchFlags] for details
258pub fn dispatch(handle: &Handle, flags: DispatchFlags) -> Result<()> {
259    let res = unsafe { ffi::corosync_cfg_dispatch(handle.cfg_handle, flags as u32) };
260    if res == ffi::CS_OK {
261        Ok(())
262    } else {
263        Err(CsError::from_c(res))
264    }
265}
266
267// Quick & dirty u8 to boolean
268fn u8_to_bool(val: u8) -> bool {
269    val != 0
270}
271
272const CFG_MAX_LINKS: usize = 8;
273const CFG_MAX_HOST_LEN: usize = 256;
274fn unpack_nodestatus(c_nodestatus: ffi::corosync_cfg_node_status_v1) -> Result<NodeStatus> {
275    let mut ns = NodeStatus {
276        version: NodeStatusVersion::V1,
277        nodeid: NodeId::from(c_nodestatus.nodeid),
278        reachable: u8_to_bool(c_nodestatus.reachable),
279        remote: u8_to_bool(c_nodestatus.remote),
280        external: u8_to_bool(c_nodestatus.external),
281        onwire_min: c_nodestatus.onwire_min,
282        onwire_max: c_nodestatus.onwire_max,
283        onwire_ver: c_nodestatus.onwire_min,
284        link_status: Vec::<LinkStatus>::new(),
285    };
286    for i in 0..CFG_MAX_LINKS {
287        let ls = LinkStatus {
288            enabled: u8_to_bool(c_nodestatus.link_status[i].enabled),
289            connected: u8_to_bool(c_nodestatus.link_status[i].connected),
290            dynconnected: u8_to_bool(c_nodestatus.link_status[i].dynconnected),
291            mtu: c_nodestatus.link_status[i].mtu,
292            src_ipaddr: string_from_bytes(
293                &c_nodestatus.link_status[i].src_ipaddr[0],
294                CFG_MAX_HOST_LEN,
295            )?,
296            dst_ipaddr: string_from_bytes(
297                &c_nodestatus.link_status[i].dst_ipaddr[0],
298                CFG_MAX_HOST_LEN,
299            )?,
300        };
301        ns.link_status.push(ls);
302    }
303
304    Ok(ns)
305}
306
307// Constructor for link status to make c_ndostatus initialization tidier.
308fn new_ls() -> ffi::corosync_knet_link_status_v1 {
309    ffi::corosync_knet_link_status_v1 {
310        enabled: 0,
311        connected: 0,
312        dynconnected: 0,
313        mtu: 0,
314        src_ipaddr: [0; 256],
315        dst_ipaddr: [0; 256],
316    }
317}
318
319/// Get the extended status of a node in the cluster (including active links) from its [NodeId].
320/// Returns a filled in [NodeStatus] struct
321pub fn node_status_get(
322    handle: &Handle,
323    nodeid: NodeId,
324    _version: NodeStatusVersion,
325) -> Result<NodeStatus> {
326    // Currently only supports V1 struct
327    unsafe {
328        // We need to initialize this even though it's all going to be overwritten.
329        let mut c_nodestatus = ffi::corosync_cfg_node_status_v1 {
330            version: 1,
331            nodeid: 0,
332            reachable: 0,
333            remote: 0,
334            external: 0,
335            onwire_min: 0,
336            onwire_max: 0,
337            onwire_ver: 0,
338            link_status: [new_ls(); 8],
339        };
340
341        let res = ffi::corosync_cfg_node_status_get(
342            handle.cfg_handle,
343            u32::from(nodeid),
344            1,
345            &mut c_nodestatus as *mut _ as *mut c_void,
346        );
347
348        if res == ffi::CS_OK {
349            unpack_nodestatus(c_nodestatus)
350        } else {
351            Err(CsError::from_c(res))
352        }
353    }
354}
355
356/// Start tracking for shutdown notifications
357pub fn track_start(handle: &Handle, _flags: TrackFlags) -> Result<()> {
358    let res = unsafe { ffi::corosync_cfg_trackstart(handle.cfg_handle, 0) };
359    if res == ffi::CS_OK {
360        Ok(())
361    } else {
362        Err(CsError::from_c(res))
363    }
364}
365
366/// Stop tracking for shutdown notifications
367pub fn track_stop(handle: &Handle) -> Result<()> {
368    let res = unsafe { ffi::corosync_cfg_trackstop(handle.cfg_handle) };
369    if res == ffi::CS_OK {
370        Ok(())
371    } else {
372        Err(CsError::from_c(res))
373    }
374}