diff --git a/.gitignore b/.gitignore index a2db24306b..cb6bb309a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,216 +1,217 @@ # Common \#* .\#* GPATH GRTAGS GTAGS TAGS Makefile Makefile.in .deps .dirstamp .libs *.pc *.pyc *.bz2 *.tar.gz *.rpm *.la *.lo *.o *~ *.gcda *.gcno # Autobuild aclocal.m4 autoconf autoheader autom4te.cache/ automake build.counter compile config.guess config.log config.status config.sub configure depcomp install-sh include/stamp-* libtool libtool.m4 ltdl.m4 libltdl ltmain.sh missing py-compile /m4/argz.m4 /m4/ltargz.m4 /m4/ltoptions.m4 /m4/ltsugar.m4 /m4/ltversion.m4 /m4/lt~obsolete.m4 test-driver ylwrap # Configure targets /cts/CTS.py /cts/CTSlab.py /cts/CTSvars.py /cts/LSBDummy /cts/OCFIPraTest.py /cts/benchmark/clubench /cts/cluster_test /cts/cts /cts/cts-cli /cts/cts-coverage /cts/cts-exec /cts/cts-fencing /cts/cts-log-watcher /cts/cts-regression /cts/cts-scheduler /cts/cts-support /cts/fence_dummy /cts/lxc_autogen.sh /cts/pacemaker-cts-dummyd /cts/pacemaker-cts-dummyd@.service /daemons/execd/pacemaker_remote /daemons/execd/pacemaker_remote.service /daemons/fenced/fence_legacy /daemons/pacemakerd/pacemaker /daemons/pacemakerd/pacemaker.combined.upstart /daemons/pacemakerd/pacemaker.service /daemons/pacemakerd/pacemaker.upstart /doc/Doxyfile /extra/logrotate/pacemaker /extra/resources/ClusterMon /extra/resources/HealthSMART /extra/resources/SysInfo /extra/resources/ifspeed /extra/resources/o2cb include/config.h include/config.h.in include/crm_config.h publican.cfg /tools/cibsecret /tools/crm_error /tools/crm_failcount /tools/crm_master /tools/crm_mon.service /tools/crm_mon.upstart /tools/crm_report /tools/crm_rule /tools/crm_standby /tools/report.collector /tools/report.common # Build targets *.7 *.7.xml *.7.html *.8 *.8.xml *.8.html /daemons/attrd/pacemaker-attrd /daemons/based/pacemaker-based /daemons/based/cibmon /daemons/controld/pacemaker-controld /daemons/execd/cts-exec-helper /daemons/execd/pacemaker-execd /daemons/execd/pacemaker-remoted /daemons/fenced/cts-fence-helper /daemons/fenced/pacemaker-fenced /daemons/fenced/pacemaker-fenced.xml /daemons/pacemakerd/pacemakerd /daemons/schedulerd/pacemaker-schedulerd /daemons/schedulerd/pacemaker-schedulerd.xml /doc/*/tmp/** /doc/*/publish /doc/*.build /doc/*/en-US/Ap-*.xml /doc/*/en-US/Ch-*.xml /doc/.ABI-build /doc/HTML /doc/abi_dumps /doc/abi-check /doc/acls.html /doc/api/* /doc/compat_reports /doc/crm_fencing.html /doc/publican-catalog* /doc/shared/en-US/*.xml /doc/shared/en-US/images/pcmk-*.png /doc/shared/en-US/images/Policy-Engine-*.png /maint/testcc scratch /tools/attrd_updater /tools/cibadmin /tools/crmadmin /tools/crm_attribute /tools/crm_diff /tools/crm_mon /tools/crm_node /tools/crm_resource /tools/crm_shadow /tools/crm_simulate /tools/crm_ticket /tools/crm_verify /tools/iso8601 /tools/stonith_admin xml/crm.dtd xml/pacemaker*.rng xml/versions.rng +xml/api/api-result*.rng lib/gnu/libgnu.a lib/gnu/stdalign.h *.coverity # Test detritus /cts/.regression.failed.diff /cts/scheduler/*.ref /cts/scheduler/*.up /cts/scheduler/*.up.err /cts/scheduler/bug-rh-1097457.log /cts/scheduler/bug-rh-1097457.trs /cts/scheduler/shadow.* /cts/test-suite.log /xml/test-*/*.up /xml/test-*/*.up.err /xml/assets/*.rng /xml/assets/diffview.js /xml/assets/xmlcatalog # Release maintenance detritus /maint/gnulib # Formerly built files (helps when jumping back and forth in checkout) /.ABI-build /Doxyfile /HTML /abi_dumps /abi-check /compat_reports /attrd /cib /coverage.sh /crmd /cts/HBDummy /doc/Clusters_from_Scratch.txt /doc/Pacemaker_Explained.txt /fencing /lrmd /mcp /pengine #Other mock pacemaker*.spec coverity-* logs *.patch *.diff *.sed *.orig *.rej *.swp diff --git a/include/crm/common/output.h b/include/crm/common/output.h new file mode 100644 index 0000000000..f31b78430f --- /dev/null +++ b/include/crm/common/output.h @@ -0,0 +1,513 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef CRM_OUTPUT__H +# define CRM_OUTPUT__H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file + * \brief Formatted output for pacemaker tools + */ + +# include +# include + +# include +# include + +# 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" \ + } + +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. + */ +pcmk__output_t *pcmk__mk_text_output(char **argv); +pcmk__output_t *pcmk__mk_xml_output(char **argv); + +/*! + * \brief This structure contains everything that makes up a single output + * formatter. + * + * Instances of this structure may be created by calling pcmk__output_new() + * with the name of the desired formatter. They should later be freed with + * pcmk__output_free(). + */ +struct pcmk__output_s { + /*! + * \brief The name of this output formatter. + */ + char *fmt_name; + + /*! + * \brief A copy of the request that generated this output. + * + * In the case of command line usage, this would be the command line + * arguments. For other use cases, it could be different. + */ + char *request; + + /*! + * \brief Does this formatter support a special quiet mode? + * + * In this mode, most output can be supressed but some information is still + * displayed to an interactive user. In general, machine-readable output + * formats will not support this while user-oriented formats will. + */ + bool supports_quiet; + + /*! + * \brief Where output should be written. + * + * This could be a file handle, or stdout or stderr. This is really only + * useful internally. + */ + FILE *dest; + + /*! + * \brief Custom messages that are currently registered on this formatter. + * + * Keys are the string message IDs, values are ::pcmk__message_fn_t function + * pointers. + */ + GHashTable *messages; + + /*! + * \brief Implementation-specific private data. + * + * Each individual formatter may have some private data useful in its + * implementation. This points to that data. Callers should not rely on + * its contents or structure. + */ + void *priv; + + /*! + * \internal + * \brief Take whatever actions are necessary to prepare out for use. This is + * called by pcmk__output_new(). End users should not need to call this. + * + * \note For formatted output implementers - This function should be written in + * such a way that it can be called repeatedly on an already initialized + * object without causing problems, or on a previously finished object + * without crashing. + * + * \param[in,out] out The output functions structure. + * + * \return true on success, false on error. + */ + bool (*init) (pcmk__output_t *out); + + /*! + * \internal + * \brief Free the private formatter-specific data. + * + * This is called from pcmk__output_free() and does not typically need to be + * called directly. + * + * \param[in,out] out The output functions structure. + */ + void (*free_priv)(pcmk__output_t *out); + + /*! + * \internal + * \brief Take whatever actions are necessary to end formatted output. + * + * This could include flushing output to a file, but does not include freeing + * anything. Note that pcmk__output_free() will automatically call this + * function, so there is typically no need to do so manually. + * + * \note For formatted output implementers - This function should be written in + * such a way that it can be called repeatedly on a previously finished + * object without crashing. + * + * \param[in,out] out The output functions structure. + * \param[in] exit_status The exit value of the whole program. + */ + void (*finish) (pcmk__output_t *out, crm_exit_t exit_status); + + /*! + * \internal + * \brief Finalize output and then immediately set back up to start a new set + * of output. + * + * This is conceptually the same as calling finish and then init, though in + * practice more be happening behind the scenes. + * + * \note This function differs from finish in that no exit_status is added. + * The idea is that the program is not shutting down, so there is not + * yet a final exit code. Call finish on the last time through if this + * is needed. + * + * \param[in,out] out The output functions structure. + */ + void (*reset) (pcmk__output_t *out); + + /*! + * \internal + * \brief Register a custom message. + * + * \param[in,out] out The output functions structure. + * \param[in] message_id The name of the message to register. This name + * will be used as the message_id parameter to the + * message function in order to call the custom + * format function. + * \param[in] fn The custom format function to call for message_id. + */ + void (*register_message) (pcmk__output_t *out, const char *message_id, + pcmk__message_fn_t fn); + + /*! + * \internal + * \brief Call a previously registered custom message. + * + * \param[in,out] out The output functions structure. + * \param[in] message_id The name of the message to call. This name must + * be the same as the message_id parameter of some + * previous call to register_message. + * \param[in] ... Arguments to be passed to the registered function. + * + * \return 0 if a function was registered for the message, that function was + * called, and returned successfully. A negative value is returned if + * no function was registered. A positive value is returned if the + * function was called but encountered an error. + */ + int (*message) (pcmk__output_t *out, const char *message_id, ...); + + /*! + * \internal + * \brief Format the output of a completed subprocess. + * + * \param[in,out] out The output functions structure. + * \param[in] exit_status The exit value of the subprocess. + * \param[in] proc_stdout stdout from the completed subprocess. + * \param[in] proc_stderr stderr from the completed subprocess. + */ + void (*subprocess_output) (pcmk__output_t *out, int exit_status, + const char *proc_stdout, const char *proc_stderr); + + /*! + * \internal + * \brief Format an informational message that should be shown to + * to an interactive user. Not all formatters will do this. + * + * \note A newline will automatically be added to the end of the format + * string, so callers should not include a newline. + * + * \param[in,out] out The output functions structure. + * \param[in] buf The message to be printed. + * \param[in] ... Arguments to be formatted. + */ + void (*info) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); + + /*! + * \internal + * \brief Format 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. + * + * \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 "-". + * + * \return true if longname was handled, false otherwise. + */ +bool +pcmk__parse_output_args(const char *argname, char *argvalue, char **output_ty, + char **output_dest); + +/*! + * \internal + * \brief Register a new output formatter, making it available for use + * the same as a base formatter. + * + * \param[in] fmt The new output formatter to register. + * + * \return 0 on success or an error code on error. + */ +int +pcmk__register_format(const char *fmt_name, pcmk__output_factory_t create); + + +/*! + * \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); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/crm/common/results.h b/include/crm/common/results.h index 4b16c45b6b..7d8e2601fe 100644 --- a/include/crm/common/results.h +++ b/include/crm/common/results.h @@ -1,144 +1,145 @@ /* * Copyright 2012-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_RESULTS__H # define CRM_RESULTS__H #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief Function and executable result codes * \ingroup core */ # define CRM_ASSERT(expr) do { \ if(__unlikely((expr) == FALSE)) { \ crm_abort(__FILE__, __FUNCTION__, __LINE__, #expr, TRUE, FALSE); \ abort(); /* Redundant but it makes static analyzers happy */ \ } \ } while(0) /* * Function return codes * * For system error codes, see: * - /usr/include/asm-generic/errno.h * - /usr/include/asm-generic/errno-base.h */ # define pcmk_ok 0 # define PCMK_ERROR_OFFSET 190 /* Replacements on non-linux systems, see include/portability.h */ # define PCMK_CUSTOM_OFFSET 200 /* Purely custom codes */ # define pcmk_err_generic 201 # define pcmk_err_no_quorum 202 # define pcmk_err_schema_validation 203 # define pcmk_err_transform_failed 204 # define pcmk_err_old_data 205 # define pcmk_err_diff_failed 206 # define pcmk_err_diff_resync 207 # define pcmk_err_cib_modified 208 # define pcmk_err_cib_backup 209 # define pcmk_err_cib_save 210 # define pcmk_err_schema_unchanged 211 # define pcmk_err_cib_corrupt 212 # define pcmk_err_multiple 213 # define pcmk_err_node_unknown 214 # define pcmk_err_already 215 # define pcmk_err_bad_nvpair 216 +# define pcmk_err_unknown_format 217 /* * Exit status codes * * We want well-specified (i.e. OS-invariant) exit status codes for our daemons * and applications so they can be relied on by callers. (Function return codes * and errno's do not make good exit statuses.) * * The only hard rule is that exit statuses must be between 0 and 255; all else * is convention. Universally, 0 is success, and 1 is generic error (excluding * OSes we don't support -- for example, OpenVMS considers 1 success!). * * For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for * application use. OCF adds 8-9 and 189-199. * * sysexits.h was an attempt to give additional meanings, but never really * caught on. It uses 0 and 64-78. * * Bash reserves 2 ("incorrect builtin usage") and 126-255 (126 is "command * found but not executable", 127 is "command not found", 128 + n is * "interrupted by signal n"). * * tldp.org recommends 64-113 for application use. * * We try to overlap with the above conventions when practical. */ typedef enum crm_exit_e { // Common convention CRM_EX_OK = 0, CRM_EX_ERROR = 1, // LSB + OCF CRM_EX_INVALID_PARAM = 2, CRM_EX_UNIMPLEMENT_FEATURE = 3, CRM_EX_INSUFFICIENT_PRIV = 4, CRM_EX_NOT_INSTALLED = 5, CRM_EX_NOT_CONFIGURED = 6, CRM_EX_NOT_RUNNING = 7, // sysexits.h CRM_EX_USAGE = 64, // command line usage error CRM_EX_DATAERR = 65, // user-supplied data incorrect CRM_EX_NOINPUT = 66, // input file not available CRM_EX_NOUSER = 67, // user does not exist CRM_EX_NOHOST = 68, // host unknown CRM_EX_UNAVAILABLE = 69, // needed service unavailable CRM_EX_SOFTWARE = 70, // internal software bug CRM_EX_OSERR = 71, // external (OS/environmental) problem CRM_EX_OSFILE = 72, // system file not usable CRM_EX_CANTCREAT = 73, // file couldn't be created CRM_EX_IOERR = 74, // file I/O error CRM_EX_TEMPFAIL = 75, // try again CRM_EX_PROTOCOL = 76, // protocol violated CRM_EX_NOPERM = 77, // non-file permission issue CRM_EX_CONFIG = 78, // misconfiguration // Custom CRM_EX_FATAL = 100, // do not respawn CRM_EX_PANIC = 101, // panic the local host CRM_EX_DISCONNECT = 102, // lost connection to something CRM_EX_OLD = 103, // update older than existing config CRM_EX_DIGEST = 104, // digest comparison failed CRM_EX_NOSUCH = 105, // requested item does not exist CRM_EX_QUORUM = 106, // local partition does not have quorum CRM_EX_UNSAFE = 107, // requires --force or new conditions CRM_EX_EXISTS = 108, // requested item already exists CRM_EX_MULTIPLE = 109, // requested item has multiple matches CRM_EX_EXPIRED = 110, // requested item has expired CRM_EX_NOT_YET_IN_EFFECT = 111, // requested item is not in effect CRM_EX_INDETERMINATE = 112, // could not determine status // Other CRM_EX_TIMEOUT = 124, // convention from timeout(1) CRM_EX_MAX = 255, // ensure crm_exit_t can hold this } crm_exit_t; const char *pcmk_strerror(int rc); const char *pcmk_errorname(int rc); const char *bz2_strerror(int rc); crm_exit_t crm_errno2exit(int rc); const char *crm_exit_name(crm_exit_t exit_code); const char *crm_exit_str(crm_exit_t exit_code); _Noreturn crm_exit_t crm_exit(crm_exit_t rc); #ifdef __cplusplus } #endif #endif diff --git a/include/crm/stonith-ng.h b/include/crm/stonith-ng.h index 04650e598a..a10b8b3448 100644 --- a/include/crm/stonith-ng.h +++ b/include/crm/stonith-ng.h @@ -1,550 +1,559 @@ /* * 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 Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Fencing aka. STONITH * \ingroup fencing */ #ifndef STONITH_NG__H # define STONITH_NG__H # include # include # include // bool # include // uint32_t # include // time_t +# include + # define T_STONITH_NOTIFY_DISCONNECT "st_notify_disconnect" # define T_STONITH_NOTIFY_FENCE "st_notify_fence" # define T_STONITH_NOTIFY_HISTORY "st_notify_history" /* *INDENT-OFF* */ enum stonith_state { stonith_connected_command, stonith_connected_query, stonith_disconnected, }; enum stonith_call_options { st_opt_none = 0x00000000, st_opt_verbose = 0x00000001, st_opt_allow_suicide = 0x00000002, st_opt_manual_ack = 0x00000008, st_opt_discard_reply = 0x00000010, /* st_opt_all_replies = 0x00000020, */ st_opt_topology = 0x00000040, st_opt_scope_local = 0x00000100, st_opt_cs_nodeid = 0x00000200, st_opt_sync_call = 0x00001000, /*! Allow the timeout period for a callback to be adjusted * based on the time the server reports the operation will take. */ st_opt_timeout_updates = 0x00002000, /*! Only report back if operation is a success in callback */ st_opt_report_only_success = 0x00004000, /* used where ever apropriate - e.g. cleanup of history */ st_opt_cleanup = 0x000080000, /* used where ever apropriate - e.g. send out a history query to all nodes */ st_opt_broadcast = 0x000100000, }; /*! Order matters here, do not change values */ enum op_state { st_query, st_exec, st_done, st_duplicate, st_failed, }; // Supported fence agent interface standards enum stonith_namespace { st_namespace_invalid, st_namespace_any, st_namespace_internal, // Implemented internally by Pacemaker /* Neither of these projects are active any longer, but the fence agent * interfaces they created are still in use and supported by Pacemaker. */ st_namespace_rhcs, // Red Hat Cluster Suite compatible st_namespace_lha, // Linux-HA compatible }; enum stonith_namespace stonith_text2namespace(const char *namespace_s); const char *stonith_namespace2text(enum stonith_namespace st_namespace); enum stonith_namespace stonith_get_namespace(const char *agent, const char *namespace_s); typedef struct stonith_key_value_s { char *key; char *value; struct stonith_key_value_s *next; } stonith_key_value_t; typedef struct stonith_history_s { char *target; char *action; char *origin; char *delegate; char *client; int state; time_t completed; struct stonith_history_s *next; } stonith_history_t; typedef struct stonith_s stonith_t; typedef struct stonith_event_s { char *id; char *type; char *message; char *operation; int result; char *origin; char *target; char *action; char *executioner; char *device; /*! The name of the client that initiated the action. */ char *client_origin; } stonith_event_t; typedef struct stonith_callback_data_s { int rc; int call_id; void *userdata; } stonith_callback_data_t; typedef struct stonith_api_operations_s { /*! * \brief Destroy the stonith api structure. */ int (*free) (stonith_t *st); /*! * \brief Connect to the local stonith daemon. * * \retval 0, success * \retval negative error code on failure */ int (*connect) (stonith_t *st, const char *name, int *stonith_fd); /*! * \brief Disconnect from the local stonith daemon. * * \retval 0, success * \retval negative error code on failure */ int (*disconnect)(stonith_t *st); /*! * \brief Remove a registered stonith device with the local stonith daemon. * * \note Synchronous, guaranteed to occur in daemon before function returns. * * \retval 0, success * \retval negative error code on failure */ int (*remove_device)( stonith_t *st, int options, const char *name); /*! * \brief Register a stonith device with the local stonith daemon. * * \note Synchronous, guaranteed to occur in daemon before function returns. * * \retval 0, success * \retval negative error code on failure */ int (*register_device)( stonith_t *st, int options, const char *id, const char *provider, const char *agent, stonith_key_value_t *params); /*! * \brief Remove a fencing level for a specific node. * * \note This feature is not available when stonith is in standalone mode. * * \retval 0, success * \retval negative error code on failure */ int (*remove_level)( stonith_t *st, int options, const char *node, int level); /*! * \brief Register a fencing level containing the fencing devices to be used * at that level for a specific node. * * \note This feature is not available when stonith is in standalone mode. * * \retval 0, success * \retval negative error code on failure */ int (*register_level)( stonith_t *st, int options, const char *node, int level, stonith_key_value_t *device_list); /*! * \brief Get the metadata documentation for a resource. * * \note Value is returned in output. Output must be freed when set. * * \retval 0 success * \retval negative error code on failure */ int (*metadata)(stonith_t *st, int options, const char *device, const char *provider, char **output, int timeout); /*! * \brief Retrieve a list of installed stonith agents * * \note if provider is not provided, all known agents will be returned * \note list must be freed using stonith_key_value_freeall() * \note call_options parameter is not used, it is reserved for future use. * * \retval num items in list on success * \retval negative error code on failure */ int (*list_agents)(stonith_t *stonith, int call_options, const char *provider, stonith_key_value_t **devices, int timeout); /*! * \brief Retrieve string listing hosts and port assignments from a local stonith device. * * \retval 0 on success * \retval negative error code on failure */ int (*list)(stonith_t *st, int options, const char *id, char **list_output, int timeout); /*! * \brief Check to see if a local stonith device is reachable * * \retval 0 on success * \retval negative error code on failure */ int (*monitor)(stonith_t *st, int options, const char *id, int timeout); /*! * \brief Check to see if a local stonith device's port is reachable * * \retval 0 on success * \retval negative error code on failure */ int (*status)(stonith_t *st, int options, const char *id, const char *port, int timeout); /*! * \brief Retrieve a list of registered stonith devices. * * \note If node is provided, only devices that can fence the node id * will be returned. * * \retval num items in list on success * \retval negative error code on failure */ int (*query)(stonith_t *st, int options, const char *node, stonith_key_value_t **devices, int timeout); /*! * \brief Issue a fencing action against a node. * * \note Possible actions are, 'on', 'off', and 'reboot'. * * \param st, stonith connection * \param options, call options * \param node, The target node to fence * \param action, The fencing action to take * \param timeout, The default per device timeout to use with each device * capable of fencing the target. * * \retval 0 success * \retval negative error code on failure. */ int (*fence)(stonith_t *st, int options, const char *node, const char *action, int timeout, int tolerance); /*! * \brief Manually confirm that a node is down. * * \retval 0 success * \retval negative error code on failure. */ int (*confirm)(stonith_t *st, int options, const char *node); /*! * \brief Retrieve a list of fencing operations that have occurred for a specific node. * * \note History is not available in standalone mode. * * \retval 0 success * \retval negative error code on failure. */ int (*history)(stonith_t *st, int options, const char *node, stonith_history_t **output, int timeout); int (*register_notification)( stonith_t *st, const char *event, void (*notify)(stonith_t *st, stonith_event_t *e)); int (*remove_notification)(stonith_t *st, const char *event); /*! * \brief Register a callback to receive the result of an asynchronous call * * \param[in] call_id The call ID to register callback for * \param[in] timeout Default time to wait until callback expires * \param[in] options Bitmask of \c stonith_call_options (respects * \c st_opt_timeout_updates and * \c st_opt_report_only_success) * \param[in] userdata Pointer that will be given to callback * \param[in] callback_name Unique name to identify callback * \param[in] callback The callback function to register * * \return \c TRUE on success, \c FALSE if call_id is negative, -errno otherwise * * \todo This function should return \c pcmk_ok on success, and \c call_id * when negative, but that would break backward compatibility. */ int (*register_callback)(stonith_t *st, int call_id, int timeout, int options, void *userdata, const char *callback_name, void (*callback)(stonith_t *st, stonith_callback_data_t *data)); /*! * \brief Remove a registered callback for a given call id. */ int (*remove_callback)(stonith_t *st, int call_id, bool all_callbacks); /*! * \brief Remove fencing level for specific node, node regex or attribute * * \param[in] st Fencer connection to use * \param[in] options Bitmask of stonith_call_options to pass to the fencer * \param[in] node If not NULL, target level by this node name * \param[in] pattern If not NULL, target by node name using this regex * \param[in] attr If not NULL, target by this node attribute * \param[in] value If not NULL, target by this node attribute value * \param[in] level Index number of level to remove * * \return 0 on success, negative error code otherwise * * \note This feature is not available when stonith is in standalone mode. * The caller should set only one of node, pattern or attr/value. */ int (*remove_level_full)(stonith_t *st, int options, const char *node, const char *pattern, const char *attr, const char *value, int level); /*! * \brief Register fencing level for specific node, node regex or attribute * * \param[in] st Fencer connection to use * \param[in] options Bitmask of stonith_call_options to pass to fencer * \param[in] node If not NULL, target level by this node name * \param[in] pattern If not NULL, target by node name using this regex * \param[in] attr If not NULL, target by this node attribute * \param[in] value If not NULL, target by this node attribute value * \param[in] level Index number of level to add * \param[in] device_list Devices to use in level * * \return 0 on success, negative error code otherwise * * \note This feature is not available when stonith is in standalone mode. * The caller should set only one of node, pattern or attr/value. */ int (*register_level_full)(stonith_t *st, int options, const char *node, const char *pattern, const char *attr, const char *value, int level, stonith_key_value_t *device_list); /*! * \brief Validate an arbitrary stonith device configuration * * \param[in] st Stonithd connection to use * \param[in] call_options Bitmask of stonith_call_options to use with fencer * \param[in] rsc_id ID used to replace CIB secrets in params * \param[in] namespace_s Namespace of fence agent to validate (optional) * \param[in] agent Fence agent to validate * \param[in] params Configuration parameters to pass to fence agent * \param[in] timeout Fail if no response within this many seconds * \param[out] output If non-NULL, where to store any agent output * \param[out] error_output If non-NULL, where to store agent error output * * \return pcmk_ok if validation succeeds, -errno otherwise * * \note If pcmk_ok is returned, the caller is responsible for freeing * the output (if requested). */ int (*validate)(stonith_t *st, int call_options, const char *rsc_id, const char *namespace_s, const char *agent, stonith_key_value_t *params, int timeout, char **output, char **error_output); } stonith_api_operations_t; struct stonith_s { enum stonith_state state; int call_id; int call_timeout; void *st_private; stonith_api_operations_t *cmds; }; /* *INDENT-ON* */ /* Core functions */ stonith_t *stonith_api_new(void); void stonith_api_delete(stonith_t * st); void stonith_dump_pending_callbacks(stonith_t * st); // deprecated (use stonith_get_namespace() instead) const char *get_stonith_provider(const char *agent, const char *provider); bool stonith_dispatch(stonith_t * st); stonith_key_value_t *stonith_key_value_add(stonith_key_value_t * kvp, const char *key, const char *value); void stonith_key_value_freeall(stonith_key_value_t * kvp, int keys, int values); void stonith_history_free(stonith_history_t *history); /* Basic helpers that allows nodes to be fenced and the history to be * queried without mainloop or the caller understanding the full API * * At least one of nodeid and uname are required */ int stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off); time_t stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress); /* * Helpers for using the above functions without install-time dependencies * * Usage: * #include * * To turn a node off by corosync nodeid: * stonith_api_kick_helper(nodeid, 120, 1); * * To check the last fence date/time (also by nodeid): * last = stonith_api_time_helper(nodeid, 0); * * To check if fencing is in progress: * if(stonith_api_time_helper(nodeid, 1) > 0) { ... } * * eg. #include #include #include int main(int argc, char ** argv) { int rc = 0; int nodeid = 102; rc = stonith_api_time_helper(nodeid, 0); printf("%d last fenced at %s\n", nodeid, ctime(rc)); rc = stonith_api_kick_helper(nodeid, 120, 1); printf("%d fence result: %d\n", nodeid, rc); rc = stonith_api_time_helper(nodeid, 0); printf("%d last fenced at %s\n", nodeid, ctime(rc)); return 0; } */ # define STONITH_LIBRARY "libstonithd.so.26" typedef int (*st_api_kick_fn) (int nodeid, const char *uname, int timeout, bool off); typedef time_t (*st_api_time_fn) (int nodeid, const char *uname, bool in_progress); static inline int stonith_api_kick_helper(uint32_t nodeid, int timeout, bool off) { static void *st_library = NULL; static st_api_kick_fn st_kick_fn; if (st_library == NULL) { st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY); } if (st_library && st_kick_fn == NULL) { st_kick_fn = (st_api_kick_fn) dlsym(st_library, "stonith_api_kick"); } if (st_kick_fn == NULL) { #ifdef ELIBACC return -ELIBACC; #else return -ENOSYS; #endif } return (*st_kick_fn) (nodeid, NULL, timeout, off); } static inline time_t stonith_api_time_helper(uint32_t nodeid, bool in_progress) { static void *st_library = NULL; static st_api_time_fn st_time_fn; if (st_library == NULL) { st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY); } if (st_library && st_time_fn == NULL) { st_time_fn = (st_api_time_fn) dlsym(st_library, "stonith_api_time"); } if (st_time_fn == NULL) { return 0; } return (*st_time_fn) (nodeid, NULL, in_progress); } /** * Does the given agent describe a stonith resource that can exist? * * \param[in] agent What is the name of the agent? * \param[in] timeout Timeout to use when querying. If 0 is given, * use a default of 120. * * \return A boolean */ bool stonith_agent_exists(const char *agent, int timeout); /*! - * \brief Turn stonith action into a more readable string + * \brief Register stonith-specific messages. + * + * \param out The output functions structure. + */ +void stonith_register_messages(pcmk__output_t *out); + +/*! + * \brief Turn stonith action into a more readable string. * - * \param[in] action Stonith action + * \param action Stonith action */ const char *stonith_action_str(const char *action); #ifdef __cplusplus } #endif #endif diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am index 78f8d4e38b..f8bebd7acd 100644 --- a/lib/common/Makefile.am +++ b/lib/common/Makefile.am @@ -1,43 +1,44 @@ # # Copyright 2004-2019 Andrew Beekhof # # 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 35:0:1 libcrmcommon_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libcrmcommon_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) libcrmcommon_la_LIBADD = @LIBADD_DL@ $(GNUTLSLIBS) libcrmcommon_la_SOURCES = compat.c digest.c ipc.c io.c procfs.c utils.c xml.c \ iso8601.c remote.c mainloop.c logging.c watchdog.c \ schemas.c strings.c xpath.c attrd_client.c alerts.c \ - operations.c pid.c results.c acl.c agents.c nvpair.c + operations.c pid.c results.c acl.c agents.c nvpair.c \ + output.c output_text.c output_xml.c if BUILD_CIBSECRETS libcrmcommon_la_SOURCES += cib_secrets.c endif #libcrmcommon_la_SOURCES += $(top_builddir)/lib/gnu/md5.c libcrmcommon_la_SOURCES += ../gnu/md5.c clean-generic: rm -f *.log *.debug *.xml *~ diff --git a/lib/common/output.c b/lib/common/output.c new file mode 100644 index 0000000000..534a377c03 --- /dev/null +++ b/lib/common/output.c @@ -0,0 +1,149 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include +#include +#include +#include +#include + +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) { + 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); + return 0; +} + +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 new file mode 100644 index 0000000000..76ae4ca4e1 --- /dev/null +++ b/lib/common/output_text.c @@ -0,0 +1,223 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include +#include +#include + +/* Disabled for the moment, but we can enable it (or remove it entirely) + * when we make a decision on whether this is preferred output. + */ +#define FANCY_TEXT_OUTPUT 0 + +typedef struct text_list_data_s { + unsigned int len; + char *singular_noun; + char *plural_noun; +} text_list_data_t; + +typedef struct text_private_s { + GQueue *parent_q; +} text_private_t; + +static void +text_free_priv(pcmk__output_t *out) { + text_private_t *priv = out->priv; + + if (priv == NULL) { + return; + } + + g_queue_free(priv->parent_q); + free(priv); +} + +static bool +text_init(pcmk__output_t *out) { + text_private_t *priv = NULL; + + /* If text_init was previously called on this output struct, just return. */ + if (out->priv != NULL) { + return true; + } else { + out->priv = calloc(1, sizeof(text_private_t)); + if (out->priv == NULL) { + return false; + } + + priv = out->priv; + } + + priv->parent_q = g_queue_new(); + return true; +} + +static void +text_finish(pcmk__output_t *out, crm_exit_t exit_status) { + /* This function intentionally left blank */ +} + +static void +text_reset(pcmk__output_t *out) { + CRM_ASSERT(out->priv != NULL); + + text_free_priv(out); + text_init(out); +} + +static void +text_subprocess_output(pcmk__output_t *out, int exit_status, + const char *proc_stdout, const char *proc_stderr) { + if (proc_stdout != NULL) { + fprintf(out->dest, "%s\n", proc_stdout); + } + + if (proc_stderr != NULL) { + fprintf(out->dest, "%s\n", proc_stderr); + } +} + +G_GNUC_PRINTF(2, 3) +static void +text_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 = strdup(singular_noun); + new_list->plural_noun = strdup(plural_noun); + + g_queue_push_tail(priv->parent_q, new_list); +} + +static void +text_list_item(pcmk__output_t *out, const char *id, const char *content) { + text_private_t *priv = out->priv; + + CRM_ASSERT(priv != NULL); + +#if FANCY_TEXT_OUTPUT > 0 + if (id != NULL) { + pcmk__indented_printf(out, "* %s: %s\n", id, content); + } else { + pcmk__indented_printf(out, "* %s\n", content); + } +#else + fprintf(out->dest, "%s\n", content); +#endif + + ((text_list_data_t *) g_queue_peek_tail(priv->parent_q))->len++; +} + +static void +text_end_list(pcmk__output_t *out) { + text_private_t *priv = out->priv; + text_list_data_t *node = NULL; + + CRM_ASSERT(priv != NULL); + node = g_queue_pop_tail(priv->parent_q); + + if (node->singular_noun != NULL && node->plural_noun != NULL) { + if (node->len == 1) { + pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun); + } else { + pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun); + } + } + + free(node); +} + +pcmk__output_t * +pcmk__mk_text_output(char **argv) { + pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); + + if (retval == NULL) { + return NULL; + } + + retval->request = g_strjoinv(" ", argv); + retval->supports_quiet = true; + + retval->init = text_init; + retval->free_priv = text_free_priv; + retval->finish = text_finish; + retval->reset = text_reset; + + retval->register_message = pcmk__register_message; + retval->message = pcmk__call_message; + + retval->subprocess_output = text_subprocess_output; + retval->info = text_info; + retval->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 new file mode 100644 index 0000000000..c93d66ce4e --- /dev/null +++ b/lib/common/output_xml.c @@ -0,0 +1,257 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +typedef struct xml_private_s { + xmlNode *root; + GQueue *parent_q; +} 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); + 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(); + g_queue_push_tail(priv->parent_q, priv->root); + + return true; +} + +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)); + xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str); + + 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(node, (pcmkXmlStr) "source", (pcmkXmlStr) "stderr"); + } + + pcmk__xml_add_node(out, node); + free(rc_as_str); +} + +G_GNUC_PRINTF(2, 3) +static void +xml_info(pcmk__output_t *out, const char *format, ...) { + /* This function intentially left blank */ +} + +static void +xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) { + xmlNodePtr parent = NULL; + xmlNodePtr cdata_node = NULL; + xml_private_t *priv = out->priv; + + CRM_ASSERT(priv != NULL); + + parent = xmlNewChild(g_queue_peek_tail(priv->parent_q), NULL, + (pcmkXmlStr) name, NULL); + cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf)); + xmlAddChild(parent, cdata_node); +} + +static void +xml_begin_list(pcmk__output_t *out, const char *name, + const char *singular_noun, const char *plural_noun) { + xmlNodePtr list_node = NULL; + xml_private_t *priv = out->priv; + + CRM_ASSERT(priv != NULL); + + list_node = create_xml_node(g_queue_peek_tail(priv->parent_q), "list"); + xmlSetProp(list_node, (pcmkXmlStr) "name", (pcmkXmlStr) name); + g_queue_push_tail(priv->parent_q, list_node); +} + +static void +xml_list_item(pcmk__output_t *out, const char *name, const char *content) { + xml_private_t *priv = out->priv; + xmlNodePtr item_node = NULL; + + CRM_ASSERT(priv != NULL); + + item_node = xmlNewChild(g_queue_peek_tail(priv->parent_q), NULL, + (pcmkXmlStr) "item", (pcmkXmlStr) content); + xmlSetProp(item_node, (pcmkXmlStr) "name", (pcmkXmlStr) name); +} + +static void +xml_end_list(pcmk__output_t *out) { + char *buf = NULL; + xml_private_t *priv = out->priv; + xmlNodePtr node; + + CRM_ASSERT(priv != NULL); + + node = g_queue_pop_tail(priv->parent_q); + buf = crm_strdup_printf("%lu", xmlChildElementCount(node)); + xmlSetProp(node, (pcmkXmlStr) "count", (pcmkXmlStr) buf); + free(buf); +} + +pcmk__output_t * +pcmk__mk_xml_output(char **argv) { + pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); + + if (retval == NULL) { + return NULL; + } + + retval->request = g_strjoinv(" ", argv); + retval->supports_quiet = false; + + retval->init = xml_init; + retval->free_priv = xml_free_priv; + retval->finish = xml_finish; + retval->reset = xml_reset; + + retval->register_message = pcmk__register_message; + retval->message = pcmk__call_message; + + retval->subprocess_output = xml_subprocess_output; + retval->info = xml_info; + retval->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); +} diff --git a/lib/common/results.c b/lib/common/results.c index 553a4b153b..6216d194d5 100644 --- a/lib/common/results.c +++ b/lib/common/results.c @@ -1,495 +1,499 @@ /* * 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 Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include const char * pcmk_errorname(int rc) { int error = abs(rc); switch (error) { case E2BIG: return "E2BIG"; case EACCES: return "EACCES"; case EADDRINUSE: return "EADDRINUSE"; case EADDRNOTAVAIL: return "EADDRNOTAVAIL"; case EAFNOSUPPORT: return "EAFNOSUPPORT"; case EAGAIN: return "EAGAIN"; case EALREADY: return "EALREADY"; case EBADF: return "EBADF"; case EBADMSG: return "EBADMSG"; case EBUSY: return "EBUSY"; case ECANCELED: return "ECANCELED"; case ECHILD: return "ECHILD"; case ECOMM: return "ECOMM"; case ECONNABORTED: return "ECONNABORTED"; case ECONNREFUSED: return "ECONNREFUSED"; case ECONNRESET: return "ECONNRESET"; /* case EDEADLK: return "EDEADLK"; */ case EDESTADDRREQ: return "EDESTADDRREQ"; case EDOM: return "EDOM"; case EDQUOT: return "EDQUOT"; case EEXIST: return "EEXIST"; case EFAULT: return "EFAULT"; case EFBIG: return "EFBIG"; case EHOSTDOWN: return "EHOSTDOWN"; case EHOSTUNREACH: return "EHOSTUNREACH"; case EIDRM: return "EIDRM"; case EILSEQ: return "EILSEQ"; case EINPROGRESS: return "EINPROGRESS"; case EINTR: return "EINTR"; case EINVAL: return "EINVAL"; case EIO: return "EIO"; case EISCONN: return "EISCONN"; case EISDIR: return "EISDIR"; case ELIBACC: return "ELIBACC"; case ELOOP: return "ELOOP"; case EMFILE: return "EMFILE"; case EMLINK: return "EMLINK"; case EMSGSIZE: return "EMSGSIZE"; #ifdef EMULTIHOP // Not available on OpenBSD case EMULTIHOP: return "EMULTIHOP"; #endif case ENAMETOOLONG: return "ENAMETOOLONG"; case ENETDOWN: return "ENETDOWN"; case ENETRESET: return "ENETRESET"; case ENETUNREACH: return "ENETUNREACH"; case ENFILE: return "ENFILE"; case ENOBUFS: return "ENOBUFS"; case ENODATA: return "ENODATA"; case ENODEV: return "ENODEV"; case ENOENT: return "ENOENT"; case ENOEXEC: return "ENOEXEC"; case ENOKEY: return "ENOKEY"; case ENOLCK: return "ENOLCK"; #ifdef ENOLINK // Not available on OpenBSD case ENOLINK: return "ENOLINK"; #endif case ENOMEM: return "ENOMEM"; case ENOMSG: return "ENOMSG"; case ENOPROTOOPT: return "ENOPROTOOPT"; case ENOSPC: return "ENOSPC"; case ENOSR: return "ENOSR"; case ENOSTR: return "ENOSTR"; case ENOSYS: return "ENOSYS"; case ENOTBLK: return "ENOTBLK"; case ENOTCONN: return "ENOTCONN"; case ENOTDIR: return "ENOTDIR"; case ENOTEMPTY: return "ENOTEMPTY"; case ENOTSOCK: return "ENOTSOCK"; /* case ENOTSUP: return "ENOTSUP"; */ case ENOTTY: return "ENOTTY"; case ENOTUNIQ: return "ENOTUNIQ"; case ENXIO: return "ENXIO"; case EOPNOTSUPP: return "EOPNOTSUPP"; case EOVERFLOW: return "EOVERFLOW"; case EPERM: return "EPERM"; case EPFNOSUPPORT: return "EPFNOSUPPORT"; case EPIPE: return "EPIPE"; case EPROTO: return "EPROTO"; case EPROTONOSUPPORT: return "EPROTONOSUPPORT"; case EPROTOTYPE: return "EPROTOTYPE"; case ERANGE: return "ERANGE"; case EREMOTE: return "EREMOTE"; case EREMOTEIO: return "EREMOTEIO"; case EROFS: return "EROFS"; case ESHUTDOWN: return "ESHUTDOWN"; case ESPIPE: return "ESPIPE"; case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT"; case ESRCH: return "ESRCH"; case ESTALE: return "ESTALE"; case ETIME: return "ETIME"; case ETIMEDOUT: return "ETIMEDOUT"; case ETXTBSY: return "ETXTBSY"; case EUNATCH: return "EUNATCH"; case EUSERS: return "EUSERS"; /* case EWOULDBLOCK: return "EWOULDBLOCK"; */ case EXDEV: return "EXDEV"; #ifdef EBADE /* Not available on OSX */ case EBADE: return "EBADE"; case EBADFD: return "EBADFD"; case EBADSLT: return "EBADSLT"; case EDEADLOCK: return "EDEADLOCK"; case EBADR: return "EBADR"; case EBADRQC: return "EBADRQC"; case ECHRNG: return "ECHRNG"; #ifdef EISNAM /* Not available on Illumos/Solaris */ case EISNAM: return "EISNAM"; case EKEYEXPIRED: return "EKEYEXPIRED"; case EKEYREJECTED: return "EKEYREJECTED"; case EKEYREVOKED: return "EKEYREVOKED"; #endif case EL2HLT: return "EL2HLT"; case EL2NSYNC: return "EL2NSYNC"; case EL3HLT: return "EL3HLT"; case EL3RST: return "EL3RST"; case ELIBBAD: return "ELIBBAD"; case ELIBMAX: return "ELIBMAX"; case ELIBSCN: return "ELIBSCN"; case ELIBEXEC: return "ELIBEXEC"; #ifdef ENOMEDIUM /* Not available on Illumos/Solaris */ case ENOMEDIUM: return "ENOMEDIUM"; case EMEDIUMTYPE: return "EMEDIUMTYPE"; #endif case ENONET: return "ENONET"; case ENOPKG: return "ENOPKG"; case EREMCHG: return "EREMCHG"; case ERESTART: return "ERESTART"; case ESTRPIPE: return "ESTRPIPE"; #ifdef EUCLEAN /* Not available on Illumos/Solaris */ case EUCLEAN: return "EUCLEAN"; #endif case EXFULL: return "EXFULL"; #endif case pcmk_err_generic: return "pcmk_err_generic"; case pcmk_err_no_quorum: return "pcmk_err_no_quorum"; case pcmk_err_schema_validation: return "pcmk_err_schema_validation"; case pcmk_err_transform_failed: return "pcmk_err_transform_failed"; case pcmk_err_old_data: return "pcmk_err_old_data"; case pcmk_err_diff_failed: return "pcmk_err_diff_failed"; case pcmk_err_diff_resync: return "pcmk_err_diff_resync"; case pcmk_err_cib_modified: return "pcmk_err_cib_modified"; case pcmk_err_cib_backup: return "pcmk_err_cib_backup"; case pcmk_err_cib_save: return "pcmk_err_cib_save"; case pcmk_err_cib_corrupt: return "pcmk_err_cib_corrupt"; case pcmk_err_multiple: return "pcmk_err_multiple"; case pcmk_err_node_unknown: return "pcmk_err_node_unknown"; case pcmk_err_already: return "pcmk_err_already"; case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair"; + case pcmk_err_unknown_format: return "pcmk_err_unknown_format"; } return "Unknown"; } const char * pcmk_strerror(int rc) { int error = abs(rc); if (error == 0) { return "OK"; } else if (error < PCMK_ERROR_OFFSET) { return strerror(error); } switch (error) { case pcmk_err_generic: return "Generic Pacemaker error"; case pcmk_err_no_quorum: return "Operation requires quorum"; case pcmk_err_schema_validation: return "Update does not conform to the configured schema"; case pcmk_err_transform_failed: return "Schema transform failed"; case pcmk_err_old_data: return "Update was older than existing configuration"; case pcmk_err_diff_failed: return "Application of an update diff failed"; case pcmk_err_diff_resync: return "Application of an update diff failed, requesting a full refresh"; case pcmk_err_cib_modified: return "The on-disk configuration was manually modified"; case pcmk_err_cib_backup: return "Could not archive the previous configuration"; case pcmk_err_cib_save: return "Could not save the new configuration to disk"; case pcmk_err_cib_corrupt: return "Could not parse on-disk configuration"; case pcmk_err_multiple: return "Resource active on multiple nodes"; case pcmk_err_node_unknown: return "Node not found"; case pcmk_err_already: return "Situation already as requested"; case pcmk_err_bad_nvpair: return "Bad name/value pair given"; case pcmk_err_schema_unchanged: return "Schema is already the latest available"; + case pcmk_err_unknown_format: + return "Unknown output format"; /* The following cases will only be hit on systems for which they are non-standard */ /* coverity[dead_error_condition] False positive on non-Linux */ case ENOTUNIQ: return "Name not unique on network"; /* coverity[dead_error_condition] False positive on non-Linux */ case ECOMM: return "Communication error on send"; /* coverity[dead_error_condition] False positive on non-Linux */ case ELIBACC: return "Can not access a needed shared library"; /* coverity[dead_error_condition] False positive on non-Linux */ case EREMOTEIO: return "Remote I/O error"; /* coverity[dead_error_condition] False positive on non-Linux */ case EUNATCH: return "Protocol driver not attached"; /* coverity[dead_error_condition] False positive on non-Linux */ case ENOKEY: return "Required key not available"; } crm_err("Unknown error code: %d", rc); return "Unknown error"; } const char * crm_exit_name(crm_exit_t exit_code) { switch (exit_code) { case CRM_EX_OK: return "CRM_EX_OK"; case CRM_EX_ERROR: return "CRM_EX_ERROR"; case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM"; case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE"; case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV"; case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED"; case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED"; case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING"; case CRM_EX_USAGE: return "CRM_EX_USAGE"; case CRM_EX_DATAERR: return "CRM_EX_DATAERR"; case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT"; case CRM_EX_NOUSER: return "CRM_EX_NOUSER"; case CRM_EX_NOHOST: return "CRM_EX_NOHOST"; case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE"; case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE"; case CRM_EX_OSERR: return "CRM_EX_OSERR"; case CRM_EX_OSFILE: return "CRM_EX_OSFILE"; case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT"; case CRM_EX_IOERR: return "CRM_EX_IOERR"; case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL"; case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL"; case CRM_EX_NOPERM: return "CRM_EX_NOPERM"; case CRM_EX_CONFIG: return "CRM_EX_CONFIG"; case CRM_EX_FATAL: return "CRM_EX_FATAL"; case CRM_EX_PANIC: return "CRM_EX_PANIC"; case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT"; case CRM_EX_DIGEST: return "CRM_EX_DIGEST"; case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH"; case CRM_EX_QUORUM: return "CRM_EX_QUORUM"; case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE"; case CRM_EX_EXISTS: return "CRM_EX_EXISTS"; case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE"; case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED"; case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT"; case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE"; case CRM_EX_OLD: return "CRM_EX_OLD"; case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT"; case CRM_EX_MAX: return "CRM_EX_UNKNOWN"; } return "CRM_EX_UNKNOWN"; } const char * crm_exit_str(crm_exit_t exit_code) { switch (exit_code) { case CRM_EX_OK: return "OK"; case CRM_EX_ERROR: return "Error occurred"; case CRM_EX_INVALID_PARAM: return "Invalid parameter"; case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented"; case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges"; case CRM_EX_NOT_INSTALLED: return "Not installed"; case CRM_EX_NOT_CONFIGURED: return "Not configured"; case CRM_EX_NOT_RUNNING: return "Not running"; case CRM_EX_USAGE: return "Incorrect usage"; case CRM_EX_DATAERR: return "Invalid data given"; case CRM_EX_NOINPUT: return "Input file not available"; case CRM_EX_NOUSER: return "User does not exist"; case CRM_EX_NOHOST: return "Host does not exist"; case CRM_EX_UNAVAILABLE: return "Necessary service unavailable"; case CRM_EX_SOFTWARE: return "Internal software bug"; case CRM_EX_OSERR: return "Operating system error occurred"; case CRM_EX_OSFILE: return "System file not available"; case CRM_EX_CANTCREAT: return "Cannot create output file"; case CRM_EX_IOERR: return "I/O error occurred"; case CRM_EX_TEMPFAIL: return "Temporary failure, try again"; case CRM_EX_PROTOCOL: return "Protocol violated"; case CRM_EX_NOPERM: return "Insufficient privileges"; case CRM_EX_CONFIG: return "Invalid configuration"; case CRM_EX_FATAL: return "Fatal error occurred, will not respawn"; case CRM_EX_PANIC: return "System panic required"; case CRM_EX_DISCONNECT: return "Not connected"; case CRM_EX_DIGEST: return "Digest mismatch"; case CRM_EX_NOSUCH: return "No such object"; case CRM_EX_QUORUM: return "Quorum required"; case CRM_EX_UNSAFE: return "Operation not safe"; case CRM_EX_EXISTS: return "Requested item already exists"; case CRM_EX_MULTIPLE: return "Multiple items match request"; case CRM_EX_EXPIRED: return "Requested item has expired"; case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect"; case CRM_EX_INDETERMINATE: return "Could not determine status"; case CRM_EX_OLD: return "Update was older than existing configuration"; case CRM_EX_TIMEOUT: return "Timeout occurred"; case CRM_EX_MAX: return "Error occurred"; } if (exit_code > 128) { return "Interrupted by signal"; } return "Unknown exit status"; } /*! * \brief Map an errno to a similar exit status * * \param[in] errno Error number to map * * \return Exit status corresponding to errno */ crm_exit_t crm_errno2exit(int rc) { rc = abs(rc); // Convenience for functions that return -errno if (rc == EOPNOTSUPP) { rc = ENOTSUP; // Values are same on Linux, can't use both in case } switch (rc) { case pcmk_ok: return CRM_EX_OK; case pcmk_err_no_quorum: return CRM_EX_QUORUM; case pcmk_err_old_data: return CRM_EX_OLD; case pcmk_err_schema_validation: case pcmk_err_transform_failed: return CRM_EX_CONFIG; case pcmk_err_bad_nvpair: return CRM_EX_INVALID_PARAM; case EACCES: return CRM_EX_INSUFFICIENT_PRIV; case EBADF: case EINVAL: case EFAULT: case ENOSYS: case EOVERFLOW: return CRM_EX_SOFTWARE; case EBADMSG: case EMSGSIZE: case ENOMSG: case ENOPROTOOPT: case EPROTO: case EPROTONOSUPPORT: case EPROTOTYPE: return CRM_EX_PROTOCOL; case ECOMM: case ENOMEM: return CRM_EX_OSERR; case ECONNABORTED: case ECONNREFUSED: case ECONNRESET: case ENOTCONN: return CRM_EX_DISCONNECT; case EEXIST: case pcmk_err_already: return CRM_EX_EXISTS; case EIO: return CRM_EX_IOERR; case ENOTSUP: return CRM_EX_UNIMPLEMENT_FEATURE; case ENOTUNIQ: case pcmk_err_multiple: return CRM_EX_MULTIPLE; case ENXIO: case pcmk_err_node_unknown: + case pcmk_err_unknown_format: return CRM_EX_NOSUCH; case ETIME: case ETIMEDOUT: return CRM_EX_TIMEOUT; default: return CRM_EX_ERROR; } } const char * bz2_strerror(int rc) { // See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17 switch (rc) { case BZ_OK: case BZ_RUN_OK: case BZ_FLUSH_OK: case BZ_FINISH_OK: case BZ_STREAM_END: return "Ok"; case BZ_CONFIG_ERROR: return "libbz2 has been improperly compiled on your platform"; case BZ_SEQUENCE_ERROR: return "library functions called in the wrong order"; case BZ_PARAM_ERROR: return "parameter is out of range or otherwise incorrect"; case BZ_MEM_ERROR: return "memory allocation failed"; case BZ_DATA_ERROR: return "data integrity error is detected during decompression"; case BZ_DATA_ERROR_MAGIC: return "the compressed stream does not start with the correct magic bytes"; case BZ_IO_ERROR: return "error reading or writing in the compressed file"; case BZ_UNEXPECTED_EOF: return "compressed file finishes before the logical end of stream is detected"; case BZ_OUTBUFF_FULL: return "output data will not fit into the buffer provided"; } return "Unknown error"; } crm_exit_t crm_exit(crm_exit_t rc) { /* A compiler could theoretically use any type for crm_exit_t, but an int * should always hold it, so cast to int to keep static analysis happy. */ if ((((int) rc) < 0) || (((int) rc) > CRM_EX_MAX)) { rc = CRM_EX_ERROR; } mainloop_cleanup(); crm_xml_cleanup(); qb_log_fini(); crm_args_fini(); if (crm_system_name) { crm_info("Exiting %s " CRM_XS " with status %d", crm_system_name, rc); free(crm_system_name); } else { crm_trace("Exiting with status %d", rc); } exit(rc); } diff --git a/lib/fencing/Makefile.am b/lib/fencing/Makefile.am index 6191cb9e2f..71f1774d07 100644 --- a/lib/fencing/Makefile.am +++ b/lib/fencing/Makefile.am @@ -1,23 +1,23 @@ # # Copyright 2004-2018 International Business Machines # Author: Sun Jiang Dong # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/Makefile.common lib_LTLIBRARIES = libstonithd.la libstonithd_la_LDFLAGS = -version-info 27:0:1 libstonithd_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libstonithd_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) libstonithd_la_LIBADD = $(top_builddir)/lib/common/libcrmcommon.la libstonithd_la_LIBADD += $(top_builddir)/lib/services/libcrmservice.la -libstonithd_la_SOURCES = st_client.c st_rhcs.c +libstonithd_la_SOURCES = st_client.c st_output.c st_rhcs.c if BUILD_LHA_SUPPORT libstonithd_la_SOURCES += st_lha.c endif diff --git a/lib/fencing/st_output.c b/lib/fencing/st_output.c new file mode 100644 index 0000000000..fbc9f12589 --- /dev/null +++ b/lib/fencing/st_output.c @@ -0,0 +1,220 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include + +#include +#include +#include +#include + +static int +fence_target_text(pcmk__output_t *out, va_list args) { + const char *hostname = va_arg(args, const char *); + const char *uuid = va_arg(args, const char *); + const char *status = va_arg(args, const char *); + + pcmk__indented_printf(out, "%s\t%s\t%s\n", hostname, uuid, status); + return 0; +} + +static int +fence_target_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr node = NULL; + const char *hostname = va_arg(args, const char *); + const char *uuid = va_arg(args, const char *); + const char *status = va_arg(args, const char *); + + node = xmlNewNode(NULL, (pcmkXmlStr) "target"); + xmlSetProp(node, (pcmkXmlStr) "hostname", (pcmkXmlStr) hostname); + xmlSetProp(node, (pcmkXmlStr) "uuid", (pcmkXmlStr) uuid); + xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) status); + + pcmk__xml_add_node(out, node); + return 0; +} + +static int +last_fenced_text(pcmk__output_t *out, va_list args) { + const char *target = va_arg(args, const char *); + time_t when = va_arg(args, time_t); + + if (when) { + pcmk__indented_printf(out, "Node %s last fenced at: %s\n", target, ctime(&when)); + } else { + pcmk__indented_printf(out, "Node %s has never been fenced\n", target); + } + + return 0; +} + +static int +last_fenced_xml(pcmk__output_t *out, va_list args) { + const char *target = va_arg(args, const char *); + time_t when = va_arg(args, time_t); + + if (when) { + xmlNodePtr node = xmlNewNode(NULL, (pcmkXmlStr) "last-fenced"); + + /* Remove the newline that ctime automatically adds. */ + char *ts = ctime(&when); + char *buf = crm_strdup_printf("%.*s", (int) strcspn(ts, "\n"), ts); + + xmlSetProp(node, (pcmkXmlStr) "target", (pcmkXmlStr) target); + xmlSetProp(node, (pcmkXmlStr) "when", (pcmkXmlStr) buf); + + pcmk__xml_add_node(out, node); + + free(buf); + } + + return 0; +} + +static int +stonith_event_text(pcmk__output_t *out, va_list args) { + stonith_history_t *event = va_arg(args, stonith_history_t *); + + switch (event->state) { + case st_failed: + pcmk__indented_printf(out, "%s failed %s node %s on behalf of %s from %s at %s\n", + event->delegate ? event->delegate : "We", + stonith_action_str(event->action), event->target, + event->client, event->origin, ctime(&event->completed)); + break; + + case st_done: + pcmk__indented_printf(out, "%s succeeded %s node %s on behalf of %s from %s at %s\n", + event->delegate ? event->delegate : "This node", + stonith_action_str(event->action), event->target, + event->client, event->origin, ctime(&event->completed)); + break; + + default: + /* ocf:pacemaker:controld depends on "wishes to" being + * in this output, when used with older versions of DLM + * that don't report stateful_merge_wait + */ + pcmk__indented_printf(out, "%s at %s wishes to %s node %s - %d %lld\n", + event->client, event->origin, stonith_action_str(event->action), + event->target, event->state, (long long) event->completed); + break; + } + + return 0; +} + +static int +stonith_event_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr node = NULL; + stonith_history_t *event = va_arg(args, stonith_history_t *); + + node = xmlNewNode(NULL, (pcmkXmlStr) "stonith-event"); + + switch (event->state) { + case st_failed: + xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) "failed"); + break; + + case st_done: + xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) "done"); + break; + + default: { + char *state = crm_itoa(event->state); + xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) state); + free(state); + break; + } + } + + if (event->delegate != NULL) { + xmlSetProp(node, (pcmkXmlStr) "delegate", (pcmkXmlStr) event->delegate); + } + + xmlSetProp(node, (pcmkXmlStr) "action", (pcmkXmlStr) stonith_action_str(event->action)); + xmlSetProp(node, (pcmkXmlStr) "target", (pcmkXmlStr) event->target); + xmlSetProp(node, (pcmkXmlStr) "client", (pcmkXmlStr) event->client); + xmlSetProp(node, (pcmkXmlStr) "origin", (pcmkXmlStr) event->origin); + xmlSetProp(node, (pcmkXmlStr) "when", (pcmkXmlStr) ctime(&event->completed)); + + pcmk__xml_add_node(out, node); + + return 0; +} + +static int +validate_agent_text(pcmk__output_t *out, va_list args) { + const char *agent = va_arg(args, const char *); + const char *device = va_arg(args, const char *); + const char *output = va_arg(args, const char *); + const char *error_output = va_arg(args, const char *); + int rc = va_arg(args, int); + + if (device) { + pcmk__indented_printf(out, "Validation of %s on %s %s\n", agent, device, + rc ? "failed" : "succeeded"); + } else { + pcmk__indented_printf(out, "Validation of %s %s\n", agent, + rc ? "failed" : "succeeded"); + } + + if (output) { + puts(output); + } + + if (error_output) { + puts(error_output); + } + + return rc; +} + +static int +validate_agent_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr node = NULL; + + const char *agent = va_arg(args, const char *); + const char *device = va_arg(args, const char *); + const char *output = va_arg(args, const char *); + const char *error_output = va_arg(args, const char *); + int rc = va_arg(args, int); + + node = xmlNewNode(NULL, (pcmkXmlStr) "validate"); + xmlSetProp(node, (pcmkXmlStr) "agent", (pcmkXmlStr) agent); + if (device != NULL) { + xmlSetProp(node, (pcmkXmlStr) "device", (pcmkXmlStr) device); + } + xmlSetProp(node, (pcmkXmlStr) "valid", (pcmkXmlStr) (rc ? "false" : "true")); + + pcmk__xml_push_parent(out, node); + out->subprocess_output(out, rc, output, error_output); + pcmk__xml_pop_parent(out); + + pcmk__xml_add_node(out, node); + return rc; +} + +static pcmk__message_entry_t fmt_functions[] = { + { "fence-target", "text", fence_target_text }, + { "fence-target", "xml", fence_target_xml }, + { "last-fenced", "text", last_fenced_text }, + { "last-fenced", "xml", last_fenced_xml }, + { "stonith-event", "text", stonith_event_text }, + { "stonith-event", "xml", stonith_event_xml }, + { "validate", "text", validate_agent_text }, + { "validate", "xml", validate_agent_xml }, + + { NULL, NULL, NULL } +}; + +void +stonith_register_messages(pcmk__output_t *out) { + pcmk__register_messages(out, fmt_functions); +} diff --git a/pacemaker.spec.in b/pacemaker.spec.in index 9ade8da153..c492255e15 100644 --- a/pacemaker.spec.in +++ b/pacemaker.spec.in @@ -1,838 +1,839 @@ # Globals and defines to control package behavior (configure these as desired) ## User and group to use for nonprivileged services %global uname hacluster %global gname haclient ## Where to install Pacemaker documentation %global pcmk_docdir %{_docdir}/%{name} ## GitHub entity that distributes source (for ease of using a fork) %global github_owner ClusterLabs ## Upstream pacemaker version, and its package version (specversion ## can be incremented to build packages reliably considered "newer" ## than previously built packages with the same pcmkversion) %global pcmkversion 2.0.1 %global specversion 1 ## Upstream commit (or git tag, such as "Pacemaker-" plus the ## {pcmkversion} macro for an official release) to use for this package %global commit HEAD ## Since git v2.11, the extent of abbreviation is autoscaled by default ## (used to be constant of 7), so we need to convey it for non-tags, too. %global commit_abbrev 7 ## Python major version to use (2, 3, or 0 for auto-detect) %global python_major 0 # Define globals for convenient use later ## Workaround to use parentheses in other globals %global lparen ( %global rparen ) ## Short version of git commit %define shortcommit %(c=%{commit}; case ${c} in Pacemaker-*%{rparen} echo ${c:10};; *%{rparen} echo ${c:0:%{commit_abbrev}};; esac) ## Whether this is a tagged release %define tag_release %([ %{commit} != Pacemaker-%{shortcommit} ]; echo $?) ## Whether this is a release candidate (in case of a tagged release) %define pre_release %([ "%{tag_release}" -eq 0 ] || { case "%{shortcommit}" in *-rc[[:digit:]]*%{rparen} false;; esac; }; echo $?) ## Heuristic used to infer bleeding-edge deployments that are ## less likely to have working versions of the documentation tools %define bleeding %(test ! -e /etc/yum.repos.d/fedora-rawhide.repo; echo $?) ## Whether this platform defaults to using systemd as an init system ## (needs to be evaluated prior to BuildRequires being enumerated and ## installed as it's intended to conditionally select some of these, and ## for that there are only few indicators with varying reliability: ## - presence of systemd-defined macros (when building in a full-fledged ## environment, which is not the case with ordinary mock-based builds) ## - systemd-aware rpm as manifested with the presence of particular ## macro (rpm itself will trivially always be present when building) ## - existence of /usr/lib/os-release file, which is something heavily ## propagated by systemd project ## - when not good enough, there's always a possibility to check ## particular distro-specific macros (incl. version comparison) %define systemd_native (%{?_unitdir:1}%{!?_unitdir:0}%{nil \ } || %{?__transaction_systemd_inhibit:1}%{!?__transaction_systemd_inhibit:0}%{nil \ } || %(test -f /usr/lib/os-release; test $? -ne 0; echo $?)) %if 0%{?fedora} > 20 || 0%{?rhel} > 7 ## Base GnuTLS cipher priorities (presumably only the initial, required keyword) ## overridable with "rpmbuild --define 'pcmk_gnutls_priorities PRIORITY-SPEC'" %define gnutls_priorities %{?pcmk_gnutls_priorities}%{!?pcmk_gnutls_priorities:@SYSTEM} %endif # Python-related definitions ## Use Python 3 on certain platforms if major version not specified %if %{?python_major} == 0 %if 0%{?fedora} > 26 || 0%{?rhel} > 7 %global python_major 3 %endif %endif ## Turn off auto-compilation of Python files outside Python specific paths, ## so there's no risk that unexpected "__python" macro gets picked to do the ## RPM-native byte-compiling there (only "{_datadir}/pacemaker/tests" affected) ## -- distro-dependent tricks or automake's fallback to be applied there %if %{defined _python_bytecompile_extra} %global _python_bytecompile_extra 0 %else ### the statement effectively means no RPM-native byte-compiling will occur at ### all, so distro-dependent tricks for Python-specific packages to be applied %global __os_install_post %(echo '%{__os_install_post}' | { sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g'; }) %endif ## Values that differ by Python major version %if 0%{?python_major} > 2 %global python_name python3 %global python_path %{?__python3}%{!?__python3:/usr/bin/python%{?python3_pkgversion}%{!?python3_pkgversion:3}} %define python_site %{?python3_sitelib}%{!?python3_sitelib:%( %{python_path} -c 'from distutils.sysconfig import get_python_lib as gpl; print(gpl(1))' 2>/dev/null)} %else %if 0%{?python_major} > 1 %global python_name python2 %global python_path %{?__python2}%{!?__python2:/usr/bin/python%{?python2_pkgversion}%{!?python2_pkgversion:2}} %define python_site %{?python2_sitelib}%{!?python2_sitelib:%( %{python_path} -c 'from distutils.sysconfig import get_python_lib as gpl; print(gpl(1))' 2>/dev/null)} %else %global python_name python %global python_path %{?__python}%{!?__python:/usr/bin/python%{?python_pkgversion}} %define python_site %{?python_sitelib}%{!?python_sitelib:%( python -c 'from distutils.sysconfig import get_python_lib as gpl; print(gpl(1))' 2>/dev/null)} %endif %endif # Definitions for backward compatibility with older RPM versions ## Ensure the license macro behaves consistently (older RPM will otherwise ## overwrite it once it encounters "License:"). Courtesy Jason Tibbitts: ## https://pkgs.fedoraproject.org/cgit/rpms/epel-rpm-macros.git/tree/macros.zzz-epel?h=el6&id=e1adcb77 %if !%{defined _licensedir} %define description %{lua: rpm.define("license %doc") print("%description") } %endif # Define conditionals so that "rpmbuild --with " and # "rpmbuild --without " can enable and disable specific features ## Add option to enable support for stonith/external fencing agents %bcond_with stonithd ## Add option to create binaries suitable for use with profiling tools %bcond_with profiling ## Add option to create binaries with coverage analysis %bcond_with coverage ## Add option to skip generating documentation ## (the build tools aren't available everywhere) %bcond_without doc ## Add option to prefix package version with "0." ## (so later "official" packages will be considered updates) %bcond_with pre_release ## Add option to ship Upstart job files %bcond_with upstart_job ## Add option to turn off hardening of libraries and daemon executables %bcond_without hardening ## Add option to disable links for legacy daemon names %bcond_without legacy_links # Keep sane profiling data if requested %if %{with profiling} ## Disable -debuginfo package and stripping binaries/libraries %define debug_package %{nil} %endif # Define the release version # (do not look at externally enforced pre-release flag for tagged releases # as only -rc tags, captured with the second condition, implies that then) %if (!%{tag_release} && %{with pre_release}) || 0%{pre_release} %if 0%{pre_release} %define pcmk_release 0.%{specversion}.%(s=%{shortcommit}; echo ${s: -3}) %else %define pcmk_release 0.%{specversion}.%{shortcommit}.git %endif %else %if 0%{tag_release} %define pcmk_release %{specversion} %else %define pcmk_release %{specversion}.%{shortcommit}.git %endif %endif Name: pacemaker Summary: Scalable High-Availability cluster resource manager Version: %{pcmkversion} Release: %{pcmk_release}%{?dist} %if %{defined _unitdir} License: GPLv2+ and LGPLv2+ %else # initscript is Revised BSD License: GPLv2+ and LGPLv2+ and BSD %endif Url: http://www.clusterlabs.org Group: System Environment/Daemons # Hint: use "spectool -s 0 pacemaker.spec" (rpmdevtools) to check the final URL: # https://github.com/ClusterLabs/pacemaker/archive/e91769e5a39f5cb2f7b097d3c612368f0530535e/pacemaker-e91769e.tar.gz Source0: https://github.com/%{github_owner}/%{name}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz Requires: resource-agents Requires: %{name}-libs%{?_isa} = %{version}-%{release} Requires: %{name}-cluster-libs%{?_isa} = %{version}-%{release} Requires: %{name}-cli = %{version}-%{release} %if !%{defined _unitdir} Requires: procps-ng Requires: psmisc %endif %{?systemd_requires} Requires: %{python_path} BuildRequires: %{python_name}-devel # Pacemaker requires a minimum libqb functionality Requires: libqb >= 0.13.0 BuildRequires: libqb-devel >= 0.13.0 # Basics required for the build (even if usually satisfied through other BRs) BuildRequires: coreutils findutils grep sed # Required for core functionality BuildRequires: automake autoconf gcc libtool pkgconfig libtool-ltdl-devel BuildRequires: pkgconfig(glib-2.0) >= 2.16 BuildRequires: libxml2-devel libxslt-devel libuuid-devel BuildRequires: bzip2-devel # Enables optional functionality BuildRequires: ncurses-devel docbook-style-xsl BuildRequires: help2man gnutls-devel pam-devel pkgconfig(dbus-1) %if %{systemd_native} BuildRequires: pkgconfig(systemd) %endif Requires: corosync >= 2.0.0 BuildRequires: corosynclib-devel >= 2.0.0 %if %{with stonithd} BuildRequires: cluster-glue-libs-devel %endif ## (note no avoiding effect when building through non-customized mock) %if !%{bleeding} %if %{with doc} BuildRequires: inkscape asciidoc publican %endif %endif Provides: pcmk-cluster-manager = %{version}-%{release} Provides: pcmk-cluster-manager%{?_isa} = %{version}-%{release} # Bundled bits ## Pacemaker uses the crypto/md5-buffer module from gnulib %if 0%{?fedora} || 0%{?rhel} Provides: bundled(gnulib) %endif %description Pacemaker is an advanced, scalable High-Availability cluster resource manager. It supports more than 16 node clusters with significant capabilities for managing resources and dependencies. It will run scripts at initialization, when machines go up or down, when related resources fail and can be configured to periodically check resource health. Available rpmbuild rebuild options: --with(out) : coverage doc stonithd hardening pre_release profiling upstart_job %package cli License: GPLv2+ and LGPLv2+ Summary: Command line tools for controlling Pacemaker clusters Group: System Environment/Daemons Requires: %{name}-libs%{?_isa} = %{version}-%{release} %if 0%{?fedora} > 22 || 0%{?rhel} > 7 Recommends: pcmk-cluster-manager = %{version}-%{release} %endif Requires: perl-TimeDate Requires: procps-ng Requires: psmisc Requires(post):coreutils %description cli Pacemaker is an advanced, scalable High-Availability cluster resource manager. The %{name}-cli package contains command line tools that can be used to query and control the cluster from machines that may, or may not, be part of the cluster. %package libs License: GPLv2+ and LGPLv2+ Summary: Core Pacemaker libraries Group: System Environment/Daemons Requires(pre): shadow-utils Requires: %{name}-schemas = %{version}-%{release} # sbd 1.4.0+ supports the libpe_status API for pe_working_set_t Conflicts: sbd < 1.4.0 %description libs Pacemaker is an advanced, scalable High-Availability cluster resource manager. The %{name}-libs package contains shared libraries needed for cluster nodes and those just running the CLI tools. %package cluster-libs License: GPLv2+ and LGPLv2+ Summary: Cluster Libraries used by Pacemaker Group: System Environment/Daemons Requires: %{name}-libs%{?_isa} = %{version}-%{release} %description cluster-libs Pacemaker is an advanced, scalable High-Availability cluster resource manager. The %{name}-cluster-libs package contains cluster-aware shared libraries needed for nodes that will form part of the cluster nodes. %package remote %if %{defined _unitdir} License: GPLv2+ and LGPLv2+ %else # initscript is Revised BSD License: GPLv2+ and LGPLv2+ and BSD %endif Summary: Pacemaker remote daemon for non-cluster nodes Group: System Environment/Daemons Requires: %{name}-libs%{?_isa} = %{version}-%{release} Requires: %{name}-cli = %{version}-%{release} Requires: resource-agents %if !%{defined _unitdir} Requires: procps-ng %endif # -remote can be fully independent of systemd %{?systemd_ordering}%{!?systemd_ordering:%{?systemd_requires}} Provides: pcmk-cluster-manager = %{version}-%{release} Provides: pcmk-cluster-manager%{?_isa} = %{version}-%{release} %description remote Pacemaker is an advanced, scalable High-Availability cluster resource manager. The %{name}-remote package contains the Pacemaker Remote daemon which is capable of extending pacemaker functionality to remote nodes not running the full corosync/cluster stack. %package libs-devel License: GPLv2+ and LGPLv2+ Summary: Pacemaker development package Group: Development/Libraries Requires: %{name}-libs%{?_isa} = %{version}-%{release} Requires: %{name}-cluster-libs%{?_isa} = %{version}-%{release} Requires: libuuid-devel%{?_isa} libtool-ltdl-devel%{?_isa} Requires: libxml2-devel%{?_isa} libxslt-devel%{?_isa} Requires: bzip2-devel%{?_isa} glib2-devel%{?_isa} Requires: libqb-devel%{?_isa} Requires: corosynclib-devel%{?_isa} >= 2.0.0 %description libs-devel Pacemaker is an advanced, scalable High-Availability cluster resource manager. The %{name}-libs-devel package contains headers and shared libraries for developing tools for Pacemaker. %package cts License: GPLv2+ and LGPLv2+ Summary: Test framework for cluster-related technologies like Pacemaker Group: System Environment/Daemons Requires: %{python_path} Requires: %{name}-libs = %{version}-%{release} Requires: procps-ng Requires: psmisc BuildArch: noarch # systemd python bindings are separate package in some distros %if %{defined systemd_requires} %if 0%{?fedora} > 22 || 0%{?rhel} > 7 Requires: %{python_name}-systemd %else %if 0%{?fedora} > 20 || 0%{?rhel} > 6 Requires: systemd-python %endif %endif %endif %description cts Test framework for cluster-related technologies like Pacemaker %package doc License: CC-BY-SA-4.0 Summary: Documentation for Pacemaker Group: Documentation BuildArch: noarch %description doc Documentation for Pacemaker. Pacemaker is an advanced, scalable High-Availability cluster resource manager. %package schemas License: GPLv2+ Summary: Schemas and upgrade stylesheets for Pacemaker BuildArch: noarch %description schemas Schemas and upgrade stylesheets for Pacemaker Pacemaker is an advanced, scalable High-Availability cluster resource manager. %prep %setup -q -n %{name}-%{commit} %build # Early versions of autotools (e.g. RHEL <= 5) do not support --docdir export docdir=%{pcmk_docdir} export systemdunitdir=%{?_unitdir}%{!?_unitdir:no} %if %{with hardening} # prefer distro-provided hardening flags in case they are defined # through _hardening_{c,ld}flags macros, configure script will # use its own defaults otherwise; if such hardenings are completely # undesired, rpmbuild using "--without hardening" # (or "--define '_without_hardening 1'") export CFLAGS_HARDENED_EXE="%{?_hardening_cflags}" export CFLAGS_HARDENED_LIB="%{?_hardening_cflags}" export LDFLAGS_HARDENED_EXE="%{?_hardening_ldflags}" export LDFLAGS_HARDENED_LIB="%{?_hardening_ldflags}" %endif ./autogen.sh %{configure} \ PYTHON=%{python_path} \ %{!?with_hardening: --disable-hardening} \ %{!?with_legacy_links: --disable-legacy-links} \ %{?with_profiling: --with-profiling} \ %{?with_coverage: --with-coverage} \ %{!?with_doc: --with-brand=} \ %{?gnutls_priorities: --with-gnutls-priorities="%{gnutls_priorities}"} \ --with-initdir=%{_initrddir} \ --localstatedir=%{_var} \ --with-version=%{version}-%{release} %if 0%{?suse_version} >= 1200 # Fedora handles rpath removal automagically sed -i 's|^hardcode_libdir_flag_spec=.*|hardcode_libdir_flag_spec=""|g' libtool sed -i 's|^runpath_var=LD_RUN_PATH|runpath_var=DIE_RPATH_DIE|g' libtool %endif make %{_smp_mflags} V=1 all %check { cts/cts-scheduler --run load-stopped-loop \ && cts/cts-cli \ && touch .CHECKED } 2>&1 | sed 's/[fF]ail/faiil/g' # prevent false positives in rpmlint [ -f .CHECKED ] && rm -f -- .CHECKED exit $? # TODO remove when rpm<4.14 compatibility irrelevant %install # skip automake-native Python byte-compilation, since RPM-native one (possibly # distro-confined to Python-specific directories, which is currently the only # relevant place, anyway) assures proper intrinsic alignment with wider system # (such as with py_byte_compile macro, which is concurrent Fedora/EL specific) make install \ DESTDIR=%{buildroot} V=1 docdir=%{pcmk_docdir} \ %{?_python_bytecompile_extra:%{?py_byte_compile:am__py_compile=true}} mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig install -m 644 daemons/pacemakerd/pacemaker.sysconfig ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig/pacemaker install -m 644 tools/crm_mon.sysconfig ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig/crm_mon %if %{with upstart_job} mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/init install -m 644 pacemakerd/pacemaker.upstart ${RPM_BUILD_ROOT}%{_sysconfdir}/init/pacemaker.conf install -m 644 pacemakerd/pacemaker.combined.upstart ${RPM_BUILD_ROOT}%{_sysconfdir}/init/pacemaker.combined.conf install -m 644 tools/crm_mon.upstart ${RPM_BUILD_ROOT}%{_sysconfdir}/init/crm_mon.conf %endif %if %{defined _unitdir} mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/lib/rpm-state/%{name} %endif # Don't package static libs find %{buildroot} -name '*.a' -type f -print0 | xargs -0 rm -f find %{buildroot} -name '*.la' -type f -print0 | xargs -0 rm -f # For now, don't package the servicelog-related binaries built only for # ppc64le when certain dependencies are installed. If they get more exercise by # advanced users, we can reconsider. rm -f %{buildroot}/%{_sbindir}/notifyServicelogEvent rm -f %{buildroot}/%{_sbindir}/ipmiservicelogd # Don't ship init scripts for systemd based platforms %if %{defined _unitdir} rm -f %{buildroot}/%{_initrddir}/pacemaker rm -f %{buildroot}/%{_initrddir}/pacemaker_remote %endif # Byte-compile Python sources where suitable and the distro procedures known %if %{defined py_byte_compile} %{py_byte_compile %{python_path} %{buildroot}%{_datadir}/pacemaker/tests} %if !%{defined _python_bytecompile_extra} %{py_byte_compile %{python_path} %{buildroot}%{python_site}/cts} %endif %endif %if %{with coverage} GCOV_BASE=%{buildroot}/%{_var}/lib/pacemaker/gcov mkdir -p $GCOV_BASE find . -name '*.gcno' -type f | while read F ; do D=`dirname $F` mkdir -p ${GCOV_BASE}/$D cp $F ${GCOV_BASE}/$D done %endif %post %if %{defined _unitdir} %systemd_post pacemaker.service %else /sbin/chkconfig --add pacemaker || : %endif %preun %if %{defined _unitdir} %systemd_preun pacemaker.service %else /sbin/service pacemaker stop >/dev/null 2>&1 || : if [ "$1" -eq 0 ]; then # Package removal, not upgrade /sbin/chkconfig --del pacemaker || : fi %endif %postun %if %{defined _unitdir} %systemd_postun_with_restart pacemaker.service %endif %pre remote %if %{defined _unitdir} # Stop the service before anything is touched, and remember to restart # it as one of the last actions (compared to using systemd_postun_with_restart, # this avoids suicide when sbd is in use) systemctl --quiet is-active pacemaker_remote if [ $? -eq 0 ] ; then mkdir -p %{_localstatedir}/lib/rpm-state/%{name} touch %{_localstatedir}/lib/rpm-state/%{name}/restart_pacemaker_remote systemctl stop pacemaker_remote >/dev/null 2>&1 else rm -f %{_localstatedir}/lib/rpm-state/%{name}/restart_pacemaker_remote fi %endif %post remote %if %{defined _unitdir} %systemd_post pacemaker_remote.service %else /sbin/chkconfig --add pacemaker_remote || : %endif %preun remote %if %{defined _unitdir} %systemd_preun pacemaker_remote.service %else /sbin/service pacemaker_remote stop >/dev/null 2>&1 || : if [ "$1" -eq 0 ]; then # Package removal, not upgrade /sbin/chkconfig --del pacemaker_remote || : fi %endif %postun remote %if %{defined _unitdir} # This next line is a no-op, because we stopped the service earlier, but # we leave it here because it allows us to revert to the standard behavior # in the future if desired %systemd_postun_with_restart pacemaker_remote.service # Explicitly take care of removing the flag-file(s) upon final removal if [ "$1" -eq 0 ] ; then rm -f %{_localstatedir}/lib/rpm-state/%{name}/restart_pacemaker_remote fi %endif %posttrans remote %if %{defined _unitdir} if [ -e %{_localstatedir}/lib/rpm-state/%{name}/restart_pacemaker_remote ] ; then systemctl start pacemaker_remote >/dev/null 2>&1 rm -f %{_localstatedir}/lib/rpm-state/%{name}/restart_pacemaker_remote fi %endif %post cli %if %{defined _unitdir} %systemd_post crm_mon.service %endif if [ "$1" -eq 2 ]; then # Package upgrade, not initial install: # Move any pre-2.0 logs to new location to ensure they get rotated { mv -fbS.rpmsave %{_var}/log/pacemaker.log* %{_var}/log/pacemaker \ || mv -f %{_var}/log/pacemaker.log* %{_var}/log/pacemaker } >/dev/null 2>/dev/null || : fi %preun cli %if %{defined _unitdir} %systemd_preun crm_mon.service %endif %postun cli %if %{defined _unitdir} %systemd_postun_with_restart crm_mon.service %endif %pre libs getent group %{gname} >/dev/null || groupadd -r %{gname} -g 189 getent passwd %{uname} >/dev/null || useradd -r -g %{gname} -u 189 -s /sbin/nologin -c "cluster user" %{uname} exit 0 %if %{defined ldconfig_scriptlets} %ldconfig_scriptlets libs %ldconfig_scriptlets cluster-libs %else %post libs -p /sbin/ldconfig %postun libs -p /sbin/ldconfig %post cluster-libs -p /sbin/ldconfig %postun cluster-libs -p /sbin/ldconfig %endif %files ########################################################### %config(noreplace) %{_sysconfdir}/sysconfig/pacemaker %{_sbindir}/pacemakerd %if %{defined _unitdir} %{_unitdir}/pacemaker.service %else %{_initrddir}/pacemaker %endif %exclude %{_libexecdir}/pacemaker/cts-log-watcher %exclude %{_libexecdir}/pacemaker/cts-support %exclude %{_sbindir}/pacemaker-remoted %if %{with legacy_links} %exclude %{_sbindir}/pacemaker_remoted %endif %{_libexecdir}/pacemaker/* %{_sbindir}/crm_attribute %{_sbindir}/crm_master %{_sbindir}/fence_legacy %doc %{_mandir}/man7/pacemaker-controld.* %doc %{_mandir}/man7/pacemaker-schedulerd.* %doc %{_mandir}/man7/pacemaker-fenced.* %doc %{_mandir}/man7/ocf_pacemaker_controld.* %doc %{_mandir}/man7/ocf_pacemaker_o2cb.* %doc %{_mandir}/man7/ocf_pacemaker_remote.* %doc %{_mandir}/man8/crm_attribute.* %doc %{_mandir}/man8/crm_master.* %doc %{_mandir}/man8/fence_legacy.* %doc %{_mandir}/man8/pacemakerd.* %doc %{_datadir}/pacemaker/alerts %license licenses/GPLv2 %doc COPYING %doc ChangeLog %dir %attr (750, %{uname}, %{gname}) %{_var}/lib/pacemaker/cib %dir %attr (750, %{uname}, %{gname}) %{_var}/lib/pacemaker/pengine /usr/lib/ocf/resource.d/pacemaker/controld /usr/lib/ocf/resource.d/pacemaker/o2cb /usr/lib/ocf/resource.d/pacemaker/remote %if %{with upstart_job} %config(noreplace) %{_sysconfdir}/init/pacemaker.conf %config(noreplace) %{_sysconfdir}/init/pacemaker.combined.conf %endif %files cli %dir %attr (750, root, %{gname}) %{_sysconfdir}/pacemaker %config(noreplace) %{_sysconfdir}/logrotate.d/pacemaker %config(noreplace) %{_sysconfdir}/sysconfig/crm_mon %if %{defined _unitdir} %{_unitdir}/crm_mon.service %endif %if %{with upstart_job} %config(noreplace) %{_sysconfdir}/init/crm_mon.conf %endif %{_sbindir}/attrd_updater %{_sbindir}/cibadmin %{_sbindir}/crm_diff %{_sbindir}/crm_error %{_sbindir}/crm_failcount %{_sbindir}/crm_mon %{_sbindir}/crm_node %{_sbindir}/crm_resource %{_sbindir}/crm_rule %{_sbindir}/crm_standby %{_sbindir}/crm_verify %{_sbindir}/crmadmin %{_sbindir}/iso8601 %{_sbindir}/crm_shadow %{_sbindir}/crm_simulate %{_sbindir}/crm_report %{_sbindir}/crm_ticket %{_sbindir}/stonith_admin %exclude %{_datadir}/pacemaker/alerts %exclude %{_datadir}/pacemaker/tests %{_datadir}/pacemaker %exclude %{_datadir}/pacemaker/*.rng %exclude %{_datadir}/pacemaker/*.xsl %{_datadir}/snmp/mibs/PCMK-MIB.txt %exclude /usr/lib/ocf/resource.d/pacemaker/controld %exclude /usr/lib/ocf/resource.d/pacemaker/o2cb %exclude /usr/lib/ocf/resource.d/pacemaker/remote %dir /usr/lib/ocf %dir /usr/lib/ocf/resource.d /usr/lib/ocf/resource.d/pacemaker %doc %{_mandir}/man7/* %exclude %{_mandir}/man7/pacemaker-controld.* %exclude %{_mandir}/man7/pacemaker-schedulerd.* %exclude %{_mandir}/man7/pacemaker-fenced.* %exclude %{_mandir}/man7/ocf_pacemaker_controld.* %exclude %{_mandir}/man7/ocf_pacemaker_o2cb.* %exclude %{_mandir}/man7/ocf_pacemaker_remote.* %doc %{_mandir}/man8/* %exclude %{_mandir}/man8/crm_attribute.* %exclude %{_mandir}/man8/crm_master.* %exclude %{_mandir}/man8/fence_legacy.* %exclude %{_mandir}/man8/pacemakerd.* %exclude %{_mandir}/man8/pacemaker-remoted.* %license licenses/GPLv2 %doc COPYING %doc ChangeLog %dir %attr (750, %{uname}, %{gname}) %{_var}/lib/pacemaker %dir %attr (750, %{uname}, %{gname}) %{_var}/lib/pacemaker/blackbox %dir %attr (750, %{uname}, %{gname}) %{_var}/lib/pacemaker/cores %dir %attr (770, %{uname}, %{gname}) %{_var}/log/pacemaker %dir %attr (770, %{uname}, %{gname}) %{_var}/log/pacemaker/bundles %files libs %{_libdir}/libcib.so.* %{_libdir}/liblrmd.so.* %{_libdir}/libcrmservice.so.* %{_libdir}/libcrmcommon.so.* %{_libdir}/libpe_status.so.* %{_libdir}/libpe_rules.so.* %{_libdir}/libpacemaker.so.* %{_libdir}/libstonithd.so.* %license licenses/LGPLv2.1 %doc COPYING %doc ChangeLog %files cluster-libs %{_libdir}/libcrmcluster.so.* %license licenses/LGPLv2.1 %doc COPYING %doc ChangeLog %files remote %config(noreplace) %{_sysconfdir}/sysconfig/pacemaker %if %{defined _unitdir} # state directory is shared between the subpackets # let rpm take care of removing it once it isn't # referenced anymore and empty %ghost %dir %{_localstatedir}/lib/rpm-state/%{name} %{_unitdir}/pacemaker_remote.service %else %{_initrddir}/pacemaker_remote %endif %{_sbindir}/pacemaker-remoted %if %{with legacy_links} %{_sbindir}/pacemaker_remoted %endif %{_mandir}/man8/pacemaker-remoted.* %license licenses/GPLv2 %doc COPYING %doc ChangeLog %files doc %doc %{pcmk_docdir} %license licenses/CC-BY-SA-4.0 %files cts %{python_site}/cts %{_datadir}/pacemaker/tests %{_libexecdir}/pacemaker/cts-log-watcher %{_libexecdir}/pacemaker/cts-support %license licenses/GPLv2 %doc COPYING %doc ChangeLog %files libs-devel %{_includedir}/pacemaker %{_libdir}/*.so %if %{with coverage} %{_var}/lib/pacemaker/gcov %endif %{_libdir}/pkgconfig/*.pc %license licenses/LGPLv2.1 %doc COPYING %doc ChangeLog %files schemas %license licenses/GPLv2 %{_datadir}/pacemaker/*.rng %{_datadir}/pacemaker/*.xsl +%{_datadir}/pacemaker/api/*.rng %changelog diff --git a/tools/stonith_admin.c b/tools/stonith_admin.c index 034949e35a..d92320a6ca 100644 --- a/tools/stonith_admin.c +++ b/tools/stonith_admin.c @@ -1,773 +1,783 @@ /* * Copyright 2009-2018 Andrew Beekhof * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include /* *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." }, { "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)" #if SUPPORT_CIBSECRETS "\n\t\t\t(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 } }; /* *INDENT-ON* */ 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; static int try_mainloop_connect(void) { stonith_t *st = async_fence_data.st; int tries = 10; int i = 0; int rc = 0; for (i = 0; i < tries; i++) { crm_debug("Connecting as %s", async_fence_data.name); rc = st->cmds->connect(st, async_fence_data.name, NULL); if (!rc) { crm_debug("stonith client connection established"); return 0; } else { crm_debug("stonith client connection failed"); } sleep(1); } crm_err("Could not connect to the fencer"); return -1; } 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; if (try_mainloop_connect()) { 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 char * -fence_action_str(const char *action) -{ - char *str = NULL; - - if (action == NULL) { - str = strdup("unknown"); - } else if (action[0] == 'o') { // on, off - str = crm_concat("turn", action, ' '); - } else { - str = strdup(action); - } - return str; -} - -static void -print_fence_event(stonith_history_t *event) -{ - char *action_s = fence_action_str(event->action); - time_t complete = event->completed; - - printf("%s was able to %s node %s on behalf of %s from %s at %s\n", - (event->delegate? event->delegate : "This node"), action_s, - event->target, event->client, event->origin, ctime(&complete)); - free(action_s); -} - static int handle_history(stonith_t *st, const char *target, int timeout, int quiet, - int verbose, int cleanup, int broadcast) + 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) { - printf("cleaning up fencing-history%s%s\n", - target?" for node ":"", target?target:""); + out->info(out, "cleaning up fencing-history%s%s", + target ? " for node " : "", target ? target : ""); } if (broadcast) { - printf("gather fencing-history from all nodes\n"); + 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); - for (hp = history; hp; hp = hp->next) { - char *action_s = NULL; - time_t complete = hp->completed; + 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; } - if (hp->state == st_failed) { - action_s = fence_action_str(hp->action); - printf("%s failed to %s node %s on behalf of %s from %s at %s\n", - hp->delegate ? hp->delegate : "We", action_s, hp->target, - hp->client, hp->origin, ctime(&complete)); - - } else if (hp->state == st_done) { - print_fence_event(latest); - - } else { - /* ocf:pacemaker:controld depends on "wishes to" being - * in this output, when used with older versions of DLM - * that don't report stateful_merge_wait - */ - action_s = fence_action_str(hp->action); - printf("%s at %s wishes to %s node %s - %d %lld\n", - hp->client, hp->origin, action_s, hp->target, hp->state, - (long long) complete); - } - - free(action_s); + out->message(out, "stonith-event", hp); } if (latest) { - if (quiet) { - printf("%lld\n", (long long) latest->completed); + if (quiet && out->supports_quiet) { + out->info(out, "%lld", (long long) latest->completed); } else if (!verbose) { // already printed if verbose - print_fence_event(latest); + 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) + 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) { - printf("Validation of %s %s\n", agent, (rc? "failed" : "succeeded")); - if (output && *output) { - puts(output); - free(output); - } - if (error_output && *error_output) { - puts(error_output); - free(error_output); - } + if (quiet) { + return rc; } + + out->message(out, "validate", agent, id, output, error_output, rc); return rc; } 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 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; + crm_log_cli_init("stonith_admin"); crm_set_options(NULL, " []", 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; switch (flag) { case 'V': verbose = 1; crm_bump_log_level(argc, argv); break; case '$': case '?': crm_help(flag, CRM_EX_OK); break; case 'K': required_agent = true; /* fall through */ case 'I': no_connect = 1; /* fall through */ case 'L': action = flag; break; 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); 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; } + break; default: ++argerr; break; } } if (optind > argc || action == 0) { ++argerr; } if (required_agent && agent == NULL) { - printf("Please specify an agent to query using -a,--agent [value]\n"); + fprintf(stderr, "Please specify an agent to query using -a,--agent [value]\n"); ++argerr; } if (argerr) { crm_help('?', CRM_EX_USAGE); } + CRM_ASSERT(pcmk__register_format("text", pcmk__mk_text_output) == 0); + CRM_ASSERT(pcmk__register_format("xml", pcmk__mk_xml_output) == 0); + + 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; + goto done; + } + + stonith_register_messages(out); + 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)); exit_code = CRM_EX_DISCONNECT; goto done; } } switch (action) { case 'I': rc = st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices, timeout); - for (dIter = devices; dIter; dIter = dIter->next) { - fprintf(stdout, " %s\n", dIter->value); + if (rc < 0) { + fprintf(stderr, "Failed to list installed devices: %s\n", pcmk_strerror(rc)); + break; } - if (rc == 0) { - fprintf(stderr, "No devices found\n"); - } else if (rc > 0) { - fprintf(stderr, "%d devices found\n", rc); - rc = 0; + + out->begin_list(out, "Installed fence devices", "fence device", "fence devices"); + for (dIter = 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); break; + case 'L': rc = st->cmds->query(st, st_opts, target, &devices, timeout); - for (dIter = devices; dIter; dIter = dIter->next) { - fprintf(stdout, " %s\n", dIter->value); + if (rc < 0) { + fprintf(stderr, "Failed to list registered devices: %s\n", pcmk_strerror(rc)); + break; } - if (rc == 0) { - fprintf(stderr, "No devices found\n"); - } else if (rc > 0) { - fprintf(stderr, "%d devices found\n", rc); - rc = 0; + + out->begin_list(out, "Registered fence devices", "fence device", "fence devices"); + for (dIter = 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); break; + case 'Q': rc = st->cmds->monitor(st, st_opts, device, timeout); if (rc < 0) { rc = st->cmds->list(st, st_opts, device, NULL, timeout); } break; case 's': rc = st->cmds->list(st, st_opts, device, &lists, timeout); - if (rc == 0) { - if (lists) { - char *source = lists, *dest = lists; - - while (*dest) { - if ((*dest == '\\') && (*(dest+1) == 'n')) { - *source = '\n'; - dest++; - dest++; - source++; - } else if ((*dest == ',') || (*dest == ';')) { - dest++; - } else { - *source = *dest; - dest++; - source++; + if (rc == 0 && lists) { + char *head = lists; + char *eol = NULL; + + out->begin_list(out, "Fence targets", "fence target", "fence targets"); + + do { + char *line = NULL; + char *elem = NULL; + + char *hostname = NULL; + char *uuid = NULL; + char *status = NULL; + + eol = strstr(head, "\\n"); + line = strndup(head, eol-head); + + while ((elem = strsep(&line, " ")) != NULL) { + if (strcmp(elem, "") == 0) { + continue; } - if (!(*dest)) { - *source = 0; + if (hostname == NULL) { + hostname = elem; + } else if (uuid == NULL) { + uuid = elem; + } else if (status == NULL) { + char *end = NULL; + status = elem; + + end = strchr(status, '\n'); + if (end != NULL) { + *end = '\0'; + } } } - fprintf(stdout, "%s", lists); - free(lists); - } - } else { + + if (hostname != NULL && uuid != NULL && status != NULL) { + out->message(out, "fence-target", hostname, uuid, status); + } + + free(line); + + head = eol+2; + } while (eol != NULL); + + out->end_list(out); + } else if (rc != 0) { fprintf(stderr, "List command returned error. rc : %d\n", rc); } break; case 'R': rc = st->cmds->register_device(st, st_opts, device, NULL, agent, 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'); break; case 'M': { char *buffer = NULL; rc = st->cmds->metadata(st, st_opt_sync_call, agent, NULL, &buffer, timeout); if (rc == pcmk_ok) { - printf("%s\n", buffer); + 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); break; case 'F': rc = mainloop_fencing(st, target, "off", timeout, tolerance); break; case 'U': rc = mainloop_fencing(st, target, "on", timeout, tolerance); break; case 'h': { time_t when = 0; if(as_nodeid) { uint32_t nodeid = atol(target); when = stonith_api_time(nodeid, NULL, FALSE); } else { when = stonith_api_time(0, target, FALSE); } - if(when) { - printf("Node %s last kicked at: %s\n", target, ctime(&when)); - } else { - printf("Node %s has never been kicked\n", target); - } + + out->message(out, "last-fenced", target, when); } + break; case 'H': rc = handle_history(st, target, timeout, quiet, - verbose, cleanup, broadcast); + verbose, cleanup, broadcast, out); break; case 'K': device = (devices? devices->key : NULL); - rc = validate(st, agent, device, params, timeout, quiet); + rc = validate(st, agent, device, params, timeout, 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: free(async_fence_data.name); stonith_key_value_freeall(params, 1, 1); - st->cmds->disconnect(st); - stonith_api_delete(st); + + 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 ee8e8fa6d2..88edbda4ec 100644 --- a/xml/Makefile.am +++ b/xml/Makefile.am @@ -1,178 +1,223 @@ # # Copyright 2004-2018 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 -RNG_numeric_versions = $(shell ls -1 $(top_srcdir)/xml/*.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) -# The highest numeric version -RNG_max ?= $(lastword $(RNG_numeric_versions)) - -# A sorted list of all RNG versions (numeric and "next") -RNG_versions = next $(RNG_numeric_versions) -RNG_version_pairs = $(join \ - ${RNG_numeric_versions},$(addprefix \ +version_pairs = $(join \ + $(1),$(addprefix \ -,$(wordlist \ - 2,$(words ${RNG_numeric_versions}),${RNG_numeric_versions} \ + 2,$(words $(1)),$(1) \ ) next \ ) \ ) -RNG_version_pairs_cnt = $(words ${RNG_version_pairs}) -RNG_version_pairs_last = $(wordlist \ + +version_pairs_last = $(wordlist \ $(words \ $(wordlist \ - 2,${RNG_version_pairs_cnt},${RNG_version_pairs} \ + 2,$(1),$(2) \ ) \ - ),${RNG_version_pairs_cnt},${RNG_version_pairs} \ + ),$(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_base = command-output stonith_admin +API_files = $(foreach base,$(API_base),$(wildcard api/$(base)*.rng)) + +RNG_versions = next $(RNG_numeric_versions) +RNG_version_pairs = $(call version_pairs,${RNG_numeric_versions}) +RNG_version_pairs_cnt = $(words ${RNG_version_pairs}) +RNG_version_pairs_last = $(call version_pairs_last,${RNG_version_pairs_cnt},${RNG_version_pairs}) + +API_generated = api/api-result.rng $(foreach base,$(API_versions),api/api-result-$(base).rng) RNG_generated = pacemaker.rng $(foreach base,$(RNG_versions),pacemaker-$(base).rng) versions.rng RNG_cfg_base = options nodes resources constraints fencing acls tags alerts RNG_base = cib $(RNG_cfg_base) status score rule nvset RNG_files = $(foreach base,$(RNG_base),$(wildcard $(base).rng $(base)-*.rng)) # List of non-Pacemaker RNGs RNG_extra = crm_mon.rng +dist_API_DATA = $(API_files) dist_RNG_DATA = $(RNG_files) $(RNG_extra) +nodist_API_DATA = $(API_generated) nodist_RNG_DATA = $(RNG_generated) EXTRA_DIST = best-match.sh versions: echo "Max: $(RNG_max)" echo "Available: $(RNG_versions)" +api-versions: + echo "Max: $(API_max)" + echo "Available: $(API_versions)" + versions.rng: Makefile.am echo " RNG $@" echo '' > $@ echo '' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' none' >> $@ echo ' pacemaker-0.6' >> $@ echo ' transitional-0.6' >> $@ echo ' pacemaker-0.7' >> $@ echo ' pacemaker-1.1' >> $@ for rng in $(RNG_versions); do echo " pacemaker-$$rng" >> $@; done echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo '' >> $@ +api/api-result.rng: api/api-result-$(API_max).rng + echo " RNG $@" + cp $(top_builddir)/xml/$< $@ + pacemaker.rng: pacemaker-$(RNG_max).rng echo " RNG $@" cp $(top_builddir)/xml/$< $@ +api/api-result-%.rng: $(API_files) Makefile.am + echo " RNG $@" + echo '' > $@ + echo '' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + for rng in $(API_base); do $(top_srcdir)/xml/best-match.sh api/$$rng $(*) $(@) " " || :; done + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo ' ' >> $@ + echo '' >> $@ + pacemaker-%.rng: $(RNG_files) best-match.sh Makefile.am echo " RNG $@" echo '' > $@ echo '' >> $@ echo ' ' >> $@ echo ' ' >> $@ $(top_srcdir)/xml/best-match.sh cib $(*) $(@) " " echo ' ' >> $@ echo ' ' >> $@ for rng in $(RNG_cfg_base); do $(top_srcdir)/xml/best-match.sh $$rng $(*) $(@) " " || :; done echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ $(top_srcdir)/xml/best-match.sh status $(*) $(@) " " echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo ' ' >> $@ echo '' >> $@ # diff fails with ec=2 if no predecessor is found; # this uses '=' GNU extension to sed, if that's not available, # one can use: hline=`echo "$${p}" | grep -Fn "$${hunk}" | cut -d: -f1`; # XXX: use line information from hunk to avoid "not detected" for ambiguity version_diff = \ @for p in $(1); do \ set `echo "$${p}" | tr '-' ' '`; \ echo "\#\#\# *-$$2.rng vs. predecessor"; \ for v in *-$$2.rng; do \ echo "\#\#\#\# $${v} vs. predecessor"; b=`echo "$${v}" | cut -d- -f1`; \ old=`./best-match.sh $${b} $$1`; \ p=`diff -u "$${old}" "$${v}" 2>/dev/null`; \ case $$? in \ 1) echo "$${p}" | sed -n -e '/^@@ /!d;=;p' \ -e ':l;n;/^\([- ]\|+.*<[^ />]\+\([^/>]\+="ID\|>$$\)\)/bl;s/^[+ ]\(.*\)/\1/p' \ | while read hline; do \ read h && read i || break; \ iline=`grep -Fn "$${i}" "$${v}" | cut -d: -f1`; \ ctxt="(not detected)"; \ if test `echo "$${iline}" | wc -l` -eq 1; then \ ctxt=`{ sed -n -e "1,$$(($${iline}-1))p" "$${v}"; \ echo "$${i}"; \ sed -n -e "$$(($${iline}+1)),$$ p" "$${v}"; \ } | $(XSLTPROC) --param skip 1 context-of.xsl -`; \ fi; \ echo "$${p}" | sed -n -e "$$(($${hline}-2)),$${hline}!d" \ -e '/^\(+++\|---\)/p'; \ echo "$${h} context: $${ctxt}"; \ echo "$${p}" | sed -n -e "1,$${hline}d" \ -e '/^\(---\|@@ \)/be;p;d;:e;n;be'; \ done; \ ;; \ 2) echo "\#\#\#\#\# $${v} has no predecessor";; \ esac; \ done; \ done diff: best-match.sh @echo "# Comparing changes in + since $(RNG_max)" $(call version_diff,${RNG_version_pairs_last}) fulldiff: best-match.sh @echo "# Comparing all changes across all the subsequent increments" $(call version_diff,${RNG_version_pairs}) sync: git rm -f $(wildcard *-next.rng) make pacemaker-next.rng -CLEANFILES = $(RNG_generated) +CLEANFILES = $(API_generated) $(RNG_generated) diff --git a/xml/api/command-output-1.0.rng b/xml/api/command-output-1.0.rng new file mode 100644 index 0000000000..710c134984 --- /dev/null +++ b/xml/api/command-output-1.0.rng @@ -0,0 +1,26 @@ + + + + + + + + + + + + + stdout + + + + + + stderr + + + + + + diff --git a/xml/api/stonith_admin-1.0.rng b/xml/api/stonith_admin-1.0.rng new file mode 100644 index 0000000000..4c12c4173c --- /dev/null +++ b/xml/api/stonith_admin-1.0.rng @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + device + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xml/best-match.sh b/xml/best-match.sh index 31224f74a8..3076005c1c 100755 --- a/xml/best-match.sh +++ b/xml/best-match.sh @@ -1,98 +1,98 @@ #!/bin/sh # # Find the (sub-)schema that best matches a desired version. # # Version numbers are assumed to be in the format X.Y, # where X and Y are integers, and Y is no more than 3 digits, # or the special value "next". # # (Sub-)schema name (e.g. "resources") base="$1"; shift # Desired version (e.g. "1.0" or "next") target="$1"; shift # If not empty, append the best match as an XML externalRef to this file # (otherwise, just echo the best match). Using readlink allows building # from a different directory. destination="$(readlink -f "$1")"; shift # Arbitrary text to print before XML (generally spaces to indent) prefix="$1"; shift # Allow building from a different directory cd "$(dirname $0)" list_candidates() { ls -1 "${1}.rng" "${1}"-*.rng 2>/dev/null } version_from_filename() { vff_filename="$1" case "$vff_filename" in *-*.rng) echo "$vff_filename" | sed -e 's/.*-\(.*\).rng/\1/' ;; *) # special case for bare ${base}.rng, no -0.1's around anyway echo 0.1 ;; esac } filename_from_version() { ffv_version="$1" ffv_base="$2" if [ "$ffv_version" = "0.1" ]; then echo "${ffv_base}.rng" else echo "${ffv_base}-${ffv_version}.rng" fi } # Convert version string (e.g. 2.10) into integer (e.g. 2010) for comparisons int_version() { echo "$1" | awk -F. '{ printf("%d%03d\n", $1,$2); }'; } best="0.0" for rng in $(list_candidates "${base}"); do case ${rng} in ${base}-${target}.rng) # We found exactly what was requested best=${target} break ;; *-next.rng) # "Next" schemas cannot be a best match unless directly requested ;; *) v=$(version_from_filename "${rng}") if [ $(int_version "${v}") -gt $(int_version "${best}") ]; then # This version beats the previous best match if [ "${target}" = "next" ]; then best=${v} elif [ $(int_version "${v}") -lt $(int_version "${target}") ]; then # This value is best only if it's still less than the target best=${v} fi fi ;; esac done if [ "$best" != "0.0" ]; then found=$(filename_from_version "$best" "$base") if [ -z "$destination" ]; then - echo "$found" + echo "$(basename $found)" else - echo "${prefix}" >> "$destination" + echo "${prefix}" >> "$destination" fi exit 0 fi exit 1