Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/output.h b/include/crm/common/output.h
index 518794cd58..96ae9658ed 100644
--- a/include/crm/common/output.h
+++ b/include/crm/common/output.h
@@ -1,566 +1,576 @@
/*
* 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 CRM_OUTPUT__H
# define CRM_OUTPUT__H
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* \brief Formatted output for pacemaker tools
*/
# include <stdbool.h>
# include <stdio.h>
# include <libxml/tree.h>
# include <glib.h>
# include <crm/common/results.h>
-# define PCMK__API_VERSION "1.0"
+# define PCMK__API_VERSION "2.0"
typedef struct pcmk__output_s pcmk__output_t;
/*!
* \internal
* \brief The type of a function that creates a ::pcmk__output_t.
*
* Instances of this type are passed to pcmk__register_format(), stored in an
* internal data structure, and later accessed by pcmk__output_new(). For
* examples, see pcmk__mk_xml_output() and pcmk__mk_text_output().
*
* \param[in] argv The list of command line arguments.
*/
typedef pcmk__output_t * (*pcmk__output_factory_t)(char **argv);
/*!
* \internal
* \brief The type of a custom message formatting function.
*
* These functions are defined by various libraries to support formatting of
* types aside from the basic types provided by a ::pcmk__output_t.
*
* The meaning of the return value will be different for each message.
* In general, however, 0 should be returned on success and a positive value
* on error.
*
* \note These functions must not call va_start or va_end - that is done
* automatically before the custom formatting function is called.
*/
typedef int (*pcmk__message_fn_t)(pcmk__output_t *out, va_list args);
/*!
* \internal
* \brief Internal type for tracking custom messages.
*
* Each library can register functions that format custom message types. These
* are commonly used to handle some library-specific type. Registration is
* done by first defining a table of ::pcmk__message_entry_t structures and
* then passing that table to pcmk__register_messages(). Separate handlers
* can be defined for the same message, but for different formats (xml vs.
* text). Unknown formats will be ignored.
*/
typedef struct pcmk__message_entry_s {
/*!
* \brief The message to be handled.
*
* This must be the same ID that is passed to the message function of
* a ::pcmk__output_t. Unknown message IDs will be ignored.
*/
const char *message_id;
/*!
* \brief The format type this handler is for.
*
* This name must match the fmt_name of the currently active formatter in
* order for the registered function to be called. It is valid to have
* multiple entries for the same message_id but with different fmt_name
* values.
*/
const char *fmt_name;
/*!
* \brief The function to be called for message_id given a match on
* fmt_name. See comments on ::pcmk__message_fn_t.
*/
pcmk__message_fn_t fn;
} pcmk__message_entry_t;
/*!
* \internal
* \brief This structure contains everything needed to add support for a
* single output formatter to a command line program.
*/
typedef struct pcmk__supported_format_s {
/*!
* \brief The name of this output formatter, which should match the
* fmt_name parameter in some ::pcmk__output_t structure.
*/
const char *name;
/*!
* \brief A function that creates a ::pcmk__output_t.
*/
pcmk__output_factory_t create;
/*!
* \brief Format-specific command line options. This can be NULL if
* no command line options should be supported.
*/
GOptionEntry *options;
} pcmk__supported_format_t;
/* The following three blocks need to be updated each time a new base formatter
* is added.
*/
extern GOptionEntry pcmk__text_output_entries[];
extern GOptionEntry pcmk__xml_output_entries[];
pcmk__output_t *pcmk__mk_text_output(char **argv);
pcmk__output_t *pcmk__mk_xml_output(char **argv);
#define PCMK__SUPPORTED_FORMAT_TEXT { "text", pcmk__mk_text_output, pcmk__text_output_entries }
#define PCMK__SUPPORTED_FORMAT_XML { "xml", pcmk__mk_xml_output, pcmk__xml_output_entries }
/*!
* \brief This structure contains everything that makes up a single output
* formatter.
*
* Instances of this structure may be created by calling pcmk__output_new()
* with the name of the desired formatter. They should later be freed with
* pcmk__output_free().
*/
struct pcmk__output_s {
/*!
* \brief The name of this output formatter.
*/
char *fmt_name;
/*!
* \brief A copy of the request that generated this output.
*
* In the case of command line usage, this would be the command line
* arguments. For other use cases, it could be different.
*/
char *request;
/*!
* \brief Does this formatter support a special quiet mode?
*
* In this mode, most output can be supressed but some information is still
* displayed to an interactive user. In general, machine-readable output
* formats will not support this while user-oriented formats will.
*/
bool supports_quiet;
/*!
* \brief Where output should be written.
*
* This could be a file handle, or stdout or stderr. This is really only
* useful internally.
*/
FILE *dest;
/*!
* \brief Custom messages that are currently registered on this formatter.
*
* Keys are the string message IDs, values are ::pcmk__message_fn_t function
* pointers.
*/
GHashTable *messages;
/*!
* \brief Implementation-specific private data.
*
* Each individual formatter may have some private data useful in its
* implementation. This points to that data. Callers should not rely on
* its contents or structure.
*/
void *priv;
/*!
* \internal
* \brief Take whatever actions are necessary to prepare out for use. This is
* called by pcmk__output_new(). End users should not need to call this.
*
* \note For formatted output implementers - This function should be written in
* such a way that it can be called repeatedly on an already initialized
* object without causing problems, or on a previously finished object
* without crashing.
*
* \param[in,out] out The output functions structure.
*
* \return true on success, false on error.
*/
bool (*init) (pcmk__output_t *out);
/*!
* \internal
* \brief Free the private formatter-specific data.
*
* This is called from pcmk__output_free() and does not typically need to be
* called directly.
*
* \param[in,out] out The output functions structure.
*/
void (*free_priv)(pcmk__output_t *out);
/*!
* \internal
* \brief Take whatever actions are necessary to end formatted output.
*
* This could include flushing output to a file, but does not include freeing
* anything. Note that pcmk__output_free() will automatically call this
* function, so there is typically no need to do so manually.
*
* \note For formatted output implementers - This function should be written in
* such a way that it can be called repeatedly on a previously finished
* object without crashing.
*
* \param[in,out] out The output functions structure.
* \param[in] exit_status The exit value of the whole program.
*/
void (*finish) (pcmk__output_t *out, crm_exit_t exit_status);
/*!
* \internal
* \brief Finalize output and then immediately set back up to start a new set
* of output.
*
* This is conceptually the same as calling finish and then init, though in
* practice more be happening behind the scenes.
*
* \note This function differs from finish in that no exit_status is added.
* The idea is that the program is not shutting down, so there is not
* yet a final exit code. Call finish on the last time through if this
* is needed.
*
* \param[in,out] out The output functions structure.
*/
void (*reset) (pcmk__output_t *out);
/*!
* \internal
* \brief Register a custom message.
*
* \param[in,out] out The output functions structure.
* \param[in] message_id The name of the message to register. This name
* will be used as the message_id parameter to the
* message function in order to call the custom
* format function.
* \param[in] fn The custom format function to call for message_id.
*/
void (*register_message) (pcmk__output_t *out, const char *message_id,
pcmk__message_fn_t fn);
/*!
* \internal
* \brief Call a previously registered custom message.
*
* \param[in,out] out The output functions structure.
* \param[in] message_id The name of the message to call. This name must
* be the same as the message_id parameter of some
* previous call to register_message.
* \param[in] ... Arguments to be passed to the registered function.
*
* \return 0 if a function was registered for the message, that function was
* called, and returned successfully. A negative value is returned if
* no function was registered. A positive value is returned if the
* function was called but encountered an error.
*/
int (*message) (pcmk__output_t *out, const char *message_id, ...);
/*!
* \internal
* \brief Format the output of a completed subprocess.
*
* \param[in,out] out The output functions structure.
* \param[in] exit_status The exit value of the subprocess.
* \param[in] proc_stdout stdout from the completed subprocess.
* \param[in] proc_stderr stderr from the completed subprocess.
*/
void (*subprocess_output) (pcmk__output_t *out, int exit_status,
const char *proc_stdout, const char *proc_stderr);
+ /*!
+ * \internal
+ * \brief Format version information. This is useful for the --version
+ * argument of command line tools.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] extended Add additional version information.
+ */
+ void (*version) (pcmk__output_t *out, bool extended);
+
/*!
* \internal
* \brief Format an informational message that should be shown to
* to an interactive user. Not all formatters will do this.
*
* \note A newline will automatically be added to the end of the format
* string, so callers should not include a newline.
*
* \param[in,out] out The output functions structure.
* \param[in] buf The message to be printed.
* \param[in] ... Arguments to be formatted.
*/
void (*info) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
/*!
* \internal
* \brief Format an error message that should be shown to an interactive
* user. Not all formatters will do this.
*
* \note A newline will automatically be added to the end of the format
* string, so callers should not include a newline.
*
* \param[in,out] out The output functions structure.
* \param[in] buf The message to be printed.
* \param[in] ... Arguments to be formatted.
*/
void (*err) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
/*!
* \internal
* \brief Format already formatted XML.
*
* \param[in,out] out The output functions structure.
* \param[in] name A name to associate with the XML.
* \param[in] buf The XML in a string.
*/
void (*output_xml) (pcmk__output_t *out, const char *name, const char *buf);
/*!
* \internal
* \brief Start a new list of items.
*
* \note For text output, this corresponds to another level of indentation. For
* XML output, this corresponds to wrapping any following output in another
* layer of tags.
*
* \note If singular_noun and plural_noun are non-NULL, calling end_list will
* result in a summary being added.
*
* \param[in,out] out The output functions structure.
* \param[in] name A descriptive, user-facing name for this list.
* \param[in] singular_noun When outputting the summary for a list with
* one item, the noun to use.
* \param[in] plural_noun When outputting the summary for a list with
* more than one item, the noun to use.
*/
void (*begin_list) (pcmk__output_t *out, const char *name,
const char *singular_noun, const char *plural_noun);
/*!
* \internal
* \brief Format a single item in a list.
*
* \param[in,out] out The output functions structure.
* \param[in] name A name to associate with this item.
* \param[in] content The item to be formatted.
*/
void (*list_item) (pcmk__output_t *out, const char *name, const char *content);
/*!
* \internal
* \brief Conclude a list.
*
* \note If begin_list was called with non-NULL for both the singular_noun
* and plural_noun arguments, this function will output a summary.
* Otherwise, no summary will be added.
*
* \param[in,out] out The output functions structure.
*/
void (*end_list) (pcmk__output_t *out);
};
/*!
* \internal
* \brief Call a formatting function for a previously registered message.
*
* \note This function is for implementing custom formatters. It should not
* be called directly. Instead, call out->message.
*
* \param[in,out] out The output functions structure.
* \param[in] message_id The message to be handled. Unknown messages
* will be ignored.
* \param[in] ... Arguments to be passed to the registered function.
*/
int
pcmk__call_message(pcmk__output_t *out, const char *message_id, ...);
/*!
* \internal
* \brief Free a ::pcmk__output_t structure that was previously created by
* pcmk__output_new(). This will first call the finish function.
*
* \note While the create and finish functions are designed in such a way that
* they can be called repeatedly, this function will completely free the
* memory of the object. Once this function has been called, producing
* more output requires starting over from pcmk__output_new().
*
* \param[in,out] out The output structure.
* \param[in] exit_status The exit value of the whole program.
*/
void pcmk__output_free(pcmk__output_t *out, crm_exit_t exit_status);
/*!
* \internal
* \brief Create a new ::pcmk__output_t structure.
*
* \param[in,out] out The destination of the new ::pcmk__output_t.
* \param[in] fmt_name How should output be formatted?
* \param[in] filename Where should formatted output be written to? This
* can be a filename (which will be overwritten if it
* already exists), or NULL or "-" for stdout. For no
* output, pass a filename of "/dev/null".
* \param[in] argv The list of command line arguments.
*
* \return 0 on success or an error code on error.
*/
int pcmk__output_new(pcmk__output_t **out, const char *fmt_name,
const char *filename, char **argv);
/*!
* \internal
* \brief Register a new output formatter, making it available for use
* the same as a base formatter.
*
* \param[in,out] context A context to add any format-specific options to. This
* can be NULL for use outside of command line programs.
* \param[in] name The name of the format. This will be used to select a
* format from command line options and for displaying help.
* \param[in] create A function that creates a ::pcmk__output_t.
* \param[in] options Format-specific command line options. These will be
* added to the context. This argument can also be NULL.
*
* \return 0 on success or an error code on error.
*/
int
pcmk__register_format(GOptionContext *context, const char *name,
pcmk__output_factory_t create, GOptionEntry *options);
/*!
* \internal
* \brief Register an entire table of output formatters at once.
*
* \param[in,out] context A context to add any format-specific options to. This
* can be NULL for use outside of command line programs.
* \param[in] table An array of ::pcmk__supported_format_t which should
* all be registered. This array must be NULL-terminated.
*
*/
void
pcmk__register_formats(GOptionContext *context, pcmk__supported_format_t *table);
/*!
* \internal
* \brief Register a function to handle a custom message.
*
* \note This function is for implementing custom formatters. It should not
* be called directly. Instead, call out->register_message.
*
* \param[in,out] out The output functions structure.
* \param[in] message_id The message to be handled.
* \param[in] fn The custom format function to call for message_id.
*/
void
pcmk__register_message(pcmk__output_t *out, const char *message_id,
pcmk__message_fn_t fn);
/*!
* \internal
* \brief Register an entire table of custom formatting functions at once.
*
* This table can contain multiple formatting functions for the same message ID
* if they are for different format types.
*
* \param[in,out] out The output functions structure.
* \param[in] table An array of ::pcmk__message_entry_t values which should
* all be registered. This array must be NULL-terminated.
*/
void
pcmk__register_messages(pcmk__output_t *out, pcmk__message_entry_t *table);
/* Functions that are useful for implementing custom message formatters */
/*!
* \internal
* \brief A printf-like function.
*
* This function writes to out->dest and indents the text to the current level
* of the text formatter's nesting. This should be used when implementing
* custom message functions instead of printf.
*
* \param[in,out] out The output functions structure.
*/
void
pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
/*!
* \internal
* \brief Add the given node as a child of the current list parent. This is
* used when implementing custom message functions.
*
* \param[in,out] out The output functions structure.
* \param[in] node An XML node to be added as a child.
*/
void
pcmk__xml_add_node(pcmk__output_t *out, xmlNodePtr node);
/*!
* \internal
* \brief Push a parent XML node onto the stack. This is used when implementing
* custom message functions.
*
* The XML output formatter maintains an internal stack to keep track of which nodes
* are parents in order to build up the tree structure. This function can be used
* to temporarily push a new node onto the stack. After calling this function, any
* other formatting functions will have their nodes added as children of this new
* parent.
*
* \param[in,out] out The output functions structure.
* \param[in] node The node to be added/
*/
void
pcmk__xml_push_parent(pcmk__output_t *out, xmlNodePtr node);
/*!
* \internal
* \brief Pop a parent XML node onto the stack. This is used when implementing
* custom message functions.
*
* This function removes a parent node from the stack. See pcmk__xml_push_parent()
* for more details.
*
* \note Little checking is done with this function. Be sure you only pop parents
* that were previously pushed. In general, it is best to keep the code between
* push and pop simple.
*
* \param[in,out] out The output functions structure.
*/
void
pcmk__xml_pop_parent(pcmk__output_t *out);
/*!
* \internal
* \brief Peek a parent XML node onto the stack. This is used when implementing
* custom message functions.
*
* This function peeks a parent node on stack. See pcmk__xml_push_parent()
* for more details. It has no side-effect and can be called for an empty stack.
*
* \note Little checking is done with this function.
*
* \param[in,out] out The output functions structure.
*
* \return NULL if stack is empty, otherwise the parent of the stack.
*/
xmlNodePtr
pcmk__xml_peek_parent(pcmk__output_t *out);
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/common/output_text.c b/lib/common/output_text.c
index 8a4f60d782..084a0ae44f 100644
--- a/lib/common/output_text.c
+++ b/lib/common/output_text.c
@@ -1,248 +1,259 @@
/*
* 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.
*/
#include <stdlib.h>
#include <crm/crm.h>
#include <crm/common/output.h>
#include <glib.h>
/* Disabled for the moment, but we can enable it (or remove it entirely)
* when we make a decision on whether this is preferred output.
*/
#define FANCY_TEXT_OUTPUT 0
GOptionEntry pcmk__text_output_entries[] = {
{ NULL }
};
typedef struct text_list_data_s {
unsigned int len;
char *singular_noun;
char *plural_noun;
} text_list_data_t;
typedef struct text_private_s {
GQueue *parent_q;
} text_private_t;
static void
text_free_priv(pcmk__output_t *out) {
text_private_t *priv = out->priv;
if (priv == NULL) {
return;
}
g_queue_free(priv->parent_q);
free(priv);
}
static bool
text_init(pcmk__output_t *out) {
text_private_t *priv = NULL;
/* If text_init was previously called on this output struct, just return. */
if (out->priv != NULL) {
return true;
} else {
out->priv = calloc(1, sizeof(text_private_t));
if (out->priv == NULL) {
return false;
}
priv = out->priv;
}
priv->parent_q = g_queue_new();
return true;
}
static void
text_finish(pcmk__output_t *out, crm_exit_t exit_status) {
/* This function intentionally left blank */
}
static void
text_reset(pcmk__output_t *out) {
CRM_ASSERT(out->priv != NULL);
text_free_priv(out);
text_init(out);
}
static void
text_subprocess_output(pcmk__output_t *out, int exit_status,
const char *proc_stdout, const char *proc_stderr) {
if (proc_stdout != NULL) {
fprintf(out->dest, "%s\n", proc_stdout);
}
if (proc_stderr != NULL) {
fprintf(out->dest, "%s\n", proc_stderr);
}
}
+static void
+text_version(pcmk__output_t *out, bool extended) {
+ if (extended) {
+ fprintf(out->dest, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
+ } else {
+ fprintf(out->dest, "Pacemaker %s\n", PACEMAKER_VERSION);
+ fprintf(out->dest, "Written by Andrew Beekhof\n");
+ }
+}
+
G_GNUC_PRINTF(2, 3)
static void
text_err(pcmk__output_t *out, const char *format, ...) {
va_list ap;
int len = 0;
va_start(ap, format);
/* Informational output does not get indented, to separate it from other
* potentially indented list output.
*/
len = vfprintf(stderr, format, ap);
CRM_ASSERT(len > 0);
va_end(ap);
/* Add a newline. */
fprintf(stderr, "\n");
}
G_GNUC_PRINTF(2, 3)
static void
text_info(pcmk__output_t *out, const char *format, ...) {
va_list ap;
int len = 0;
va_start(ap, format);
/* Informational output does not get indented, to separate it from other
* potentially indented list output.
*/
len = vfprintf(out->dest, format, ap);
CRM_ASSERT(len > 0);
va_end(ap);
/* Add a newline. */
fprintf(out->dest, "\n");
}
static void
text_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
text_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
pcmk__indented_printf(out, "%s", buf);
}
static void
text_begin_list(pcmk__output_t *out, const char *name, const char *singular_noun,
const char *plural_noun) {
text_private_t *priv = out->priv;
text_list_data_t *new_list = NULL;
CRM_ASSERT(priv != NULL);
#if FANCY_TEXT_OUTPUT > 0
pcmk__indented_printf(out, "%s:\n", name);
#endif
new_list = calloc(1, sizeof(text_list_data_t));
new_list->len = 0;
new_list->singular_noun = singular_noun == NULL ? NULL : strdup(singular_noun);
new_list->plural_noun = plural_noun == NULL ? NULL : strdup(plural_noun);
g_queue_push_tail(priv->parent_q, new_list);
}
static void
text_list_item(pcmk__output_t *out, const char *id, const char *content) {
text_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
#if FANCY_TEXT_OUTPUT > 0
if (id != NULL) {
pcmk__indented_printf(out, "* %s: %s\n", id, content);
} else {
pcmk__indented_printf(out, "* %s\n", content);
}
#else
fprintf(out->dest, "%s\n", content);
#endif
((text_list_data_t *) g_queue_peek_tail(priv->parent_q))->len++;
}
static void
text_end_list(pcmk__output_t *out) {
text_private_t *priv = out->priv;
text_list_data_t *node = NULL;
CRM_ASSERT(priv != NULL);
node = g_queue_pop_tail(priv->parent_q);
if (node->singular_noun != NULL && node->plural_noun != NULL) {
if (node->len == 1) {
pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun);
} else {
pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun);
}
}
free(node);
}
pcmk__output_t *
pcmk__mk_text_output(char **argv) {
pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
if (retval == NULL) {
return NULL;
}
retval->request = g_strjoinv(" ", argv);
retval->supports_quiet = true;
retval->init = text_init;
retval->free_priv = text_free_priv;
retval->finish = text_finish;
retval->reset = text_reset;
retval->register_message = pcmk__register_message;
retval->message = pcmk__call_message;
retval->subprocess_output = text_subprocess_output;
+ retval->version = text_version;
retval->info = text_info;
retval->err = text_err;
retval->output_xml = text_output_xml;
retval->begin_list = text_begin_list;
retval->list_item = text_list_item;
retval->end_list = text_end_list;
return retval;
}
G_GNUC_PRINTF(2, 3)
void
pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) {
va_list ap;
int len = 0;
#if FANCY_TEXT_OUTPUT > 0
int level = 0;
text_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
level = g_queue_get_length(priv->parent_q);
for (int i = 0; i < level; i++) {
putc('\t', out->dest);
}
#endif
va_start(ap, format);
len = vfprintf(out->dest, format, ap);
CRM_ASSERT(len > 0);
va_end(ap);
}
diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c
index 80b6b71c02..da18f61398 100644
--- a/lib/common/output_xml.c
+++ b/lib/common/output_xml.c
@@ -1,306 +1,322 @@
/*
* 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 _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <crm/crm.h>
#include <crm/common/output.h>
#include <crm/common/xml.h>
#include <glib.h>
GOptionEntry pcmk__xml_output_entries[] = {
{ NULL }
};
typedef struct xml_private_s {
xmlNode *root;
GQueue *parent_q;
GSList *errors;
} xml_private_t;
static void
xml_free_priv(pcmk__output_t *out) {
xml_private_t *priv = out->priv;
if (priv == NULL) {
return;
}
xmlFreeNode(priv->root);
g_queue_free(priv->parent_q);
g_slist_free(priv->errors);
free(priv);
}
static bool
xml_init(pcmk__output_t *out) {
xml_private_t *priv = NULL;
/* If xml_init was previously called on this output struct, just return. */
if (out->priv != NULL) {
return true;
} else {
out->priv = calloc(1, sizeof(xml_private_t));
if (out->priv == NULL) {
return false;
}
priv = out->priv;
}
priv->root = create_xml_node(NULL, "pacemaker-result");
xmlSetProp(priv->root, (pcmkXmlStr) "api-version", (pcmkXmlStr) PCMK__API_VERSION);
if (out->request != NULL) {
xmlSetProp(priv->root, (pcmkXmlStr) "request", (pcmkXmlStr) out->request);
}
priv->parent_q = g_queue_new();
priv->errors = NULL;
g_queue_push_tail(priv->parent_q, priv->root);
return true;
}
static void
add_error_node(gpointer data, gpointer user_data) {
char *str = (char *) data;
xmlNodePtr node = (xmlNodePtr) user_data;
xmlNewTextChild(node, NULL, (pcmkXmlStr) "error", (pcmkXmlStr) str);
}
static void
xml_finish(pcmk__output_t *out, crm_exit_t exit_status) {
xmlNodePtr node;
char *rc_as_str = NULL;
char *buf = NULL;
xml_private_t *priv = out->priv;
/* If root is NULL, xml_init failed and we are being called from pcmk__output_free
* in the pcmk__output_new path.
*/
if (priv->root == NULL) {
return;
}
rc_as_str = crm_itoa(exit_status);
node = xmlNewChild(priv->root, NULL, (pcmkXmlStr) "status", NULL);
xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str);
xmlSetProp(node, (pcmkXmlStr) "message", (pcmkXmlStr) crm_exit_str(exit_status));
if (g_slist_length(priv->errors) > 0) {
xmlNodePtr errors_node = xmlNewChild(node, NULL, (pcmkXmlStr) "errors", NULL);
g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node);
}
buf = dump_xml_formatted_with_text(priv->root);
fprintf(out->dest, "%s", buf);
free(rc_as_str);
free(buf);
}
static void
xml_reset(pcmk__output_t *out) {
char *buf = NULL;
xml_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
buf = dump_xml_formatted_with_text(priv->root);
fprintf(out->dest, "%s", buf);
free(buf);
xml_free_priv(out);
xml_init(out);
}
static void
xml_subprocess_output(pcmk__output_t *out, int exit_status,
const char *proc_stdout, const char *proc_stderr) {
xmlNodePtr node, child_node;
char *rc_as_str = NULL;
xml_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
rc_as_str = crm_itoa(exit_status);
node = xmlNewNode(g_queue_peek_tail(priv->parent_q), (pcmkXmlStr) "command");
xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str);
if (proc_stdout != NULL) {
child_node = xmlNewTextChild(node, NULL, (pcmkXmlStr) "output",
(pcmkXmlStr) proc_stdout);
xmlSetProp(child_node, (pcmkXmlStr) "source", (pcmkXmlStr) "stdout");
}
if (proc_stderr != NULL) {
child_node = xmlNewTextChild(node, NULL, (pcmkXmlStr) "output",
(pcmkXmlStr) proc_stderr);
xmlSetProp(child_node, (pcmkXmlStr) "source", (pcmkXmlStr) "stderr");
}
pcmk__xml_add_node(out, node);
free(rc_as_str);
}
+static void
+xml_version(pcmk__output_t *out, bool extended) {
+ xmlNodePtr node;
+ xml_private_t *priv = out->priv;
+ CRM_ASSERT(priv != NULL);
+
+ node = xmlNewChild(g_queue_peek_tail(priv->parent_q), NULL, (pcmkXmlStr) "version", NULL);
+
+ xmlSetProp(node, (pcmkXmlStr) "program", (pcmkXmlStr) "Pacemaker");
+ xmlSetProp(node, (pcmkXmlStr) "version", (pcmkXmlStr) PACEMAKER_VERSION);
+ xmlSetProp(node, (pcmkXmlStr) "author", (pcmkXmlStr) "Andrew Beekhof");
+ xmlSetProp(node, (pcmkXmlStr) "build", (pcmkXmlStr) BUILD_VERSION);
+ xmlSetProp(node, (pcmkXmlStr) "features", (pcmkXmlStr) CRM_FEATURES);
+}
+
G_GNUC_PRINTF(2, 3)
static void
xml_err(pcmk__output_t *out, const char *format, ...) {
xml_private_t *priv = out->priv;
int len = 0;
char *buf = NULL;
va_list ap;
CRM_ASSERT(priv != NULL);
va_start(ap, format);
len = vasprintf(&buf, format, ap);
CRM_ASSERT(len > 0);
va_end(ap);
priv->errors = g_slist_append(priv->errors, buf);
}
G_GNUC_PRINTF(2, 3)
static void
xml_info(pcmk__output_t *out, const char *format, ...) {
/* This function intentially left blank */
}
static void
xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
xmlNodePtr parent = NULL;
xmlNodePtr cdata_node = NULL;
xml_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
parent = xmlNewChild(g_queue_peek_tail(priv->parent_q), NULL,
(pcmkXmlStr) name, NULL);
cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf));
xmlAddChild(parent, cdata_node);
}
static void
xml_begin_list(pcmk__output_t *out, const char *name,
const char *singular_noun, const char *plural_noun) {
xmlNodePtr list_node = NULL;
xml_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
list_node = create_xml_node(g_queue_peek_tail(priv->parent_q), "list");
xmlSetProp(list_node, (pcmkXmlStr) "name", (pcmkXmlStr) name);
g_queue_push_tail(priv->parent_q, list_node);
}
static void
xml_list_item(pcmk__output_t *out, const char *name, const char *content) {
xml_private_t *priv = out->priv;
xmlNodePtr item_node = NULL;
CRM_ASSERT(priv != NULL);
item_node = xmlNewChild(g_queue_peek_tail(priv->parent_q), NULL,
(pcmkXmlStr) "item", (pcmkXmlStr) content);
xmlSetProp(item_node, (pcmkXmlStr) "name", (pcmkXmlStr) name);
}
static void
xml_end_list(pcmk__output_t *out) {
char *buf = NULL;
xml_private_t *priv = out->priv;
xmlNodePtr node;
CRM_ASSERT(priv != NULL);
node = g_queue_pop_tail(priv->parent_q);
buf = crm_strdup_printf("%lu", xmlChildElementCount(node));
xmlSetProp(node, (pcmkXmlStr) "count", (pcmkXmlStr) buf);
free(buf);
}
pcmk__output_t *
pcmk__mk_xml_output(char **argv) {
pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
if (retval == NULL) {
return NULL;
}
retval->request = g_strjoinv(" ", argv);
retval->supports_quiet = false;
retval->init = xml_init;
retval->free_priv = xml_free_priv;
retval->finish = xml_finish;
retval->reset = xml_reset;
retval->register_message = pcmk__register_message;
retval->message = pcmk__call_message;
retval->subprocess_output = xml_subprocess_output;
+ retval->version = xml_version;
retval->info = xml_info;
retval->err = xml_err;
retval->output_xml = xml_output_xml;
retval->begin_list = xml_begin_list;
retval->list_item = xml_list_item;
retval->end_list = xml_end_list;
return retval;
}
void
pcmk__xml_add_node(pcmk__output_t *out, xmlNodePtr node) {
xml_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
CRM_ASSERT(node != NULL);
xmlAddChild(g_queue_peek_tail(priv->parent_q), node);
}
void
pcmk__xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) {
xml_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
CRM_ASSERT(parent != NULL);
g_queue_push_tail(priv->parent_q, parent);
}
void
pcmk__xml_pop_parent(pcmk__output_t *out) {
xml_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0);
g_queue_pop_tail(priv->parent_q);
}
xmlNodePtr
pcmk__xml_peek_parent(pcmk__output_t *out) {
xml_private_t *priv = out->priv;
CRM_ASSERT(priv != NULL);
/* If queue is empty NULL will be returned */
return g_queue_peek_tail(priv->parent_q);
}
diff --git a/tools/stonith_admin.c b/tools/stonith_admin.c
index f265ee96c1..05a807c72c 100644
--- a/tools/stonith_admin.c
+++ b/tools/stonith_admin.c
@@ -1,822 +1,820 @@
/*
* Copyright 2009-2019 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/mainloop.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>
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 verbose;
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
};
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.",
"HOST" },
{ "unfence", 'U', 0, G_OPTION_ARG_STRING, &options.unfence_host,
"Unfence named host. Optional: --timeout, --tolerance.",
"HOST" },
{ "reboot", 'B', 0, G_OPTION_ARG_STRING, &options.reboot_host,
"Reboot named host. Optional: --timeout, --tolerance.",
"HOST" },
{ "confirm", 'C', 0, G_OPTION_ARG_STRING, &options.confirm_host,
"Tell clusted 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" },
{ "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_TEXT,
PCMK__SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
static int st_opts = st_opt_sync_call | st_opt_allow_suicide;
static GMainLoop *mainloop = NULL;
struct {
stonith_t *st;
const char *target;
const char *action;
char *name;
int timeout;
int tolerance;
int rc;
} async_fence_data;
gboolean
add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
char *key = crm_concat("OCF_RESKEY", 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(async_fence_data.name);
async_fence_data.name = crm_strdup_printf("%s.%s", crm_system_name, optarg);
return TRUE;
}
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(st,
st_opt_allow_suicide,
async_fence_data.target,
async_fence_data.action,
async_fence_data.timeout, async_fence_data.tolerance);
if (call_id < 0) {
g_main_loop_quit(mainloop);
return TRUE;
}
st->cmds->register_callback(st,
call_id,
async_fence_data.timeout,
st_opt_timeout_updates, NULL, "callback", fence_callback);
return TRUE;
}
static int
mainloop_fencing(stonith_t * st, const char *target, const char *action, int timeout, int tolerance)
{
crm_trigger_t *trig;
async_fence_data.st = st;
async_fence_data.target = target;
async_fence_data.action = action;
async_fence_data.timeout = timeout;
async_fence_data.tolerance = tolerance;
async_fence_data.rc = -1;
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);
return async_fence_data.rc;
}
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 = strchr(target, '=');
/* Determine if targeting by attribute, node name pattern or node name */
if (value != NULL) {
name = target;
*value++ = '\0';
} else if (*target == '@') {
pattern = target + 1;
} else {
node = target;
}
/* Register or unregister level as appropriate */
if (added) {
return st->cmds->register_level_full(st, st_opts, node, pattern,
name, value, fence_level,
devices);
}
return st->cmds->remove_level_full(st, st_opts, node, pattern,
name, value, fence_level);
}
static int
handle_history(stonith_t *st, const char *target, int timeout, int quiet,
int verbose, int cleanup, int broadcast, pcmk__output_t *out)
{
stonith_history_t *history = NULL, *hp, *latest = NULL;
int rc = 0;
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);
out->begin_list(out, "Fencing history", "event", "events");
for (hp = history; hp; hp = hp->next) {
if (hp->state == st_done) {
latest = hp;
}
if (quiet || !verbose) {
continue;
}
out->message(out, "stonith-event", hp);
}
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);
}
}
out->end_list(out);
stonith_history_free(history);
return rc;
}
static int
validate(stonith_t *st, const char *agent, const char *id,
stonith_key_value_t *params, int timeout, int quiet,
pcmk__output_t *out)
{
int rc = 1;
char *output = NULL;
char *error_output = NULL;
rc = st->cmds->validate(st, st_opt_sync_call, id, NULL, agent, params,
timeout, &output, &error_output);
if (quiet) {
return rc;
}
out->message(out, "validate", agent, id, output, error_output, rc);
return rc;
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args) {
GOptionContext *context = NULL;
GOptionGroup *defn_group, *query_group, *fence_group, *addl_group;
context = pcmk__build_arg_context(args, "text (default), xml");
defn_group = g_option_group_new("definition", "Device Definition Commands:",
"Show device definition help", NULL, NULL);
g_option_group_add_entries(defn_group, defn_entries);
g_option_context_add_group(context, defn_group);
query_group = g_option_group_new("queries", "Queries:", "Show query help", NULL, NULL);
g_option_group_add_entries(query_group, query_entries);
g_option_context_add_group(context, query_group);
fence_group = g_option_group_new("fence", "Fencing Commands:", "Show fence help", NULL, NULL);
g_option_group_add_entries(fence_group, fence_entries);
g_option_context_add_group(context, fence_group);
addl_group = g_option_group_new("additional", "Additional Options:", "Show additional options", NULL, NULL);
g_option_group_add_entries(addl_group, addl_entries);
g_option_context_add_group(context, addl_group);
return context;
}
int
main(int argc, char **argv)
{
int rc = 0;
bool no_connect = false;
bool required_agent = false;
char *target = NULL;
char *lists = NULL;
const char *device = NULL;
crm_exit_t exit_code = CRM_EX_OK;
stonith_t *st = NULL;
stonith_key_value_t *dIter = NULL;
pcmk__output_t *out = NULL;
pcmk__common_args_t *args = calloc(1, sizeof(pcmk__common_args_t));
GError *error = NULL;
GOptionContext *context = NULL;
if (args == NULL) {
crm_exit(crm_errno2exit(-ENOMEM));
}
args->summary = strdup("stonith_admin - Access the Pacemaker fencing API");
context = build_arg_context(args);
crm_log_cli_init("stonith_admin");
async_fence_data.name = strdup(crm_system_name);
if (!g_option_context_parse(context, &argc, &argv, &error)) {
fprintf(stderr, "%s: %s\n", argv[0], error->message);
}
for (int i = 0; i < options.verbose; i++) {
crm_bump_log_level(argc, argv);
}
pcmk__register_formats(context, formats);
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
if (rc != 0) {
fprintf(stderr, "Error creating output format %s: %s\n", args->output_ty, pcmk_strerror(rc));
exit_code = CRM_EX_ERROR;
goto done;
}
stonith__register_messages(out);
if (args->version) {
- out->info(out, "Pacemaker %s", PACEMAKER_VERSION);
- out->info(out, "Written by Andrew Beekhof");
- crm_exit(CRM_EX_OK);
+ 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) {
out->err(out, "%s", g_option_context_get_help(context, TRUE, NULL));
exit_code = CRM_EX_USAGE;
goto done;
}
if (required_agent && options.agent == NULL) {
out->err(out, "Please specify an agent to query using -a,--agent [value]");
out->err(out, "%s", g_option_context_get_help(context, TRUE, NULL));
exit_code = CRM_EX_USAGE;
goto done;
}
st = stonith_api_new();
if (!no_connect) {
rc = st->cmds->connect(st, async_fence_data.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 = st->cmds->list_agents(st, st_opt_sync_call, NULL, &options.devices, options.timeout);
if (rc < 0) {
out->err(out, "Failed to list installed devices: %s", pcmk_strerror(rc));
break;
}
out->begin_list(out, "Installed fence devices", "fence device", "fence devices");
for (dIter = options.devices; dIter; dIter = dIter->next) {
out->list_item(out, "device", dIter->value);
}
out->end_list(out);
rc = 0;
stonith_key_value_freeall(options.devices, 1, 1);
break;
case 'L':
rc = st->cmds->query(st, st_opts, target, &options.devices, options.timeout);
if (rc < 0) {
out->err(out, "Failed to list registered devices: %s", pcmk_strerror(rc));
break;
}
out->begin_list(out, "Registered fence devices", "fence device", "fence devices");
for (dIter = options.devices; dIter; dIter = dIter->next) {
out->list_item(out, "device", dIter->value);
}
out->end_list(out);
rc = 0;
stonith_key_value_freeall(options.devices, 1, 1);
break;
case 'Q':
rc = st->cmds->monitor(st, st_opts, device, options.timeout);
if (rc < 0) {
rc = st->cmds->list(st, st_opts, device, NULL, options.timeout);
}
break;
case 's':
rc = st->cmds->list(st, st_opts, device, &lists, options.timeout);
if (rc == 0) {
GList *targets = stonith__parse_targets(lists);
out->begin_list(out, "Fence targets", "fence target", "fence targets");
while (targets != NULL) {
out->list_item(out, NULL, (const char *) targets->data);
targets = targets->next;
}
out->end_list(out);
free(lists);
} else if (rc != 0) {
out->err(out, "List command returned error. rc : %d", rc);
}
break;
case 'R':
rc = st->cmds->register_device(st, st_opts, device, NULL, options.agent,
options.params);
break;
case 'D':
rc = st->cmds->remove_device(st, st_opts, device);
break;
case 'd':
case 'r':
rc = handle_level(st, target, options.fence_level, options.devices, action == 'r');
break;
case 'M':
{
char *buffer = NULL;
rc = st->cmds->metadata(st, st_opt_sync_call, options.agent, NULL,
&buffer, options.timeout);
if (rc == pcmk_ok) {
out->output_xml(out, "metadata", buffer);
}
free(buffer);
}
break;
case 'C':
rc = st->cmds->confirm(st, st_opts, target);
break;
case 'B':
rc = mainloop_fencing(st, target, "reboot", options.timeout, options.tolerance);
break;
case 'F':
rc = mainloop_fencing(st, target, "off", options.timeout, options.tolerance);
break;
case 'U':
rc = mainloop_fencing(st, target, "on", options.timeout, options.tolerance);
break;
case 'h':
{
time_t when = 0;
if(options.as_nodeid) {
uint32_t nodeid = atol(target);
when = stonith_api_time(nodeid, NULL, FALSE);
} else {
when = stonith_api_time(0, target, FALSE);
}
out->message(out, "last-fenced", target, when);
}
break;
case 'H':
rc = handle_history(st, target, options.timeout, args->quiet,
options.verbose, options.cleanup,
options.broadcast, out);
break;
case 'K':
device = (options.devices ? options.devices->key : NULL);
rc = validate(st, options.agent, device, options.params,
options.timeout, args->quiet, out);
break;
}
crm_info("Command returned: %s (%d)", pcmk_strerror(rc), rc);
exit_code = crm_errno2exit(rc);
done:
g_option_context_free(context);
if (out != NULL) {
pcmk__output_free(out, exit_code);
}
free(async_fence_data.name);
stonith_key_value_freeall(options.params, 1, 1);
if (st != NULL) {
st->cmds->disconnect(st);
stonith_api_delete(st);
}
return exit_code;
}
diff --git a/xml/Makefile.am b/xml/Makefile.am
index db91ad0285..ffa6a3f610 100644
--- a/xml/Makefile.am
+++ b/xml/Makefile.am
@@ -1,221 +1,221 @@
#
# Copyright 2004-2019 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
MAINTAINERCLEANFILES = Makefile.in
APIdir = $(CRM_SCHEMA_DIRECTORY)/api
RNGdir = $(CRM_SCHEMA_DIRECTORY)
xsltdir = $(RNGdir)
dist_xslt_DATA = $(top_srcdir)/xml/upgrade-1.3.xsl \
$(top_srcdir)/xml/upgrade-2.10.xsl \
$(top_srcdir)/xml/upgrade-*enter.xsl \
$(top_srcdir)/xml/upgrade-*leave.xsl
noinst_DATA = context-of.xsl
# See Readme.md for details on updating schema files
# Sorted list of available numeric RNG versions,
# extracted from filenames like NAME-MAJOR[.MINOR][.MINOR-MINOR].rng
numeric_versions = $(shell ls -1 $(1) \
| sed -n -e 's/^.*-\([0-9][0-9.]*\).rng$$/\1/p' \
| sort -u -t. -k 1,1n -k 2,2n -k 3,3n)
version_pairs = $(join \
$(1),$(addprefix \
-,$(wordlist \
2,$(words $(1)),$(1) \
) next \
) \
)
version_pairs_last = $(wordlist \
$(words \
$(wordlist \
2,$(1),$(2) \
) \
),$(1),$(2) \
)
API_numeric_versions = $(call numeric_versions,${API_files})
RNG_numeric_versions = $(call numeric_versions,${RNG_files})
# The highest numeric version
API_max ?= $(lastword $(API_numeric_versions))
RNG_max ?= $(lastword $(RNG_numeric_versions))
# A sorted list of all API and RNG versions (numeric and "next")
API_versions = next $(API_numeric_versions)
-API_request_base = command-output stonith_admin
+API_request_base = command-output stonith_admin version
API_base = $(API_request_base) item status
API_files = $(foreach base,$(API_base),$(wildcard api/$(base)*.rng))
RNG_versions = next $(RNG_numeric_versions)
RNG_version_pairs = $(call version_pairs,${RNG_numeric_versions})
RNG_version_pairs_cnt = $(words ${RNG_version_pairs})
RNG_version_pairs_last = $(call version_pairs_last,${RNG_version_pairs_cnt},${RNG_version_pairs})
API_generated = api/api-result.rng $(foreach base,$(API_versions),api/api-result-$(base).rng)
RNG_generated = pacemaker.rng $(foreach base,$(RNG_versions),pacemaker-$(base).rng) versions.rng
RNG_cfg_base = options nodes resources constraints fencing acls tags alerts
RNG_base = cib $(RNG_cfg_base) status score rule nvset
RNG_files = $(foreach base,$(RNG_base),$(wildcard $(base).rng $(base)-*.rng))
# List of non-Pacemaker RNGs
RNG_extra = crm_mon.rng
dist_API_DATA = $(API_files)
dist_RNG_DATA = $(RNG_files) $(RNG_extra)
nodist_API_DATA = $(API_generated)
nodist_RNG_DATA = $(RNG_generated)
EXTRA_DIST = best-match.sh
versions:
echo "Max: $(RNG_max)"
echo "Available: $(RNG_versions)"
api-versions:
echo "Max: $(API_max)"
echo "Available: $(API_versions)"
versions.rng: Makefile.am
echo " RNG $@"
echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
echo ' <start>' >> $@
echo ' <interleave>' >> $@
echo ' <optional>' >> $@
echo ' <attribute name="validate-with">' >> $@
echo ' <choice>' >> $@
echo ' <value>none</value>' >> $@
echo ' <value>pacemaker-0.6</value>' >> $@
echo ' <value>transitional-0.6</value>' >> $@
echo ' <value>pacemaker-0.7</value>' >> $@
echo ' <value>pacemaker-1.1</value>' >> $@
for rng in $(RNG_versions); do echo " <value>pacemaker-$$rng</value>" >> $@; done
echo ' </choice>' >> $@
echo ' </attribute>' >> $@
echo ' </optional>' >> $@
echo ' <attribute name="admin_epoch"><data type="nonNegativeInteger"/></attribute>' >> $@
echo ' <attribute name="epoch"><data type="nonNegativeInteger"/></attribute>' >> $@
echo ' <attribute name="num_updates"><data type="nonNegativeInteger"/></attribute>' >> $@
echo ' </interleave>' >> $@
echo ' </start>' >> $@
echo '</grammar>' >> $@
api/api-result.rng: api/api-result-$(API_max).rng
echo " RNG $@"
cp $(top_builddir)/xml/$< $@
pacemaker.rng: pacemaker-$(RNG_max).rng
echo " RNG $@"
cp $(top_builddir)/xml/$< $@
api/api-result-%.rng: $(API_files) Makefile.am
echo " RNG $@"
echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
echo ' <start>' >> $@
echo ' <element name="pacemaker-result">' >> $@
echo ' <attribute name="api-version"> <text /> </attribute>' >> $@
echo ' <attribute name="request"> <text /> </attribute>' >> $@
echo ' <optional>' >> $@
echo ' <choice>' >> $@
for rng in $(API_request_base); do $(top_srcdir)/xml/best-match.sh api/$$rng $(*) $(@) " " || :; done
echo ' </choice>' >> $@
echo ' </optional>' >> $@
$(top_srcdir)/xml/best-match.sh api/status $(*) $(@) " " || :
echo ' </element>' >> $@
echo ' </start>' >> $@
echo '</grammar>' >> $@
pacemaker-%.rng: $(RNG_files) best-match.sh Makefile.am
echo " RNG $@"
echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
echo ' <start>' >> $@
echo ' <element name="cib">' >> $@
$(top_srcdir)/xml/best-match.sh cib $(*) $(@) " "
echo ' <element name="configuration">' >> $@
echo ' <interleave>' >> $@
for rng in $(RNG_cfg_base); do $(top_srcdir)/xml/best-match.sh $$rng $(*) $(@) " " || :; done
echo ' </interleave>' >> $@
echo ' </element>' >> $@
echo ' <optional>' >> $@
echo ' <element name="status">' >> $@
$(top_srcdir)/xml/best-match.sh status $(*) $(@) " "
echo ' </element>' >> $@
echo ' </optional>' >> $@
echo ' </element>' >> $@
echo ' </start>' >> $@
echo '</grammar>' >> $@
# diff fails with ec=2 if no predecessor is found;
# this uses '=' GNU extension to sed, if that's not available,
# one can use: hline=`echo "$${p}" | grep -Fn "$${hunk}" | cut -d: -f1`;
# XXX: use line information from hunk to avoid "not detected" for ambiguity
version_diff = \
@for p in $(1); do \
set `echo "$${p}" | tr '-' ' '`; \
echo "\#\#\# *-$$2.rng vs. predecessor"; \
for v in *-$$2.rng; do \
echo "\#\#\#\# $${v} vs. predecessor"; b=`echo "$${v}" | cut -d- -f1`; \
old=`./best-match.sh $${b} $$1`; \
p=`diff -u "$${old}" "$${v}" 2>/dev/null`; \
case $$? in \
1) echo "$${p}" | sed -n -e '/^@@ /!d;=;p' \
-e ':l;n;/^\([- ]\|+.*<[^ />]\+\([^/>]\+="ID\|>$$\)\)/bl;s/^[+ ]\(.*\)/\1/p' \
| while read hline; do \
read h && read i || break; \
iline=`grep -Fn "$${i}" "$${v}" | cut -d: -f1`; \
ctxt="(not detected)"; \
if test `echo "$${iline}" | wc -l` -eq 1; then \
ctxt=`{ sed -n -e "1,$$(($${iline}-1))p" "$${v}"; \
echo "<inject id=\"GOAL\"/>$${i}"; \
sed -n -e "$$(($${iline}+1)),$$ p" "$${v}"; \
} | $(XSLTPROC) --param skip 1 context-of.xsl -`; \
fi; \
echo "$${p}" | sed -n -e "$$(($${hline}-2)),$${hline}!d" \
-e '/^\(+++\|---\)/p'; \
echo "$${h} context: $${ctxt}"; \
echo "$${p}" | sed -n -e "1,$${hline}d" \
-e '/^\(---\|@@ \)/be;p;d;:e;n;be'; \
done; \
;; \
2) echo "\#\#\#\#\# $${v} has no predecessor";; \
esac; \
done; \
done
diff: best-match.sh
@echo "# Comparing changes in + since $(RNG_max)"
$(call version_diff,${RNG_version_pairs_last})
fulldiff: best-match.sh
@echo "# Comparing all changes across all the subsequent increments"
$(call version_diff,${RNG_version_pairs})
sync:
git rm -f $(wildcard *-next.rng)
make pacemaker-next.rng
CLEANFILES = $(API_generated) $(RNG_generated)
diff --git a/xml/api/version-2.0.rng b/xml/api/version-2.0.rng
new file mode 100644
index 0000000000..56c810b457
--- /dev/null
+++ b/xml/api/version-2.0.rng
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+
+ <start>
+ <ref name="element-version"/>
+ </start>
+
+ <define name="element-version">
+ <element name="version">
+ <attribute name="program"> <text /> </attribute>
+ <attribute name="version"> <text /> </attribute>
+ <attribute name="author"> <text /> </attribute>
+ <attribute name="build"> <text /> </attribute>
+ <attribute name="features"> <text /> </attribute>
+ </element>
+ </define>
+
+</grammar>

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 5:02 PM (14 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1019030
Default Alt Text
(72 KB)

Event Timeline