diff --git a/Makefile.common b/Makefile.common index 0b06ec4da4..215b26c810 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1,80 +1,86 @@ # Not all current distros support AM_V_P # https://www.gnu.org/software/automake/manual/html_node/Automake-silent_002drules-Option.html V ?= $(AM_DEFAULT_VERBOSITY) PCMK_V = $(pcmk__v_$(V)) pcmk__v_0 = : pcmk__v_1 = PCMK_quiet = $(pcmk_quiet_$(V)) pcmk_quiet_0 = >/dev/null 2>&1 pcmk_quiet_1 = AM_V_XSL = $(am__v_XSL_$(V)) am__v_XSL_0 = @echo " XSL " $@; am__v_XSL_1 = AM_V_MAN = $(am__v_MAN_$(V)) am__v_MAN_0 = @echo " MAN " $@; am__v_MAN_1 = AM_V_ASCII = $(am__v_ASCII_$(V)) am__v_ASCII_0 = @echo " ASCII " $@; am__v_ASCII_1 = AM_V_PUB = $(am__v_PUB_$(V)) am__v_PUB_0 = @echo " PUB $@: $(DOCBOOK_FORMATS)"; am__v_PUB_1 = MAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include \ -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl if BUILD_HELP man8_MANS = $(sbin_PROGRAMS:%=%.8) $(sbin_SCRIPTS:%=%.8) endif +HELP2MAN_ARGS = -N --section 8 --name "Part of the Pacemaker cluster resource manager" + %.8: % $(MAN8DEPS) chmod a+x $(abs_builddir)/$< - $(AM_V_MAN)PATH=$(abs_builddir):$$PATH $(HELP2MAN) --output $@ --no-info --section 8 --name "Part of the Pacemaker cluster resource manager" $(abs_builddir)/$< + if [ -f $(abs_builddir)/$@.inc ]; then \ + $(AM_V_MAN)PATH=$(abs_builddir):$$PATH $(HELP2MAN) $(HELP2MAN_ARGS) -h --help-all -i $(abs_builddir)/$@.inc $(abs_builddir)/$< | sed -e '/.SS "Usage:"/,+3d' > $@ ; \ + else \ + $(AM_V_MAN)PATH=$(abs_builddir):$$PATH $(HELP2MAN) $(HELP2MAN_ARGS) $(abs_builddir)/$< --output $@ ; \ + fi %.xml: % $(AM_V_GEN)$(abs_builddir)/$< metadata > $@ %.dbook: %.xml $(AM_V_XSL)$(XSLTPROC) --nonet --novalid --stringparam man.name $* $(DBOOK_OPTS) $(top_srcdir)/xml/ocf-meta2man.xsl $(abs_builddir)/$< > $(abs_builddir)/$@ %.7: %.dbook $(AM_V_XSL)$(XSLTPROC) $(MANPAGE_XSLT) $(abs_builddir)/$< $(PCMK_quiet) # Build docbook from asciidoc because XML is a PITA to edit # # Build each chapter as a book (since the numbering isn't right for # articles and only books can have appendices) and then strip out the # bits we don't want/need # # XXX Sequence of tr/sed commands should be replaced with a single XSLT # %.xml: %.txt if IS_ASCIIDOC $(AM_V_ASCII)$(ASCIIDOC_CONV) -b docbook -d book -o $@-tt $< else $(AM_V_ASCII)$(ASCIIDOC_CONV) -b docbook45 -d book -o $@-tt $< endif $(AM_V_at)tr -d '\036\r' <$@-tt >$@-t; rm -f $@-tt # Fix line endings $(AM_V_at)sed -i 's/\ lang="en"//' $@-t # Never specify a language in the chapters $(AM_V_at)sed -i 's/simpara/para/g' $@-t # publican doesn't correctly render footnotes with simpara $(AM_V_at)sed -i 's/.*.*//g' $@-t # Remove dangling tag $(AM_V_at)sed -i 's/.*preface>//g' $@-t # Remove preface elements $(AM_V_at)sed -i 's:::g' $@-t # Remove empty title $(AM_V_at)sed -i 's/chapter/section/g' $@-t # Chapters become sections, so that books can become chapters $(AM_V_at)sed -i 's/<.*bookinfo.*>//g' $@-t # Strip out bookinfo, we don't need it $(AM_V_at)! grep -q "//;tb;bf;:b;N;s/.*.*<\/title>.*//;tb;/<appendix/{:i;n;/<\/appendix/{p;d};bi};bb;:f;p;d' \ $@-t # We just want the appendix tag (asciidoctor adds non-empty book-level title) $(AM_V_at)sed -i 's/book>/chapter>/g' $@-t # Rename to chapter (won't trigger if previous sed did) $(AM_V_GEN)mv $@-t $@ # echo Rebuilt $@ from $< diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am index deeff16fe6..6981586c59 100644 --- a/include/crm/common/Makefile.am +++ b/include/crm/common/Makefile.am @@ -1,18 +1,18 @@ # # Copyright 2004-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. # MAINTAINERCLEANFILES = Makefile.in headerdir=$(pkgincludedir)/crm/common header_HEADERS = xml.h ipc.h util.h iso8601.h mainloop.h logging.h results.h \ nvpair.h noinst_HEADERS = cib_secrets.h ipcs.h internal.h alerts_internal.h \ iso8601_internal.h remote_internal.h xml_internal.h \ - ipc_internal.h output.h + ipc_internal.h output.h cmdline_internal.h diff --git a/include/crm/common/cmdline_internal.h b/include/crm/common/cmdline_internal.h new file mode 100644 index 0000000000..b27b236204 --- /dev/null +++ b/include/crm/common/cmdline_internal.h @@ -0,0 +1,38 @@ +/* + * 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 CMDLINE_INTERNAL__H +#define CMDLINE_INTERNAL__H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <glib.h> + +typedef struct { + char *summary; + + gboolean version; + gboolean quiet; + unsigned int verbosity; + + char *output_ty; + char *output_ty_desc; + char *output_dest; +} pcmk__common_args_t; + +GOptionContext * +pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/crm/common/output.h b/include/crm/common/output.h index c0cbad3daf..96ae9658ed 100644 --- a/include/crm/common/output.h +++ b/include/crm/common/output.h @@ -1,531 +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" - -/* Add to the long_options block in each tool to get the formatted output - * command line options added. Then call pcmk__parse_output_args to handle - * them. - */ -# define PCMK__OUTPUT_OPTIONS(fmts) \ - { "output-as", required_argument, NULL, 0, \ - "Specify the format for output, one of: " fmts \ - }, \ - { "output-to", required_argument, NULL, 0, \ - "Specify the destination for formatted output, \"-\" for stdout or a filename" \ - } +# 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; -/* Basic formatters everything supports. This block needs to be updated every - * time a new base formatter is added. +/*! + * \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 Process formatted output related command line options. This should - * be called wherever other long options are handled. + * \brief Register a new output formatter, making it available for use + * the same as a base formatter. * - * \param[in] argname The long command line argument to process. - * \param[in] argvalue The value of the command line argument. - * \param[out] output_ty How should output be formatted? ("text", "xml", etc.) - * \param[out] output_dest Where should formatted output be written to? This is - * typically a filename, but could be NULL or "-". + * \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 true if longname was handled, false otherwise. + * \return 0 on success or an error code on error. */ -bool -pcmk__parse_output_args(const char *argname, char *argvalue, char **output_ty, - char **output_dest); +int +pcmk__register_format(GOptionContext *context, const char *name, + pcmk__output_factory_t create, GOptionEntry *options); /*! * \internal - * \brief Register a new output formatter, making it available for use - * the same as a base formatter. + * \brief Register an entire table of output formatters at once. * - * \param[in] fmt The new output formatter to register. + * \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. * - * \return 0 on success or an error code on error. */ -int -pcmk__register_format(const char *fmt_name, pcmk__output_factory_t create); - +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/Makefile.am b/lib/common/Makefile.am index 0b96e05f18..74de16b061 100644 --- a/lib/common/Makefile.am +++ b/lib/common/Makefile.am @@ -1,66 +1,67 @@ # # Copyright 2004-2019 Andrew Beekhof <andrew@beekhof.net> # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/Makefile.common AM_CPPFLAGS += -I$(top_builddir)/lib/gnu -I$(top_srcdir)/lib/gnu -DPCMK_SCHEMAS_EMERGENCY_XSLT=0 ## libraries lib_LTLIBRARIES = libcrmcommon.la # Disable -Wcast-qual if used, because we do some hacky casting, # and because libxml2 has some signatures that should be const but aren't # for backward compatibility reasons. # s390 needs -fPIC # s390-suse-linux/bin/ld: .libs/ipc.o: relocation R_390_PC32DBL against `__stack_chk_fail@@GLIBC_2.4' can not be used when making a shared object; recompile with -fPIC CFLAGS = $(CFLAGS_COPY:-Wcast-qual=) -fPIC noinst_HEADERS = crmcommon_private.h libcrmcommon_la_LDFLAGS = -version-info 36:0:2 libcrmcommon_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libcrmcommon_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) libcrmcommon_la_LIBADD = @LIBADD_DL@ $(GNUTLSLIBS) # Use += rather than backlashed continuation lines for parsing by bumplibs.sh libcrmcommon_la_SOURCES = libcrmcommon_la_SOURCES += acl.c libcrmcommon_la_SOURCES += agents.c libcrmcommon_la_SOURCES += alerts.c libcrmcommon_la_SOURCES += attrd_client.c if BUILD_CIBSECRETS libcrmcommon_la_SOURCES += cib_secrets.c endif +libcrmcommon_la_SOURCES += cmdline.c libcrmcommon_la_SOURCES += compat.c libcrmcommon_la_SOURCES += digest.c libcrmcommon_la_SOURCES += io.c libcrmcommon_la_SOURCES += ipc.c libcrmcommon_la_SOURCES += iso8601.c libcrmcommon_la_SOURCES += logging.c libcrmcommon_la_SOURCES += mainloop.c libcrmcommon_la_SOURCES += nvpair.c libcrmcommon_la_SOURCES += operations.c libcrmcommon_la_SOURCES += output.c libcrmcommon_la_SOURCES += output_text.c libcrmcommon_la_SOURCES += output_xml.c libcrmcommon_la_SOURCES += pid.c libcrmcommon_la_SOURCES += procfs.c libcrmcommon_la_SOURCES += remote.c libcrmcommon_la_SOURCES += results.c libcrmcommon_la_SOURCES += schemas.c libcrmcommon_la_SOURCES += strings.c libcrmcommon_la_SOURCES += utils.c libcrmcommon_la_SOURCES += watchdog.c libcrmcommon_la_SOURCES += xml.c libcrmcommon_la_SOURCES += xpath.c libcrmcommon_la_SOURCES += ../gnu/md5.c clean-generic: rm -f *.log *.debug *.xml *~ diff --git a/lib/common/cmdline.c b/lib/common/cmdline.c new file mode 100644 index 0000000000..d68281710d --- /dev/null +++ b/lib/common/cmdline.c @@ -0,0 +1,72 @@ +/* + * 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 <config.h> +#include <glib.h> + +#include <crm/common/cmdline_internal.h> +#include <crm/common/util.h> + +static gboolean +bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; + common_args->verbosity++; + return TRUE; +} + +static void +free_common_args(gpointer data) { + pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; + + free(common_args->summary); + free(common_args->output_ty); + free(common_args->output_ty_desc); + free(common_args->output_dest); +} + +GOptionContext * +pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts) { + char *desc = crm_strdup_printf("Report bugs to %s\n", PACKAGE_BUGREPORT); + GOptionContext *context; + GOptionGroup *main_group; + + GOptionEntry main_entries[6] = { + { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version), + "Display version information and exit.", + NULL }, + { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity, + "Increase debug output (may be specified multiple times).", + NULL }, + { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(common_args->quiet), + "Be less descriptive in output.", + NULL }, + { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty), + NULL, + "FORMAT" }, + { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest), + "Specify the destination for output, \"-\" for stdout or a filename", "DEST" }, + + { NULL } + }; + + common_args->output_ty_desc = crm_strdup_printf("Specify the format for output, one of: %s", fmts); + main_entries[3].description = common_args->output_ty_desc; + + main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args); + g_option_group_add_entries(main_group, main_entries); + + context = g_option_context_new(NULL); + g_option_context_set_summary(context, common_args->summary); + g_option_context_set_description(context, desc); + g_option_context_set_main_group(context, main_group); + + free(desc); + + return context; +} diff --git a/lib/common/output.c b/lib/common/output.c index 534a377c03..a9fc6f6016 100644 --- a/lib/common/output.c +++ b/lib/common/output.c @@ -1,149 +1,157 @@ /* * 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 <crm/common/util.h> #include <crm/common/xml.h> #include <crm/common/internal.h> #include <crm/common/output.h> #include <libxml/tree.h> static GHashTable *formatters = NULL; void pcmk__output_free(pcmk__output_t *out, crm_exit_t exit_status) { pcmk__output_factory_t fn = g_hash_table_lookup(formatters, out->fmt_name); CRM_ASSERT(fn != NULL); out->finish(out, exit_status); out->free_priv(out); g_hash_table_destroy(out->messages); free(out->fmt_name); free(out->request); free(out); } int pcmk__output_new(pcmk__output_t **out, const char *fmt_name, const char *filename, char **argv) { pcmk__output_factory_t create = NULL;; if (formatters == NULL) { return EINVAL; } /* If no name was given, just try "text". It's up to each tool to register * what it supports so this also may not be valid. */ if (fmt_name == NULL) { create = g_hash_table_lookup(formatters, "text"); } else { create = g_hash_table_lookup(formatters, fmt_name); } if (create == NULL) { return pcmk_err_unknown_format; } *out = create(argv); if (*out == NULL) { return ENOMEM; } if (fmt_name == NULL) { (*out)->fmt_name = strdup("text"); } else { (*out)->fmt_name = strdup(fmt_name); } if (filename == NULL || safe_str_eq(filename, "-")) { (*out)->dest = stdout; } else { (*out)->dest = fopen(filename, "w"); if ((*out)->dest == NULL) { return errno; } } (*out)->messages = g_hash_table_new_full(crm_str_hash, g_str_equal, free, NULL); if ((*out)->init(*out) == false) { pcmk__output_free(*out, 0); return ENOMEM; } return 0; } -bool -pcmk__parse_output_args(const char *argname, char *argvalue, char **output_ty, char **output_dest) { - if (safe_str_eq("output-as", argname)) { - *output_ty = argvalue; - return true; - } else if (safe_str_eq("output-to", argname)) { - if (safe_str_eq(argvalue, "-")) { - *output_dest = NULL; - } else { - *output_dest = argvalue; - } - - return true; - } - - return false; -} - int -pcmk__register_format(const char *fmt_name, pcmk__output_factory_t create) { +pcmk__register_format(GOptionContext *context, const char *name, + pcmk__output_factory_t create, GOptionEntry *options) { if (create == NULL) { return -EINVAL; } if (formatters == NULL) { formatters = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, NULL); } - g_hash_table_insert(formatters, strdup(fmt_name), create); + if (options != NULL && context != NULL) { + char *group_name = crm_strdup_printf("output-%s", name); + char *group_desc = crm_strdup_printf("%s Output Options:", name); + char *group_help = crm_strdup_printf("Show %s output help", name); + + GOptionGroup *group = g_option_group_new(group_name, group_desc, + group_help, NULL, NULL); + + g_option_group_add_entries(group, options); + g_option_context_add_group(context, group); + + free(group_name); + free(group_desc); + free(group_help); + } + + g_hash_table_insert(formatters, strdup(name), create); return 0; } +void +pcmk__register_formats(GOptionContext *context, pcmk__supported_format_t *formats) { + pcmk__supported_format_t *entry = NULL; + + for (entry = formats; entry->name != NULL; entry++) { + pcmk__register_format(context, entry->name, entry->create, entry->options); + } +} + int pcmk__call_message(pcmk__output_t *out, const char *message_id, ...) { va_list args; int rc = 0; pcmk__message_fn_t fn; fn = g_hash_table_lookup(out->messages, message_id); if (fn == NULL) { return -EINVAL; } va_start(args, message_id); rc = fn(out, args); va_end(args); return rc; } void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn) { g_hash_table_replace(out->messages, strdup(message_id), fn); } void pcmk__register_messages(pcmk__output_t *out, pcmk__message_entry_t *table) { pcmk__message_entry_t *entry; for (entry = table; entry->message_id != NULL; entry++) { if (safe_str_eq(out->fmt_name, entry->fmt_name)) { pcmk__register_message(out, entry->message_id, entry->fn); } } } diff --git a/lib/common/output_text.c b/lib/common/output_text.c index 06a51d10a2..084a0ae44f 100644 --- a/lib/common/output_text.c +++ b/lib/common/output_text.c @@ -1,223 +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 c3bda0eb0a..da18f61398 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -1,267 +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 = 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); } +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/Makefile.am b/tools/Makefile.am index 6516e558aa..79570fc099 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,151 +1,151 @@ # # Copyright 2004-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 $(top_srcdir)/Makefile.common if BUILD_SYSTEMD systemdunit_DATA = crm_mon.service endif noinst_HEADERS = crm_resource.h pcmkdir = $(datadir)/$(PACKAGE) pcmk_DATA = report.common report.collector sbin_SCRIPTS = crm_report crm_standby crm_master crm_failcount if BUILD_CIBSECRETS sbin_SCRIPTS += cibsecret endif -EXTRA_DIST = $(sbin_SCRIPTS) +EXTRA_DIST = $(sbin_SCRIPTS) stonith_admin.8.inc sbin_PROGRAMS = attrd_updater \ cibadmin \ crmadmin \ crm_simulate \ crm_attribute \ crm_diff \ crm_error \ crm_mon \ crm_node \ crm_resource \ crm_rule \ crm_shadow \ crm_verify \ crm_ticket \ iso8601 \ stonith_admin if BUILD_SERVICELOG sbin_PROGRAMS += notifyServicelogEvent endif if BUILD_OPENIPMI_SERVICELOG sbin_PROGRAMS += ipmiservicelogd endif ## SOURCES MAN8DEPS = crm_attribute crm_node crmadmin_SOURCES = crmadmin.c crmadmin_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_error_SOURCES = crm_error.c crm_error_LDADD = $(top_builddir)/lib/common/libcrmcommon.la cibadmin_SOURCES = cibadmin.c cibadmin_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_shadow_SOURCES = crm_shadow.c crm_shadow_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_node_SOURCES = crm_node.c crm_node_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_simulate_SOURCES = crm_simulate.c crm_simulate_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_diff_SOURCES = crm_diff.c crm_diff_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_mon_SOURCES = crm_mon.c crm_mon_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la \ $(CURSESLIBS) # Arguments could be made that this should live in crm/pengine crm_verify_SOURCES = crm_verify.c crm_verify_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_attribute_SOURCES = crm_attribute.c crm_attribute_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_resource_SOURCES = crm_resource.c crm_resource_ban.c crm_resource_runtime.c crm_resource_print.c crm_resource_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/lrmd/liblrmd.la \ $(top_builddir)/lib/services/libcrmservice.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_rule_SOURCES = crm_rule.c crm_rule_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/common/libcrmcommon.la iso8601_SOURCES = iso8601.c iso8601_LDADD = $(top_builddir)/lib/common/libcrmcommon.la attrd_updater_SOURCES = attrd_updater.c attrd_updater_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_ticket_SOURCES = crm_ticket.c crm_ticket_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la stonith_admin_SOURCES = stonith_admin.c stonith_admin_LDADD = $(top_builddir)/lib/common/libcrmcommon.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la if BUILD_SERVICELOG notifyServicelogEvent_SOURCES = notifyServicelogEvent.c notifyServicelogEvent_CFLAGS = $(SERVICELOG_CFLAGS) notifyServicelogEvent_LDADD = $(top_builddir)/lib/common/libcrmcommon.la $(SERVICELOG_LIBS) endif if BUILD_OPENIPMI_SERVICELOG ipmiservicelogd_SOURCES = ipmiservicelogd.c ipmiservicelogd_CFLAGS = $(OPENIPMI_SERVICELOG_CFLAGS) $(SERVICELOG_CFLAGS) ipmiservicelogd_LDFLAGS = $(top_builddir)/lib/common/libcrmcommon.la $(OPENIPMI_SERVICELOG_LIBS) $(SERVICELOG_LIBS) endif CLEANFILES = $(man8_MANS) diff --git a/tools/stonith_admin.8.inc b/tools/stonith_admin.8.inc new file mode 100644 index 0000000000..681ed1f4aa --- /dev/null +++ b/tools/stonith_admin.8.inc @@ -0,0 +1,5 @@ +[=synopsis] +stonith_admin <command> [<options>] + +/Access the Pacemaker fencing API/ +.SH OPTIONS diff --git a/tools/stonith_admin.c b/tools/stonith_admin.c index a7551fdc1f..df014dba31 100644 --- a/tools/stonith_admin.c +++ b/tools/stonith_admin.c @@ -1,727 +1,828 @@ /* * 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 struct crm_option long_options[] = { - { "help", no_argument, NULL, '?', - "\tDisplay this text and exit." - }, - { "version", no_argument, NULL, '$', - "\tDisplay version information and exit." - }, - { "verbose", no_argument, NULL, 'V', - "\tIncrease debug output (may be specified multiple times)." - }, - { "quiet", no_argument, NULL, 'q', - "\tBe less descriptive in output." - }, - { "cleanup", no_argument, NULL, 'c', - "\tCleanup wherever appropriate. Requires: --history." - }, - { "broadcast", no_argument, NULL, 'b', - "Broadcast wherever appropriate." - }, - PCMK__OUTPUT_OPTIONS("text, xml"), - { "-spacer-", no_argument, NULL, '-', "\nDevice definition commands:" }, - - { "register", required_argument, NULL, 'R', - "Register the named stonith device. Requires: --agent.\n" - "\t\t\tOptional: --option, --env-option." - }, - { "deregister", required_argument, NULL, 'D', - "De-register the named stonith device." - }, - { "register-level", required_argument, NULL, 'r', - "Register a stonith level for the named target,\n" - "\t\t\tspecified as one of NAME, @PATTERN, or ATTR=VALUE.\n" - "\t\t\tRequires: --index and one or more --device entries." - }, - { "deregister-level", required_argument, NULL, 'd', - "Unregister a stonith level for the named target,\n" - "\t\t\tspecified as for --register-level. Requires: --index." - }, - - { "-spacer-", no_argument, NULL, '-', "\nQueries:" }, - - { "list", required_argument, NULL, 'l', - "List devices that can terminate the specified host.\n" - "\t\t\tOptional: --timeout." - }, - { "list-registered", no_argument, NULL, 'L', - "List all registered devices. Optional: --timeout." - }, - { "list-installed", no_argument, NULL, 'I', - "List all installed devices. Optional: --timeout." - }, - { "list-targets", required_argument, NULL, 's', - "List the targets that can be fenced by the\n" - "\t\t\tnamed device. Optional: --timeout." - }, - { "metadata", no_argument, NULL, 'M', - "\tShow agent metadata. Requires: --agent.\n" - "\t\t\tOptional: --timeout." - }, - { "query", required_argument, NULL, 'Q', - "Check the named device's status. Optional: --timeout." - }, - { "history", required_argument, NULL, 'H', - "Show last successful fencing operation for named node\n" - "\t\t\t(or '*' for all nodes). Optional: --timeout, --cleanup,\n" - "\t\t\t--quiet (show only the operation's epoch timestamp),\n" - "\t\t\t--verbose (show all recorded and pending operations),\n" - "\t\t\t--broadcast (update history from all nodes available)." - }, - { "last", required_argument, NULL, 'h', - "Indicate when the named node was last fenced.\n" - "\t\t\tOptional: --as-node-id." - }, - { "validate", no_argument, NULL, 'K', - "\tValidate a fence device configuration.\n" - "\t\t\tRequires: --agent. Optional: --option, --env-option,\n" - "\t\t\t--quiet (print no output, only return status).\n" - }, - - { "-spacer-", no_argument, NULL, '-', "\nFencing Commands:" }, - - { "fence", required_argument, NULL, 'F', - "Fence named host. Optional: --timeout, --tolerance." - }, - { "unfence", required_argument, NULL, 'U', - "Unfence named host. Optional: --timeout, --tolerance." - }, - { "reboot", required_argument, NULL, 'B', - "Reboot named host. Optional: --timeout, --tolerance." - }, - { "confirm", required_argument, NULL, 'C', - "Tell cluster that named host is now safely down." - }, - - { "-spacer-", no_argument, NULL, '-', "\nAdditional Options:" }, - - { "agent", required_argument, NULL, 'a', - "The agent to use (for example, fence_xvm;\n" - "\t\t\twith --register, --metadata, --validate)." - }, - { "option", required_argument, NULL, 'o', - "Specify a device configuration parameter as NAME=VALUE\n" - "\t\t\t(may be specified multiple times; with --register,\n" - "\t\t\t--validate)." - }, - { "env-option", required_argument, NULL, 'e', - "Specify a device configuration parameter with the\n" - "\t\t\tspecified name, using the value of the\n" - "\t\t\tenvironment variable of the same name prefixed with\n" - "\t\t\tOCF_RESKEY_ (may be specified multiple times;\n" - "\t\t\twith --register, --validate)." - }, - { "tag", required_argument, NULL, 'T', - "Identify fencing operations in logs with the specified\n" - "\t\t\ttag; useful when multiple entities might invoke\n" - "\t\t\tstonith_admin (used with most commands)." - }, - { "device", required_argument, NULL, 'v', - "Device ID (with --register-level, device to associate with\n" - "\t\t\ta given host and level; may be specified multiple times)" +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\t\t\t(with --validate, name to use to load CIB secrets)" + "\n" INDENT "(with --validate, name to use to load CIB secrets)" #endif - "." - }, - { "index", required_argument, NULL, 'i', - "The stonith level (1-9) (with --register-level,\n" - "\t\t\t--deregister-level)." - }, - { "timeout", required_argument, NULL, 't', - "Operation timeout in seconds (default 120;\n" - "\t\t\tused with most commands)." - }, - { "as-node-id", no_argument, NULL, 'n', - "(Advanced) The supplied node is the corosync node ID\n" - "\t\t\t(with --last)." - }, - { "tolerance", required_argument, NULL, 0, - "(Advanced) Do nothing if an equivalent --fence request\n" - "\t\t\tsucceeded less than this many seconds earlier\n" - "\t\t\t(with --fence, --unfence, --reboot)." - }, - - { 0, 0, 0, 0 } + ".", + "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 flag; int rc = 0; - int quiet = 0; - int cleanup = 0; - int broadcast = 0; - int verbose = 0; - int argerr = 0; - int timeout = 120; - int option_index = 0; - int fence_level = 0; - int no_connect = 0; - int tolerance = 0; - int as_nodeid = FALSE; + bool no_connect = false; bool required_agent = false; - char *name = NULL; - char *value = NULL; char *target = NULL; char *lists = NULL; - const char *agent = NULL; const char *device = NULL; - const char *longname = NULL; - char action = 0; crm_exit_t exit_code = CRM_EX_OK; stonith_t *st = NULL; - stonith_key_value_t *params = NULL; - stonith_key_value_t *devices = NULL; stonith_key_value_t *dIter = NULL; - char *output_ty = NULL; - char *output_dest = NULL; pcmk__output_t *out = NULL; + pcmk__common_args_t *args = calloc(1, sizeof(pcmk__common_args_t)); + + GError *error = NULL; + GOptionContext *context = NULL; + gchar **argv_copy = 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"); - crm_set_options(NULL, "<command> [<options>]", long_options, - "access the Pacemaker fencing API"); async_fence_data.name = strdup(crm_system_name); - while (1) { - flag = crm_get_option_long(argc, argv, &option_index, &longname); - if (flag == -1) - break; + /* g_option_context_parse will remove items from argv as it proceses them, + * but we also need the list of arguments to pass to pcmk__output_new. + * So make a copy here and free it when the program quits. + */ + argv_copy = g_strdupv(argv); - switch (flag) { - case 'V': - verbose = 1; - crm_bump_log_level(argc, argv); - break; - case '$': - case '?': - crm_help(flag, CRM_EX_OK); - break; + if (!g_option_context_parse(context, &argc, &argv, &error)) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + } - case 'K': - required_agent = true; - /* fall through */ - case 'I': - no_connect = 1; - /* fall through */ - case 'L': - action = flag; - break; + for (int i = 0; i < options.verbose; i++) { + crm_bump_log_level(argc, argv); + } - case 'q': - quiet = 1; - break; - case 'c': - cleanup = 1; - break; - case 'b': - broadcast = 1; - break; - case 'R': - required_agent = true; - /* fall through */ - case 'Q': - case 'D': - case 's': - action = flag; - device = optarg; - break; - case 'T': - free(async_fence_data.name); - async_fence_data.name = crm_strdup_printf("%s.%s", crm_system_name, optarg); - break; - case 'a': - agent = optarg; - break; - case 'l': - target = optarg; - action = 'L'; - break; - case 'M': - no_connect = 1; - action = flag; - required_agent = true; - break; - case 't': - timeout = crm_atoi(optarg, NULL); - break; - case 'B': - case 'F': - case 'U': - /* using mainloop here */ - no_connect = 1; - /* fall through */ - case 'C': - /* Always log the input arguments */ - crm_log_args(argc, argv); - target = optarg; - action = flag; - break; - case 'n': - as_nodeid = TRUE; - break; - case 'h': - case 'H': - case 'r': - case 'd': - target = optarg; - action = flag; - break; - case 'i': - fence_level = crm_atoi(optarg, NULL); - break; - case 'v': - devices = stonith_key_value_add(devices, NULL, optarg); - break; - case 'o': - crm_info("Scanning: -o %s", optarg); - rc = pcmk_scan_nvpair(optarg, &name, &value); + pcmk__register_formats(context, formats); - if (rc != 2) { - crm_err("Invalid option: -o %s: %s", optarg, pcmk_strerror(rc)); - ++argerr; - } else { - crm_info("Got: '%s'='%s'", name, value); - params = stonith_key_value_add(params, name, value); - } - free(value); value = NULL; - free(name); name = NULL; - break; - case 'e': - { - char *key = crm_concat("OCF_RESKEY", optarg, '_'); - const char *env = getenv(key); - - if (env == NULL) { - crm_err("Invalid option: -e %s", optarg); - ++argerr; - } else { - crm_info("Got: '%s'='%s'", optarg, env); - params = stonith_key_value_add(params, optarg, env); - } - free(key); - } - break; - case 0: - if (safe_str_eq("tolerance", longname)) { - tolerance = crm_get_msec(optarg) / 1000; /* Send in seconds */ - } else if (pcmk__parse_output_args(longname, optarg, &output_ty, - &output_dest) == false) { - fprintf(stderr, "Unknown long option used: %s\n", longname); - ++argerr; - } + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv_copy); + 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; + } - break; - default: - ++argerr; - break; - } + stonith__register_messages(out); + + if (args->version) { + out->version(out, false); + goto done; } - if (optind > argc || action == 0) { - ++argerr; + if (options.validate_cfg) { + required_agent = true; + no_connect = true; + action = 'K'; } - if (required_agent && agent == NULL) { - fprintf(stderr, "Please specify an agent to query using -a,--agent [value]\n"); - ++argerr; + if (options.installed) { + no_connect = true; + action = 'I'; } - if (argerr) { - crm_help('?', CRM_EX_USAGE); + if (options.registered) { + action = 'L'; } - CRM_ASSERT(pcmk__register_format("text", pcmk__mk_text_output) == 0); - CRM_ASSERT(pcmk__register_format("xml", pcmk__mk_xml_output) == 0); + if (options.register_dev != NULL) { + required_agent = true; + action = 'R'; + device = options.register_dev; + } - rc = pcmk__output_new(&out, output_ty, output_dest, argv); - if (rc != 0) { - fprintf(stderr, "Error creating output format %s: %s\n", output_ty, pcmk_strerror(rc)); - exit_code = CRM_EX_ERROR; + 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_copy); + } + + if (options.fence_host != NULL) { + no_connect = true; + action = 'F'; + target = options.fence_host; + crm_log_args(argc, argv_copy); + } + + if (options.unfence_host != NULL) { + no_connect = true; + action = 'U'; + target = options.unfence_host; + crm_log_args(argc, argv_copy); + } + + if (options.confirm_host != NULL) { + action = 'C'; + target = options.confirm_host; + crm_log_args(argc, argv_copy); + } + + 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; } - stonith__register_messages(out); + 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) { - 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, &devices, timeout); + 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 = devices; dIter; dIter = dIter->next) { + 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(devices, 1, 1); + stonith_key_value_freeall(options.devices, 1, 1); break; case 'L': - rc = st->cmds->query(st, st_opts, target, &devices, timeout); + 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 = devices; dIter; dIter = dIter->next) { + 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(devices, 1, 1); + stonith_key_value_freeall(options.devices, 1, 1); break; case 'Q': - rc = st->cmds->monitor(st, st_opts, device, timeout); + rc = st->cmds->monitor(st, st_opts, device, options.timeout); if (rc < 0) { - rc = st->cmds->list(st, st_opts, device, NULL, timeout); + rc = st->cmds->list(st, st_opts, device, NULL, options.timeout); } break; case 's': - rc = st->cmds->list(st, st_opts, device, &lists, timeout); + 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, agent, - params); + 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, fence_level, devices, action == '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, agent, NULL, &buffer, timeout); + 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", timeout, tolerance); + rc = mainloop_fencing(st, target, "reboot", options.timeout, options.tolerance); break; case 'F': - rc = mainloop_fencing(st, target, "off", timeout, tolerance); + rc = mainloop_fencing(st, target, "off", options.timeout, options.tolerance); break; case 'U': - rc = mainloop_fencing(st, target, "on", timeout, tolerance); + rc = mainloop_fencing(st, target, "on", options.timeout, options.tolerance); break; case 'h': { time_t when = 0; - if(as_nodeid) { + 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, timeout, quiet, - verbose, cleanup, broadcast, out); + rc = handle_history(st, target, options.timeout, args->quiet, + options.verbose, options.cleanup, + options.broadcast, out); break; case 'K': - device = (devices? devices->key : NULL); - rc = validate(st, agent, device, params, timeout, quiet, out); + 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); - pcmk__output_free(out, exit_code); - done: + g_strfreev(argv_copy); + g_option_context_free(context); + if (out != NULL) { + pcmk__output_free(out, exit_code); + } free(async_fence_data.name); - stonith_key_value_freeall(params, 1, 1); + 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..ffa6a3f610 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_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>' >> $@ - echo ' <element name="status">' >> $@ - echo ' <attribute name="code"> <data type="integer" /> </attribute>' >> $@ - echo ' <text />' >> $@ - echo ' </element>' >> $@ + $(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/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 @@ +<?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-status"/> + </start> + + <define name="element-status"> + <element name="status"> + <attribute name="code"> <data type="integer" /> </attribute> + <attribute name="message"> <text /> </attribute> + <optional> + <element name="errors"> + <oneOrMore> + <element name="error"> + <text /> + </element> + </oneOrMore> + </element> + </optional> + </element> + </define> + +</grammar> 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>