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