diff --git a/include/crm/common/output.h b/include/crm/common/output.h index f2221e0dce..518794cd58 100644 --- a/include/crm/common/output.h +++ b/include/crm/common/output.h @@ -1,552 +1,566 @@ /* * 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 # include # include # include # include # define PCMK__API_VERSION "1.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 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 4b8d885f8c..8a4f60d782 100644 --- a/lib/common/output_text.c +++ b/lib/common/output_text.c @@ -1,228 +1,248 @@ /* * 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 #include #include #include /* 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); } } +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->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 8fb61af833..80b6b71c02 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -1,272 +1,306 @@ /* * 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 #include #include #include #include #include #include #include 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 = xmlNewTextChild(priv->root, NULL, (pcmkXmlStr) "status", - (pcmkXmlStr) crm_exit_str(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); } +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->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 4f9bbf06c8..f265ee96c1 100644 --- a/tools/stonith_admin.c +++ b/tools/stonith_admin.c @@ -1,820 +1,822 @@ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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) { - fprintf(stdout, "Pacemaker %s\n", PACEMAKER_VERSION); - fprintf(stdout, "Written by Andrew Beekhof\n"); + out->info(out, "Pacemaker %s", PACEMAKER_VERSION); + out->info(out, "Written by Andrew Beekhof"); crm_exit(CRM_EX_OK); + 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) { - fputs(g_option_context_get_help(context, TRUE, NULL), stderr); - crm_exit(CRM_EX_USAGE); + 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) { - fprintf(stderr, "Please specify an agent to query using -a,--agent [value]\n"); - fputs(g_option_context_get_help(context, TRUE, NULL), stderr); - crm_exit(CRM_EX_USAGE); + 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) { - fprintf(stderr, "Could not connect to fencer: %s\n", - pcmk_strerror(rc)); + 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) { - fprintf(stderr, "Failed to list installed devices: %s\n", pcmk_strerror(rc)); + 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) { - fprintf(stderr, "Failed to list registered devices: %s\n", pcmk_strerror(rc)); + 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) { - fprintf(stderr, "List command returned error. rc : %d\n", rc); + 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 49542a3104..db91ad0285 100644 --- a/xml/Makefile.am +++ b/xml/Makefile.am @@ -1,224 +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_base = $(API_request_base) item +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 '' > $@ echo '' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' none' >> $@ echo ' pacemaker-0.6' >> $@ echo ' transitional-0.6' >> $@ echo ' pacemaker-0.7' >> $@ echo ' pacemaker-1.1' >> $@ for rng in $(RNG_versions); do echo " pacemaker-$$rng" >> $@; done echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo '' >> $@ 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 '' > $@ echo '' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ for rng in $(API_request_base); do $(top_srcdir)/xml/best-match.sh api/$$rng $(*) $(@) " " || :; done echo ' ' >> $@ echo ' ' >> $@ - echo ' ' >> $@ - echo ' ' >> $@ - echo ' ' >> $@ - echo ' ' >> $@ + $(top_srcdir)/xml/best-match.sh api/status $(*) $(@) " " || : echo ' ' >> $@ echo ' ' >> $@ echo '' >> $@ pacemaker-%.rng: $(RNG_files) best-match.sh Makefile.am echo " RNG $@" echo '' > $@ echo '' >> $@ echo ' ' >> $@ echo ' ' >> $@ $(top_srcdir)/xml/best-match.sh cib $(*) $(@) " " echo ' ' >> $@ echo ' ' >> $@ for rng in $(RNG_cfg_base); do $(top_srcdir)/xml/best-match.sh $$rng $(*) $(@) " " || :; done echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ $(top_srcdir)/xml/best-match.sh status $(*) $(@) " " echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo '' >> $@ # 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 "$${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/status-2.0.rng b/xml/api/status-2.0.rng new file mode 100644 index 0000000000..865ac088d0 --- /dev/null +++ b/xml/api/status-2.0.rng @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + +