Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F3686890
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
52 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/include/pacemaker.h b/include/pacemaker.h
index 102d75dcc0..4a10889956 100644
--- a/include/pacemaker.h
+++ b/include/pacemaker.h
@@ -1,202 +1,202 @@
/*
* Copyright 2019 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PACEMAKER__H
# define PACEMAKER__H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef BUILD_PUBLIC_LIBPACEMAKER
/**
* \file
* \brief High Level API
* \ingroup pacemaker
*/
# include <crm/stonith-ng.h>
# include <libxml/tree.h>
/*!
* \brief Perform a STONITH action.
*
* \param[in] st A connection to the STONITH API.
* \param[in] target The node receiving the action.
* \param[in] action The action to perform.
* \param[in] name Who requested the fence action?
* \param[in] timeout How long to wait for the operation to complete (in ms).
* \param[in] tolerance If a successful action for \p target happened within
* this many ms, return 0 without performing the action
* again.
*
* \return Standard Pacemaker return code
*/
int pcmk_fence_action(stonith_t *st, const char *target, const char *action,
const char *name, unsigned int timeout, unsigned int tolerance);
/*!
* \brief List the fencing operations that have occurred for a specific node.
*
* \note If \p xml is not NULL, it will be freed first and the previous
* contents lost.
*
* \param[in,out] xml The destination for the result, as an XML tree.
* \param[in] st A connection to the STONITH API.
* \param[in] target The node to get history for.
* \param[in] timeout How long to wait for the operation to complete (in ms).
* \param[in] quiet Suppress most output.
* \param[in] verbose Include additional output.
* \param[in] broadcast Gather fencing history from all nodes.
* \param[in] cleanup Clean up fencing history after listing.
*
* \return Standard Pacemaker return code
*/
int pcmk_fence_history(xmlNodePtr *xml, stonith_t *st, char *target,
unsigned int timeout, bool quiet, int verbose,
bool broadcast, bool cleanup);
/*!
* \brief List all installed STONITH agents.
*
* \note If \p xml is not NULL, it will be freed first and the previous
* contents lost.
*
* \param[in,out] xml The destination for the result, as an XML tree.
* \param[in] st A connection to the STONITH API.
* \param[in] timeout How long to wait for the operation to complete (in ms).
*
* \return Standard Pacemaker return code
*/
int pcmk_fence_installed(xmlNodePtr *xml, stonith_t *st, unsigned int timeout);
/*!
* \brief When was a device last fenced?
*
* \note If \p xml is not NULL, it will be freed first and the previous
* contents lost.
*
* \param[in,out] xml The destination for the result, as an XML tree.
* \param[in] target The node that was fenced.
* \param[in] as_nodeid
*
* \return Standard Pacemaker return code
*/
int pcmk_fence_last(xmlNodePtr *xml, const char *target, bool as_nodeid);
/*!
* \brief List nodes that can be fenced.
*
* \note If \p xml is not NULL, it will be freed first and the previous
* contents lost.
*
- * \param[in,out] xml The destination for the result, as an XML tree.
- * \param[in] st A connection to the STONITH API.
- * \param[in] agent The agent that can do the fencing.
- * \param[in] timeout How long to wait for the operation to complete (in ms).
+ * \param[in,out] xml The destination for the result, as an XML tree
+ * \param[in] st A connection to the STONITH API
+ * \param[in] device_id Resource ID of fence device to check
+ * \param[in] timeout How long to wait for the operation to complete (in ms)
*
* \return Standard Pacemaker return code
*/
-int pcmk_fence_list_targets(xmlNodePtr *xml, stonith_t *st, char *agent,
- unsigned int timeout);
+int pcmk_fence_list_targets(xmlNodePtr *xml, stonith_t *st,
+ const char *device_id, unsigned int timeout);
/*!
* \brief Get metadata for a resource.
*
* \note If \p xml is not NULL, it will be freed first and the previous
* contents lost.
*
* \param[in,out] xml The destination for the result, as an XML tree.
* \param[in] st A connection to the STONITH API.
* \param[in] agent The fence agent to get metadata for.
* \param[in] timeout How long to wait for the operation to complete (in ms).
*
* \return Standard Pacemaker return code
*/
int pcmk_fence_metadata(xmlNodePtr *xml, stonith_t *st, char *agent,
unsigned int timeout);
/*!
* \brief List registered fence devices.
*
* \note If \p xml is not NULL, it will be freed first and the previous
* contents lost.
*
* \param[in,out] xml The destination for the result, as an XML tree.
* \param[in] st A connection to the STONITH API.
* \param[in] target If not NULL, only return devices that can fence
* this node.
* \param[in] timeout How long to wait for the operation to complete (in ms).
*
* \return Standard Pacemaker return code
*/
int pcmk_fence_registered(xmlNodePtr *xml, stonith_t *st, char *target,
unsigned int timeout);
/*!
* \brief Register a fencing level for a specific node, node regex, or attribute.
*
* \p target can take three different forms:
* - name=value, in which case \p target is an attribute.
* - @pattern, in which case \p target is a node regex.
* - Otherwise, \p target is a node name.
*
* \param[in] st A connection to the STONITH API.
* \param[in] target The object to register a fencing level for.
* \param[in] fence_level Index number of level to add.
* \param[in] devices Devices to use in level.
*
* \return Standard Pacemaker return code
*/
int pcmk_fence_register_level(stonith_t *st, char *target, int fence_level,
stonith_key_value_t *devices);
/*!
* \brief Unregister a fencing level for a specific node, node regex, or attribute.
*
* \p target can take three different forms:
* - name=value, in which case \p target is an attribute.
* - @pattern, in which case \p target is a node regex.
* - Otherwise, \p target is a node name.
*
* \param[in] st A connection to the STONITH API.
* \param[in] target The object to unregister a fencing level for.
* \param[in] fence_level Index number of level to remove.
*
* \return Standard Pacemaker return code
*/
int pcmk_fence_unregister_level(stonith_t *st, char *target, int fence_level);
/*!
* \brief Validate a STONITH device configuration.
*
* \note If \p xml is not NULL, it will be freed first and the previous
* contents lost.
*
* \param[in,out] xml The destination for the result, as an XML tree.
* \param[in] st A connection to the STONITH API.
* \param[in] agent The agent to validate (for example, "fence_xvm").
* \param[in] id STONITH device ID (may be NULL).
* \param[in] params STONITH device configuration parameters.
* \param[in] timeout How long to wait for the operation to complete (in ms).
*
* \return Standard Pacemaker return code
*/
int pcmk_fence_validate(xmlNodePtr *xml, stonith_t *st, const char *agent,
const char *id, stonith_key_value_t *params,
unsigned int timeout);
#endif
#ifdef __cplusplus
}
#endif
#endif
diff --git a/include/pcmki/pcmki_fence.h b/include/pcmki/pcmki_fence.h
index 94a174d379..1e5e934af0 100644
--- a/include/pcmki/pcmki_fence.h
+++ b/include/pcmki/pcmki_fence.h
@@ -1,223 +1,223 @@
/*
* Copyright 2019 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMKI_STONITH_H
# define PCMKI_STONITH_H
# include <crm/common/output.h>
# include <crm/stonith-ng.h>
/*!
* \brief Perform a STONITH action.
*
* \note This is the internal version of pcmk_fence_action(). External users
* of the pacemaker API should use that function instead.
*
* \param[in] st A connection to the STONITH API.
* \param[in] target The node receiving the action.
* \param[in] action The action to perform.
* \param[in] name Who requested the fence action?
* \param[in] timeout How long to wait for the operation to complete (in ms).
* \param[in] tolerance If a successful action for \p target happened within
* this many ms, return 0 without performing the action
* again.
* \param[in] delay Apply a fencing delay. Value -1 means disable also any
* static/random fencing delays from pcmk_delay_base/max
*
* \return Standard Pacemaker return code
*/
int pcmk__fence_action(stonith_t *st, const char *target, const char *action,
const char *name, unsigned int timeout, unsigned int tolerance,
int delay);
/*!
* \brief List the fencing operations that have occurred for a specific node.
*
* \note This is the internal version of pcmk_fence_history(). External users
* of the pacemaker API should use that function instead.
*
* \note \p out should be initialized with pcmk__output_new() before calling this
* function and destroyed with out->finish and pcmk__output_free() before
* reusing it with any other functions in this library.
*
* \param[in,out] out The output functions structure.
* \param[in] st A connection to the STONITH API.
* \param[in] target The node to get history for.
* \param[in] timeout How long to wait for the operation to complete (in ms).
* \param[in] quiet Suppress most output.
* \param[in] verbose Include additional output.
* \param[in] broadcast Gather fencing history from all nodes.
* \param[in] cleanup Clean up fencing history after listing.
*
* \return Standard Pacemaker return code
*/
int pcmk__fence_history(pcmk__output_t *out, stonith_t *st, char *target,
unsigned int timeout, bool quiet, int verbose,
bool broadcast, bool cleanup);
/**
* \brief List all installed STONITH agents.
*
* \note This is the internal version of pcmk_fence_installed(). External users
* of the pacemaker API should use that function instead.
*
* \note \p out should be initialized with pcmk__output_new() before calling this
* function and destroyed with out->finish and pcmk__output_free() before
* reusing it with any other functions in this library.
*
* \param[in,out] out The output functions structure.
* \param[in] st A connection to the STONITH API.
* \param[in] timeout How long to wait for the operation to complete (in ms).
*
* \return Standard Pacemaker return code
*/
int pcmk__fence_installed(pcmk__output_t *out, stonith_t *st, unsigned int timeout);
/*!
* \brief When was a device last fenced?
*
* \note This is the internal version of pcmk_fence_last(). External users
* of the pacemaker API should use that function instead.
*
* \note \p out should be initialized with pcmk__output_new() before calling this
* function and destroyed with out->finish and pcmk__output_free() before
* reusing it with any other functions in this library.
*
* \param[in,out] out The output functions structure.
* \param[in] target The node that was fenced.
* \param[in] as_nodeid
*
* \return Standard Pacemaker return code
*/
int pcmk__fence_last(pcmk__output_t *out, const char *target, bool as_nodeid);
/*!
* \brief List nodes that can be fenced.
*
* \note This is the internal version of pcmk_fence_list_targets(). External users
* of the pacemaker API should use that function instead.
*
* \note \p out should be initialized with pcmk__output_new() before calling this
* function and destroyed with out->finish and pcmk__output_free() before
* reusing it with any other functions in this library.
*
- * \param[in,out] out The output functions structure.
- * \param[in] st A connection to the STONITH API.
- * \param[in] agent The agent that can do the fencing.
- * \param[in] timeout How long to wait for the operation to complete (in ms).
+ * \param[in,out] out The output functions structure
+ * \param[in] st A connection to the STONITH API
+ * \param[in] device_id Resource ID of fence device to check
+ * \param[in] timeout How long to wait for the operation to complete (in ms)
*
* \return Standard Pacemaker return code
*/
-int pcmk__fence_list_targets(pcmk__output_t *out, stonith_t *st, char *agent,
- unsigned int timeout);
+int pcmk__fence_list_targets(pcmk__output_t *out, stonith_t *st,
+ const char *device_id, unsigned int timeout);
/*!
* \brief Get metadata for a resource.
*
* \note This is the internal version of pcmk_fence_metadata(). External users
* of the pacemaker API should use that function instead.
*
* \note \p out should be initialized with pcmk__output_new() before calling this
* function and destroyed with out->finish and pcmk__output_free() before
* reusing it with any other functions in this library.
*
* \param[in,out] out The output functions structure.
* \param[in] st A connection to the STONITH API.
* \param[in] agent The fence agent to get metadata for.
* \param[in] timeout How long to wait for the operation to complete (in ms).
*
* \return Standard Pacemaker return code
*/
int pcmk__fence_metadata(pcmk__output_t *out, stonith_t *st, char *agent,
unsigned int timeout);
/*!
* \brief List registered fence devices.
*
* \note This is the internal version of pcmk_fence_metadata(). External users
* of the pacemaker API should use that function instead.
*
* \note \p out should be initialized with pcmk__output_new() before calling this
* function and destroyed with out->finish and pcmk__output_free() before
* reusing it with any other functions in this library.
*
* \param[in,out] out The output functions structure.
* \param[in] st A connection to the STONITH API.
* \param[in] target If not NULL, only return devices that can fence
* this node.
* \param[in] timeout How long to wait for the operation to complete (in ms).
*
* \return Standard Pacemaker return code
*/
int pcmk__fence_registered(pcmk__output_t *out, stonith_t *st, char *target,
unsigned int timeout);
/*!
* \brief Register a fencing level for a specific node, node regex, or attribute.
*
* \note This is the internal version of pcmk_fence_register_level(). External users
* of the pacemaker API should use that function instead.
*
* \p target can take three different forms:
* - name=value, in which case \p target is an attribute.
* - @pattern, in which case \p target is a node regex.
* - Otherwise, \p target is a node name.
*
* \param[in] st A connection to the STONITH API.
* \param[in] target The object to register a fencing level for.
* \param[in] fence_level Index number of level to add.
* \param[in] devices Devices to use in level.
*
* \return Standard Pacemaker return code
*/
int pcmk__fence_register_level(stonith_t *st, char *target, int fence_level,
stonith_key_value_t *devices);
/*!
* \brief Unregister a fencing level for a specific node, node regex, or attribute.
*
* \note This is the internal version of pcmk_fence_unregister_level(). External users
* of the pacemaker API should use that function instead.
*
* \p target can take three different forms:
* - name=value, in which case \p target is an attribute.
* - @pattern, in which case \p target is a node regex.
* - Otherwise, \p target is a node name.
*
* \param[in] st A connection to the STONITH API.
* \param[in] target The object to unregister a fencing level for.
* \param[in] fence_level Index number of level to remove.
*
* \return Standard Pacemaker return code
*/
int pcmk__fence_unregister_level(stonith_t *st, char *target, int fence_level);
/**
* \brief Validate a STONITH device configuration.
*
* \note This is the internal version of pcmk_stonith_validate(). External users
* of the pacemaker API should use that function instead.
*
* \note \p out should be initialized with pcmk__output_new() before calling this
* function and destroyed with out->finish and pcmk__output_free() before
* reusing it with any other functions in this library.
*
* \param[in,out] out The output functions structure.
* \param[in] st A connection to the STONITH API.
* \param[in] agent The agent to validate (for example, "fence_xvm").
* \param[in] id STONITH device ID (may be NULL).
* \param[in] params STONITH device configuration parameters.
* \param[in] timeout How long to wait for the operation to complete (in ms).
*
* \return Standard Pacemaker return code
*/
int pcmk__fence_validate(pcmk__output_t *out, stonith_t *st, const char *agent,
const char *id, stonith_key_value_t *params,
unsigned int timeout);
#endif
diff --git a/lib/pacemaker/pcmk_fence.c b/lib/pacemaker/pcmk_fence.c
index 2f610a3b22..a20d39d14b 100644
--- a/lib/pacemaker/pcmk_fence.c
+++ b/lib/pacemaker/pcmk_fence.c
@@ -1,499 +1,499 @@
/*
* Copyright 2009-2020 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/output.h>
#include <crm/common/results.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <glib.h>
#include <libxml/tree.h>
#include <pacemaker.h>
#include <pcmki/pcmki_output.h>
#include <pcmki/pcmki_fence.h>
static int st_opts = st_opt_sync_call | st_opt_allow_suicide;
static GMainLoop *mainloop = NULL;
static struct {
stonith_t *st;
const char *target;
const char *action;
char *name;
unsigned int timeout;
unsigned int tolerance;
int delay;
int rc;
} async_fence_data;
static int
handle_level(stonith_t *st, char *target, int fence_level,
stonith_key_value_t *devices, bool added) {
char *node = NULL;
char *pattern = NULL;
char *name = NULL;
char *value = NULL;
int rc = pcmk_rc_ok;
if (target == NULL) {
// Not really possible, but makes static analysis happy
return EINVAL;
}
/* Determine if targeting by attribute, node name pattern or node name */
value = strchr(target, '=');
if (value != NULL) {
name = target;
*value++ = '\0';
} else if (*target == '@') {
pattern = target + 1;
} else {
node = target;
}
/* Register or unregister level as appropriate */
if (added) {
rc = st->cmds->register_level_full(st, st_opts, node, pattern,
name, value, fence_level,
devices);
} else {
rc = st->cmds->remove_level_full(st, st_opts, node, pattern,
name, value, fence_level);
}
return pcmk_legacy2rc(rc);
}
static void
notify_callback(stonith_t * st, stonith_event_t * e)
{
if (e->result != pcmk_ok) {
return;
}
if (safe_str_eq(async_fence_data.target, e->target) &&
safe_str_eq(async_fence_data.action, e->action)) {
async_fence_data.rc = e->result;
g_main_loop_quit(mainloop);
}
}
static void
fence_callback(stonith_t * stonith, stonith_callback_data_t * data)
{
async_fence_data.rc = data->rc;
g_main_loop_quit(mainloop);
}
static gboolean
async_fence_helper(gpointer user_data)
{
stonith_t *st = async_fence_data.st;
int call_id = 0;
int rc = stonith_api_connect_retry(st, async_fence_data.name, 10);
if (rc != pcmk_ok) {
fprintf(stderr, "Could not connect to fencer: %s\n", pcmk_strerror(rc));
g_main_loop_quit(mainloop);
return TRUE;
}
st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, notify_callback);
call_id = st->cmds->fence_with_delay(st,
st_opt_allow_suicide,
async_fence_data.target,
async_fence_data.action,
async_fence_data.timeout/1000,
async_fence_data.tolerance/1000,
async_fence_data.delay);
if (call_id < 0) {
g_main_loop_quit(mainloop);
return TRUE;
}
st->cmds->register_callback(st,
call_id,
async_fence_data.timeout/1000,
st_opt_timeout_updates, NULL, "callback", fence_callback);
return TRUE;
}
int
pcmk__fence_action(stonith_t *st, const char *target, const char *action,
const char *name, unsigned int timeout, unsigned int tolerance,
int delay)
{
crm_trigger_t *trig;
async_fence_data.st = st;
async_fence_data.name = strdup(name);
async_fence_data.target = target;
async_fence_data.action = action;
async_fence_data.timeout = timeout;
async_fence_data.tolerance = tolerance;
async_fence_data.delay = delay;
async_fence_data.rc = pcmk_err_generic;
trig = mainloop_add_trigger(G_PRIORITY_HIGH, async_fence_helper, NULL);
mainloop_set_trigger(trig);
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
free(async_fence_data.name);
return pcmk_legacy2rc(async_fence_data.rc);
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
pcmk_fence_action(stonith_t *st, const char *target, const char *action,
const char *name, unsigned int timeout, unsigned int tolerance,
int delay)
{
return pcmk__fence_action(st, target, action, name, timeout, tolerance, delay);
}
#endif
int
pcmk__fence_history(pcmk__output_t *out, stonith_t *st, char *target,
unsigned int timeout, bool quiet, int verbose,
bool broadcast, bool cleanup) {
stonith_history_t *history = NULL, *hp, *latest = NULL;
int rc = pcmk_rc_ok;
if (!quiet) {
if (cleanup) {
out->info(out, "cleaning up fencing-history%s%s",
target ? " for node " : "", target ? target : "");
}
if (broadcast) {
out->info(out, "gather fencing-history from all nodes");
}
}
rc = st->cmds->history(st, st_opts | (cleanup ? st_opt_cleanup : 0) |
(broadcast ? st_opt_broadcast : 0),
safe_str_eq(target, "*") ? NULL : target,
&history, timeout/1000);
if (cleanup) {
// Cleanup doesn't return a history list
stonith_history_free(history);
return pcmk_legacy2rc(rc);
}
out->begin_list(out, "event", "events", "Fencing history");
history = stonith__sort_history(history);
for (hp = history; hp; hp = hp->next) {
if (hp->state == st_done) {
latest = hp;
}
if (quiet || !verbose) {
continue;
}
out->message(out, "stonith-event", hp, 1, stonith__later_succeeded(hp, history));
out->increment_list(out);
}
if (latest) {
if (quiet && out->supports_quiet) {
out->info(out, "%lld", (long long) latest->completed);
} else if (!verbose) { // already printed if verbose
out->message(out, "stonith-event", latest, 0, FALSE);
out->increment_list(out);
}
}
out->end_list(out);
stonith_history_free(history);
return pcmk_legacy2rc(rc);
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
pcmk_fence_history(xmlNodePtr *xml, stonith_t *st, char *target, unsigned int timeout,
bool quiet, int verbose, bool broadcast, bool cleanup) {
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__out_prologue(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_history(out, st, target, timeout, quiet, verbose,
broadcast, cleanup);
pcmk__out_epilogue(out, xml, rc);
return rc;
}
#endif
int
pcmk__fence_installed(pcmk__output_t *out, stonith_t *st, unsigned int timeout) {
stonith_key_value_t *devices = NULL;
int rc = pcmk_rc_ok;
rc = st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices, timeout/1000);
/* list_agents returns a negative error code or a positive number of agents. */
if (rc < 0) {
return pcmk_legacy2rc(rc);
}
out->begin_list(out, "fence device", "fence devices", "Installed fence devices");
for (stonith_key_value_t *dIter = devices; dIter; dIter = dIter->next) {
out->list_item(out, "device", "%s", dIter->value);
}
out->end_list(out);
stonith_key_value_freeall(devices, 1, 1);
return pcmk_rc_ok;
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
pcmk_fence_installed(xmlNodePtr *xml, stonith_t *st, unsigned int timeout) {
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__out_prologue(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_installed(out, st, timeout);
pcmk__out_epilogue(out, xml, rc);
return rc;
}
#endif
int
pcmk__fence_last(pcmk__output_t *out, const char *target, bool as_nodeid) {
time_t when = 0;
if (target == NULL) {
return pcmk_rc_ok;
}
if (as_nodeid) {
when = stonith_api_time(atol(target), NULL, FALSE);
} else {
when = stonith_api_time(0, target, FALSE);
}
return out->message(out, "last-fenced", target, when);
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
pcmk_fence_last(xmlNodePtr *xml, const char *target, bool as_nodeid) {
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__out_prologue(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_last(out, target, as_nodeid);
pcmk__out_epilogue(out, xml, rc);
return rc;
}
#endif
int
-pcmk__fence_list_targets(pcmk__output_t *out, stonith_t *st, char *agent,
- unsigned int timeout) {
+pcmk__fence_list_targets(pcmk__output_t *out, stonith_t *st,
+ const char *device_id, unsigned int timeout) {
GList *targets = NULL;
char *lists = NULL;
int rc = pcmk_rc_ok;
- rc = st->cmds->list(st, st_opts, agent, &lists, timeout/1000);
+ rc = st->cmds->list(st, st_opts, device_id, &lists, timeout/1000);
if (rc != pcmk_rc_ok) {
return pcmk_legacy2rc(rc);
}
targets = stonith__parse_targets(lists);
out->begin_list(out, "fence target", "fence targets", "Fence Targets");
while (targets != NULL) {
out->list_item(out, NULL, "%s", (const char *) targets->data);
targets = targets->next;
}
out->end_list(out);
free(lists);
return rc;
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
-pcmk_fence_list_targets(xmlNodePtr *xml, stonith_t *st, char *agent,
+pcmk_fence_list_targets(xmlNodePtr *xml, stonith_t *st, const char *device_id,
unsigned int timeout) {
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__out_prologue(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
- rc = pcmk__fence_list_targets(out, st, agent, timeout);
+ rc = pcmk__fence_list_targets(out, st, device_id, timeout);
pcmk__out_epilogue(out, xml, rc);
return rc;
}
#endif
int
pcmk__fence_metadata(pcmk__output_t *out, stonith_t *st, char *agent,
unsigned int timeout) {
char *buffer = NULL;
int rc = st->cmds->metadata(st, st_opt_sync_call, agent, NULL, &buffer,
timeout/1000);
if (rc != pcmk_rc_ok) {
return pcmk_legacy2rc(rc);
}
out->output_xml(out, "metadata", buffer);
free(buffer);
return rc;
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
pcmk_fence_metadata(xmlNodePtr *xml, stonith_t *st, char *agent,
unsigned int timeout) {
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__out_prologue(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_metadata(out, st, agent, timeout);
pcmk__out_epilogue(out, xml, rc);
return rc;
}
#endif
int
pcmk__fence_registered(pcmk__output_t *out, stonith_t *st, char *target,
unsigned int timeout) {
stonith_key_value_t *devices = NULL;
int rc = pcmk_rc_ok;
rc = st->cmds->query(st, st_opts, target, &devices, timeout/1000);
/* query returns a negative error code or a positive number of results. */
if (rc < 0) {
return pcmk_legacy2rc(rc);
}
out->begin_list(out, "fence device", "fence devices", "Registered fence devices");
for (stonith_key_value_t *dIter = devices; dIter; dIter = dIter->next) {
out->list_item(out, "device", "%s", dIter->value);
}
out->end_list(out);
stonith_key_value_freeall(devices, 1, 1);
/* Return pcmk_rc_ok here, not the number of results. Callers probably
* don't care.
*/
return pcmk_rc_ok;
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
pcmk_fence_registered(xmlNodePtr *xml, stonith_t *st, char *target,
unsigned int timeout) {
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__out_prologue(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_registered(out, st, target, timeout);
pcmk__out_epilogue(out, xml, rc);
return rc;
}
#endif
int
pcmk__fence_register_level(stonith_t *st, char *target, int fence_level,
stonith_key_value_t *devices) {
return handle_level(st, target, fence_level, devices, true);
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
pcmk_fence_register_level(stonith_t *st, char *target, int fence_level,
stonith_key_value_t *devices) {
return pcmk__fence_register_level(st, target, fence_level, devices);
}
#endif
int
pcmk__fence_unregister_level(stonith_t *st, char *target, int fence_level) {
return handle_level(st, target, fence_level, NULL, false);
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
pcmk_fence_unregister_level(stonith_t *st, char *target, int fence_level) {
return pcmk__fence_unregister_level(st, target, fence_level);
}
#endif
int
pcmk__fence_validate(pcmk__output_t *out, stonith_t *st, const char *agent,
const char *id, stonith_key_value_t *params,
unsigned int timeout) {
char *output = NULL;
char *error_output = NULL;
int rc;
rc = st->cmds->validate(st, st_opt_sync_call, id, NULL, agent, params,
timeout/1000, &output, &error_output);
out->message(out, "validate", agent, id, output, error_output, rc);
return pcmk_legacy2rc(rc);
}
#ifdef BUILD_PUBLIC_LIBPACEMAKER
int
pcmk_fence_validate(xmlNodePtr *xml, stonith_t *st, const char *agent,
const char *id, stonith_key_value_t *params,
unsigned int timeout) {
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__out_prologue(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_validate(out, st, agent, id, params, timeout);
pcmk__out_epilogue(out, xml, rc);
return rc;
}
#endif
diff --git a/tools/stonith_admin.c b/tools/stonith_admin.c
index 1273a57819..34dc82bfb7 100644
--- a/tools/stonith_admin.c
+++ b/tools/stonith_admin.c
@@ -1,631 +1,631 @@
/*
* Copyright 2009-2020 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/cluster/internal.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/output.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <crm/cib.h>
#include <crm/pengine/status.h>
#include <crm/common/xml.h>
#include <pacemaker-internal.h>
#define SUMMARY "stonith_admin - Access the Pacemaker fencing API"
char action = 0;
struct {
gboolean as_nodeid;
gboolean broadcast;
gboolean cleanup;
gboolean installed;
gboolean metadata;
gboolean registered;
gboolean validate_cfg;
stonith_key_value_t *devices;
stonith_key_value_t *params;
int fence_level;
int timeout ;
int tolerance;
int delay;
char *agent;
char *confirm_host;
char *fence_host;
char *history;
char *last_fenced;
char *query;
char *reboot_host;
char *register_dev;
char *register_level;
char *targets;
char *terminate;
char *unfence_host;
char *unregister_dev;
char *unregister_level;
} options = {
.timeout = 120,
.delay = 0
};
gboolean add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
#define INDENT " "
/* *INDENT-OFF* */
static GOptionEntry defn_entries[] = {
{ "register", 'R', 0, G_OPTION_ARG_STRING, &options.register_dev,
"Register the named stonith device. Requires: --agent.\n"
INDENT "Optional: --option, --env-option.",
"DEVICE" },
{ "deregister", 'D', 0, G_OPTION_ARG_STRING, &options.unregister_dev,
"De-register the named stonith device.",
"DEVICE" },
{ "register-level", 'r', 0, G_OPTION_ARG_STRING, &options.register_level,
"Register a stonith level for the named target,\n"
INDENT "specified as one of NAME, @PATTERN, or ATTR=VALUE.\n"
INDENT "Requires: --index and one or more --device entries.",
"TARGET" },
{ "deregister-level", 'd', 0, G_OPTION_ARG_STRING, &options.unregister_level,
"Unregister a stonith level for the named target,\n"
INDENT "specified as for --register-level. Requires: --index",
"TARGET" },
{ NULL }
};
static GOptionEntry query_entries[] = {
{ "list", 'l', 0, G_OPTION_ARG_STRING, &options.terminate,
"List devices that can terminate the specified host.\n"
INDENT "Optional: --timeout",
"HOST" },
{ "list-registered", 'L', 0, G_OPTION_ARG_NONE, &options.registered,
"List all registered devices. Optional: --timeout.",
NULL },
{ "list-installed", 'I', 0, G_OPTION_ARG_NONE, &options.installed,
"List all installed devices. Optional: --timeout.",
NULL },
{ "list-targets", 's', 0, G_OPTION_ARG_STRING, &options.targets,
"List the targets that can be fenced by the\n"
INDENT "named device. Optional: --timeout.",
"DEVICE" },
{ "metadata", 'M', 0, G_OPTION_ARG_NONE, &options.metadata,
"Show agent metadata. Requires: --agent.\n"
INDENT "Optional: --timeout.",
NULL },
{ "query", 'Q', 0, G_OPTION_ARG_STRING, &options.query,
"Check the named device's status. Optional: --timeout.",
"DEVICE" },
{ "history", 'H', 0, G_OPTION_ARG_STRING, &options.history,
"Show last successful fencing operation for named node\n"
INDENT "(or '*' for all nodes). Optional: --timeout, --cleanup,\n"
INDENT "--quiet (show only the operation's epoch timestamp),\n"
INDENT "--verbose (show all recorded and pending operations),\n"
INDENT "--broadcast (update history from all nodes available).",
"NODE" },
{ "last", 'h', 0, G_OPTION_ARG_STRING, &options.last_fenced,
"Indicate when the named node was last fenced.\n"
INDENT "Optional: --as-node-id.",
"NODE" },
{ "validate", 'K', 0, G_OPTION_ARG_NONE, &options.validate_cfg,
"Validate a fence device configuration.\n"
INDENT "Requires: --agent. Optional: --option, --env-option,\n"
INDENT "--quiet (print no output, only return status).",
NULL },
{ NULL }
};
static GOptionEntry fence_entries[] = {
{ "fence", 'F', 0, G_OPTION_ARG_STRING, &options.fence_host,
"Fence named host. Optional: --timeout, --tolerance, --delay.",
"HOST" },
{ "unfence", 'U', 0, G_OPTION_ARG_STRING, &options.unfence_host,
"Unfence named host. Optional: --timeout, --tolerance, --delay.",
"HOST" },
{ "reboot", 'B', 0, G_OPTION_ARG_STRING, &options.reboot_host,
"Reboot named host. Optional: --timeout, --tolerance, --delay.",
"HOST" },
{ "confirm", 'C', 0, G_OPTION_ARG_STRING, &options.confirm_host,
"Tell cluster that named host is now safely down.",
"HOST", },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "cleanup", 'c', 0, G_OPTION_ARG_NONE, &options.cleanup,
"Cleanup wherever appropriate. Requires --history.",
NULL },
{ "broadcast", 'b', 0, G_OPTION_ARG_NONE, &options.broadcast,
"Broadcast wherever appropriate.",
NULL },
{ "agent", 'a', 0, G_OPTION_ARG_STRING, &options.agent,
"The agent to use (for example, fence_xvm;\n"
INDENT "with --register, --metadata, --validate).",
"AGENT" },
{ "option", 'o', 0, G_OPTION_ARG_CALLBACK, add_stonith_params,
"Specify a device configuration parameter as NAME=VALUE\n"
INDENT "(may be specified multiple times; with --register,\n"
INDENT "--validate).",
"PARAM" },
{ "env-option", 'e', 0, G_OPTION_ARG_CALLBACK, add_env_params,
"Specify a device configuration parameter with the\n"
INDENT "specified name, using the value of the\n"
INDENT "environment variable of the same name prefixed with\n"
INDENT "OCF_RESKEY_ (may be specified multiple times;\n"
INDENT "with --register, --validate).",
"PARAM" },
{ "tag", 'T', 0, G_OPTION_ARG_CALLBACK, set_tag,
"Identify fencing operations in logs with the specified\n"
INDENT "tag; useful when multiple entities might invoke\n"
INDENT "stonith_admin (used with most commands).",
"TAG" },
{ "device", 'v', 0, G_OPTION_ARG_CALLBACK, add_stonith_device,
"Device ID (with --register-level, device to associate with\n"
INDENT "a given host and level; may be specified multiple times)"
#if SUPPORT_CIBSECRETS
"\n" INDENT "(with --validate, name to use to load CIB secrets)"
#endif
".",
"DEVICE" },
{ "index", 'i', 0, G_OPTION_ARG_INT, &options.fence_level,
"The stonith level (1-9) (with --register-level,\n"
INDENT "--deregister-level).",
"LEVEL" },
{ "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
"Operation timeout in seconds (default 120;\n"
INDENT "used with most commands).",
"SECONDS" },
{ "delay", 'y', 0, G_OPTION_ARG_INT, &options.delay,
"Apply a fencing delay in seconds. Any static/random delays from\n"
INDENT "pcmk_delay_base/max will be added, otherwise all\n"
INDENT "disabled with the value -1\n"
INDENT "(default 0; with --fence, --reboot, --unfence).",
"SECONDS" },
{ "as-node-id", 'n', 0, G_OPTION_ARG_NONE, &options.as_nodeid,
"(Advanced) The supplied node is the corosync node ID\n"
INDENT "(with --last).",
NULL },
{ "tolerance", 0, 0, G_OPTION_ARG_CALLBACK, add_tolerance,
"(Advanced) Do nothing if an equivalent --fence request\n"
INDENT "succeeded less than this many seconds earlier\n"
INDENT "(with --fence, --unfence, --reboot).",
"SECONDS" },
{ NULL }
};
/* *INDENT-ON* */
static pcmk__supported_format_t formats[] = {
PCMK__SUPPORTED_FORMAT_HTML,
PCMK__SUPPORTED_FORMAT_TEXT,
PCMK__SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
static int st_opts = st_opt_sync_call | st_opt_allow_suicide;
static char *name = NULL;
gboolean
add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
char *key = crm_strdup_printf("OCF_RESKEY_%s", optarg);
const char *env = getenv(key);
gboolean retval = TRUE;
if (env == NULL) {
crm_err("Invalid option: -e %s", optarg);
g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Invalid option: -e %s", optarg);
retval = FALSE;
} else {
crm_info("Got: '%s'='%s'", optarg, env);
options.params = stonith_key_value_add(options.params, optarg, env);
}
free(key);
return retval;
}
gboolean
add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.devices = stonith_key_value_add(options.devices, NULL, optarg);
return TRUE;
}
gboolean
add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.tolerance = crm_get_msec(optarg) / 1000;
return TRUE;
}
gboolean
add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
char *name = NULL;
char *value = NULL;
int rc = 0;
gboolean retval = TRUE;
crm_info("Scanning: -o %s", optarg);
rc = pcmk_scan_nvpair(optarg, &name, &value);
if (rc != 2) {
crm_err("Invalid option: -o %s: %s", optarg, pcmk_strerror(rc));
g_set_error(error, G_OPTION_ERROR, rc, "Invalid option: -o %s: %s", optarg, pcmk_strerror(rc));
retval = FALSE;
} else {
crm_info("Got: '%s'='%s'", name, value);
options.params = stonith_key_value_add(options.params, name, value);
}
free(name);
free(value);
return retval;
}
gboolean
set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
free(name);
name = crm_strdup_printf("%s.%s", crm_system_name, optarg);
return TRUE;
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
GOptionContext *context = NULL;
GOptionEntry extra_prog_entries[] = {
{ "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet),
"Be less descriptive in output.",
NULL },
{ NULL }
};
context = pcmk__build_arg_context(args, "text (default), html, xml", group, NULL);
/* Add the -q option, which cannot be part of the globally supported options
* because some tools use that flag for something else.
*/
pcmk__add_main_args(context, extra_prog_entries);
pcmk__add_arg_group(context, "definition", "Device Definition Commands:",
"Show device definition help", defn_entries);
pcmk__add_arg_group(context, "queries", "Queries:",
"Show query help", query_entries);
pcmk__add_arg_group(context, "fence", "Fencing Commands:",
"Show fence help", fence_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
int rc = 0;
bool no_connect = false;
bool required_agent = false;
char *target = NULL;
const char *device = NULL;
crm_exit_t exit_code = CRM_EX_OK;
stonith_t *st = NULL;
pcmk__output_t *out = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
GError *error = NULL;
GOptionContext *context = NULL;
GOptionGroup *output_group = NULL;
gchar **processed_args = NULL;
context = build_arg_context(args, &output_group);
pcmk__register_formats(output_group, formats);
crm_log_cli_init("stonith_admin");
name = strdup(crm_system_name);
processed_args = pcmk__cmdline_preproc(argv, "adehilorstvBCDFHQRTU");
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
exit_code = CRM_EX_USAGE;
goto done;
}
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
if (rc != pcmk_rc_ok) {
fprintf(stderr, "Error creating output format %s: %s\n",
args->output_ty, pcmk_rc_str(rc));
exit_code = CRM_EX_ERROR;
goto done;
}
stonith__register_messages(out);
if (args->version) {
out->version(out, false);
goto done;
}
if (options.validate_cfg) {
required_agent = true;
no_connect = true;
action = 'K';
}
if (options.installed) {
no_connect = true;
action = 'I';
}
if (options.registered) {
action = 'L';
}
if (options.register_dev != NULL) {
required_agent = true;
action = 'R';
device = options.register_dev;
}
if (options.query != NULL) {
action = 'Q';
device = options.query;
}
if (options.unregister_dev != NULL) {
action = 'D';
device = options.unregister_dev;
}
if (options.targets != NULL) {
action = 's';
device = options.targets;
}
if (options.terminate != NULL) {
action = 'L';
target = options.terminate;
}
if (options.metadata) {
no_connect = true;
required_agent = true;
action = 'M';
}
if (options.reboot_host != NULL) {
no_connect = true;
action = 'B';
target = options.reboot_host;
crm_log_args(argc, argv);
}
if (options.fence_host != NULL) {
no_connect = true;
action = 'F';
target = options.fence_host;
crm_log_args(argc, argv);
}
if (options.unfence_host != NULL) {
no_connect = true;
action = 'U';
target = options.unfence_host;
crm_log_args(argc, argv);
}
if (options.confirm_host != NULL) {
action = 'C';
target = options.confirm_host;
crm_log_args(argc, argv);
}
if (options.last_fenced != NULL) {
action = 'h';
target = options.last_fenced;
}
if (options.history != NULL) {
action = 'H';
target = options.history;
}
if (options.register_level != NULL) {
action = 'r';
target = options.register_level;
}
if (options.unregister_level != NULL) {
action = 'd';
target = options.unregister_level;
}
if (optind > argc || action == 0) {
char *help = g_option_context_get_help(context, TRUE, NULL);
out->err(out, "%s", help);
g_free(help);
exit_code = CRM_EX_USAGE;
goto done;
}
if (required_agent && options.agent == NULL) {
char *help = g_option_context_get_help(context, TRUE, NULL);
out->err(out, "Please specify an agent to query using -a,--agent [value]");
out->err(out, "%s", help);
g_free(help);
exit_code = CRM_EX_USAGE;
goto done;
}
st = stonith_api_new();
if (st == NULL) {
rc = -ENOMEM;
} else if (!no_connect) {
rc = st->cmds->connect(st, name, NULL);
}
if (rc < 0) {
out->err(out, "Could not connect to fencer: %s", pcmk_strerror(rc));
exit_code = CRM_EX_DISCONNECT;
goto done;
}
switch (action) {
case 'I':
rc = pcmk__fence_installed(out, st, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Failed to list installed devices: %s", pcmk_strerror(rc));
}
break;
case 'L':
rc = pcmk__fence_registered(out, st, target, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Failed to list registered devices: %s", pcmk_strerror(rc));
}
break;
case 'Q':
rc = st->cmds->monitor(st, st_opts, device, options.timeout);
if (rc != pcmk_rc_ok) {
rc = st->cmds->list(st, st_opts, device, NULL, options.timeout);
}
rc = pcmk_legacy2rc(rc);
break;
case 's':
- rc = pcmk__fence_list_targets(out, st, target, options.timeout*1000);
+ rc = pcmk__fence_list_targets(out, st, device, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Couldn't list targets: %s", pcmk_strerror(rc));
}
break;
case 'R':
rc = st->cmds->register_device(st, st_opts, device, NULL, options.agent,
options.params);
rc = pcmk_legacy2rc(rc);
break;
case 'D':
rc = st->cmds->remove_device(st, st_opts, device);
rc = pcmk_legacy2rc(rc);
break;
case 'd':
rc = pcmk__fence_unregister_level(st, target, options.fence_level);
break;
case 'r':
rc = pcmk__fence_register_level(st, target, options.fence_level, options.devices);
break;
case 'M':
rc = pcmk__fence_metadata(out, st, options.agent, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Can't get fence agent meta-data: %s", pcmk_strerror(rc));
}
break;
case 'C':
rc = st->cmds->confirm(st, st_opts, target);
rc = pcmk_legacy2rc(rc);
break;
case 'B':
rc = pcmk__fence_action(st, target, "reboot", name, options.timeout*1000,
options.tolerance*1000, options.delay);
break;
case 'F':
rc = pcmk__fence_action(st, target, "off", name, options.timeout*1000,
options.tolerance*1000, options.delay);
break;
case 'U':
rc = pcmk__fence_action(st, target, "on", name, options.timeout*1000,
options.tolerance*1000, options.delay);
break;
case 'h':
rc = pcmk__fence_last(out, target, options.as_nodeid);
break;
case 'H':
rc = pcmk__fence_history(out, st, target, options.timeout*1000, args->quiet,
args->verbosity, options.broadcast, options.cleanup);
break;
case 'K':
device = options.devices ? options.devices->key : NULL;
rc = pcmk__fence_validate(out, st, options.agent, device, options.params,
options.timeout*1000);
break;
}
crm_info("Command returned: %s (%d)", pcmk_rc_str(rc), rc);
exit_code = pcmk_rc2exitc(rc);
done:
g_strfreev(processed_args);
g_clear_error(&error);
pcmk__free_arg_context(context);
if (out != NULL) {
out->finish(out, exit_code, true, NULL);
pcmk__output_free(out);
}
free(name);
stonith_key_value_freeall(options.params, 1, 1);
if (st != NULL) {
st->cmds->disconnect(st);
stonith_api_delete(st);
}
return exit_code;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Apr 21, 6:09 PM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1659345
Default Alt Text
(52 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment