root/lib/pengine/pe_output.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pe__resource_description
  2. compare_attribute
  3. add_extra_info
  4. filter_attr_list
  5. get_operation_list
  6. add_dump_node
  7. append_dump_text
  8. get_cluster_stack
  9. last_changed_string
  10. op_history_string
  11. resource_history_string
  12. get_node_feature_set
  13. is_mixed_version
  14. formatted_xml_buf
  15. PCMK__OUTPUT_ARGS
  16. PCMK__OUTPUT_ARGS
  17. pe__node_display_name
  18. pe__name_and_nvpairs_xml
  19. role_desc
  20. PCMK__OUTPUT_ARGS
  21. PCMK__OUTPUT_ARGS
  22. PCMK__OUTPUT_ARGS
  23. PCMK__OUTPUT_ARGS
  24. PCMK__OUTPUT_ARGS
  25. PCMK__OUTPUT_ARGS
  26. PCMK__OUTPUT_ARGS
  27. PCMK__OUTPUT_ARGS
  28. PCMK__OUTPUT_ARGS
  29. PCMK__OUTPUT_ARGS
  30. PCMK__OUTPUT_ARGS
  31. PCMK__OUTPUT_ARGS
  32. PCMK__OUTPUT_ARGS
  33. PCMK__OUTPUT_ARGS
  34. no_quorum_policy_text
  35. PCMK__OUTPUT_ARGS
  36. PCMK__OUTPUT_ARGS
  37. PCMK__OUTPUT_ARGS
  38. PCMK__OUTPUT_ARGS
  39. PCMK__OUTPUT_ARGS
  40. PCMK__OUTPUT_ARGS
  41. PCMK__OUTPUT_ARGS
  42. failed_action_friendly
  43. failed_action_technical
  44. PCMK__OUTPUT_ARGS
  45. PCMK__OUTPUT_ARGS
  46. PCMK__OUTPUT_ARGS
  47. status_node
  48. PCMK__OUTPUT_ARGS
  49. node_text_status
  50. PCMK__OUTPUT_ARGS
  51. health_text
  52. node_type_str
  53. PCMK__OUTPUT_ARGS
  54. PCMK__OUTPUT_ARGS
  55. PCMK__OUTPUT_ARGS
  56. PCMK__OUTPUT_ARGS
  57. PCMK__OUTPUT_ARGS
  58. PCMK__OUTPUT_ARGS
  59. PCMK__OUTPUT_ARGS
  60. PCMK__OUTPUT_ARGS
  61. PCMK__OUTPUT_ARGS
  62. PCMK__OUTPUT_ARGS
  63. PCMK__OUTPUT_ARGS
  64. PCMK__OUTPUT_ARGS
  65. PCMK__OUTPUT_ARGS
  66. PCMK__OUTPUT_ARGS
  67. PCMK__OUTPUT_ARGS
  68. PCMK__OUTPUT_ARGS
  69. PCMK__OUTPUT_ARGS
  70. PCMK__OUTPUT_ARGS
  71. PCMK__OUTPUT_ARGS
  72. PCMK__OUTPUT_ARGS
  73. PCMK__OUTPUT_ARGS
  74. PCMK__OUTPUT_ARGS
  75. PCMK__OUTPUT_ARGS
  76. PCMK__OUTPUT_ARGS
  77. print_resource_header
  78. PCMK__OUTPUT_ARGS
  79. PCMK__OUTPUT_ARGS
  80. PCMK__OUTPUT_ARGS
  81. PCMK__OUTPUT_ARGS
  82. PCMK__OUTPUT_ARGS
  83. PCMK__OUTPUT_ARGS
  84. PCMK__OUTPUT_ARGS
  85. pe__register_messages

   1 /*
   2  * Copyright 2019-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdint.h>
  13 
  14 #include <crm/common/xml_internal.h>
  15 #include <crm/common/output.h>
  16 #include <crm/common/scheduler_internal.h>
  17 #include <crm/cib/util.h>
  18 #include <crm/common/xml.h>
  19 #include <crm/pengine/internal.h>
  20 
  21 const char *
  22 pe__resource_description(const pcmk_resource_t *rsc, uint32_t show_opts)
     /* [previous][next][first][last][top][bottom][index][help] */
  23 {
  24     const char * desc = NULL;
  25     // User-supplied description
  26     if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only|pcmk_show_description)) {
  27         desc = crm_element_value(rsc->xml, PCMK_XA_DESCRIPTION);
  28     }
  29     return desc;
  30 }
  31 
  32 /* Never display node attributes whose name starts with one of these prefixes */
  33 #define FILTER_STR { PCMK__FAIL_COUNT_PREFIX, PCMK__LAST_FAILURE_PREFIX,    \
  34                      PCMK__NODE_ATTR_SHUTDOWN, PCMK_NODE_ATTR_TERMINATE,    \
  35                      PCMK_NODE_ATTR_STANDBY, "#", NULL }
  36 
  37 static int
  38 compare_attribute(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
  39 {
  40     int rc;
  41 
  42     rc = strcmp((const char *)a, (const char *)b);
  43 
  44     return rc;
  45 }
  46 
  47 /*!
  48  * \internal
  49  * \brief Determine whether extended information about an attribute should be added.
  50  *
  51  * \param[in]     node            Node that ran this resource
  52  * \param[in,out] rsc_list        List of resources for this node
  53  * \param[in,out] scheduler       Scheduler data
  54  * \param[in]     attrname        Attribute to find
  55  * \param[out]    expected_score  Expected value for this attribute
  56  *
  57  * \return true if extended information should be printed, false otherwise
  58  * \note Currently, extended information is only supported for ping/pingd
  59  *       resources, for which a message will be printed if connectivity is lost
  60  *       or degraded.
  61  */
  62 static bool
  63 add_extra_info(const pcmk_node_t *node, GList *rsc_list,
     /* [previous][next][first][last][top][bottom][index][help] */
  64                pcmk_scheduler_t *scheduler, const char *attrname,
  65                int *expected_score)
  66 {
  67     GList *gIter = NULL;
  68 
  69     for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) {
  70         pcmk_resource_t *rsc = (pcmk_resource_t *) gIter->data;
  71         const char *type = g_hash_table_lookup(rsc->meta, PCMK_XA_TYPE);
  72         const char *name = NULL;
  73         GHashTable *params = NULL;
  74 
  75         if (rsc->children != NULL) {
  76             if (add_extra_info(node, rsc->children, scheduler, attrname,
  77                                expected_score)) {
  78                 return true;
  79             }
  80         }
  81 
  82         if (!pcmk__strcase_any_of(type, "ping", "pingd", NULL)) {
  83             continue;
  84         }
  85 
  86         params = pe_rsc_params(rsc, node, scheduler);
  87         name = g_hash_table_lookup(params, PCMK_XA_NAME);
  88 
  89         if (name == NULL) {
  90             name = "pingd";
  91         }
  92 
  93         /* To identify the resource with the attribute name. */
  94         if (pcmk__str_eq(name, attrname, pcmk__str_casei)) {
  95             int host_list_num = 0;
  96             const char *hosts = g_hash_table_lookup(params, "host_list");
  97             const char *multiplier = g_hash_table_lookup(params, "multiplier");
  98             int multiplier_i;
  99 
 100             if (hosts) {
 101                 char **host_list = g_strsplit(hosts, " ", 0);
 102                 host_list_num = g_strv_length(host_list);
 103                 g_strfreev(host_list);
 104             }
 105 
 106             if ((multiplier == NULL)
 107                 || (pcmk__scan_min_int(multiplier, &multiplier_i,
 108                                        INT_MIN) != pcmk_rc_ok)) {
 109                 /* The ocf:pacemaker:ping resource agent defaults multiplier to
 110                  * 1. The agent currently does not handle invalid text, but it
 111                  * should, and this would be a reasonable choice ...
 112                  */
 113                 multiplier_i = 1;
 114             }
 115             *expected_score = host_list_num * multiplier_i;
 116 
 117             return true;
 118         }
 119     }
 120     return false;
 121 }
 122 
 123 static GList *
 124 filter_attr_list(GList *attr_list, char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 125 {
 126     int i;
 127     const char *filt_str[] = FILTER_STR;
 128 
 129     CRM_CHECK(name != NULL, return attr_list);
 130 
 131     /* filtering automatic attributes */
 132     for (i = 0; filt_str[i] != NULL; i++) {
 133         if (g_str_has_prefix(name, filt_str[i])) {
 134             return attr_list;
 135         }
 136     }
 137 
 138     return g_list_insert_sorted(attr_list, name, compare_attribute);
 139 }
 140 
 141 static GList *
 142 get_operation_list(xmlNode *rsc_entry) {
     /* [previous][next][first][last][top][bottom][index][help] */
 143     GList *op_list = NULL;
 144     xmlNode *rsc_op = NULL;
 145 
 146     for (rsc_op = pcmk__xe_first_child(rsc_entry, NULL, NULL, NULL);
 147          rsc_op != NULL; rsc_op = pcmk__xe_next(rsc_op)) {
 148 
 149         const char *task = crm_element_value(rsc_op, PCMK_XA_OPERATION);
 150         const char *interval_ms_s = crm_element_value(rsc_op,
 151                                                       PCMK_META_INTERVAL);
 152         const char *op_rc = crm_element_value(rsc_op, PCMK__XA_RC_CODE);
 153         int op_rc_i;
 154 
 155         pcmk__scan_min_int(op_rc, &op_rc_i, 0);
 156 
 157         /* Display 0-interval monitors as "probe" */
 158         if (pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_casei)
 159             && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) {
 160             task = "probe";
 161         }
 162 
 163         /* Ignore notifies and some probes */
 164         if (pcmk__str_eq(task, PCMK_ACTION_NOTIFY, pcmk__str_none)
 165             || (pcmk__str_eq(task, "probe", pcmk__str_none)
 166                 && (op_rc_i == CRM_EX_NOT_RUNNING))) {
 167             continue;
 168         }
 169 
 170         if (pcmk__xe_is(rsc_op, PCMK__XE_LRM_RSC_OP)) {
 171             op_list = g_list_append(op_list, rsc_op);
 172         }
 173     }
 174 
 175     op_list = g_list_sort(op_list, sort_op_by_callid);
 176     return op_list;
 177 }
 178 
 179 static void
 180 add_dump_node(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 181 {
 182     xmlNodePtr node = user_data;
 183 
 184     node = pcmk__xe_create(node, (const char *) key);
 185     pcmk__xe_set_content(node, "%s", (const char *) value);
 186 }
 187 
 188 static void
 189 append_dump_text(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 190 {
 191     char **dump_text = user_data;
 192     char *new_text = crm_strdup_printf("%s %s=%s",
 193                                        *dump_text, (char *)key, (char *)value);
 194 
 195     free(*dump_text);
 196     *dump_text = new_text;
 197 }
 198 
 199 #define XPATH_STACK "//" PCMK_XE_NVPAIR     \
 200                     "[@" PCMK_XA_NAME "='"  \
 201                         PCMK_OPT_CLUSTER_INFRASTRUCTURE "']"
 202 
 203 static const char *
 204 get_cluster_stack(pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 205 {
 206     xmlNode *stack = get_xpath_object(XPATH_STACK, scheduler->input, LOG_DEBUG);
 207 
 208     if (stack != NULL) {
 209         return crm_element_value(stack, PCMK_XA_VALUE);
 210     }
 211     return PCMK_VALUE_UNKNOWN;
 212 }
 213 
 214 static char *
 215 last_changed_string(const char *last_written, const char *user,
     /* [previous][next][first][last][top][bottom][index][help] */
 216                     const char *client, const char *origin) {
 217     if (last_written != NULL || user != NULL || client != NULL || origin != NULL) {
 218         return crm_strdup_printf("%s%s%s%s%s%s%s",
 219                                  last_written ? last_written : "",
 220                                  user ? " by " : "",
 221                                  user ? user : "",
 222                                  client ? " via " : "",
 223                                  client ? client : "",
 224                                  origin ? " on " : "",
 225                                  origin ? origin : "");
 226     } else {
 227         return strdup("");
 228     }
 229 }
 230 
 231 static char *
 232 op_history_string(xmlNode *xml_op, const char *task, const char *interval_ms_s,
     /* [previous][next][first][last][top][bottom][index][help] */
 233                   int rc, bool print_timing) {
 234     const char *call = crm_element_value(xml_op, PCMK__XA_CALL_ID);
 235     char *interval_str = NULL;
 236     char *buf = NULL;
 237 
 238     if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) {
 239         char *pair = pcmk__format_nvpair(PCMK_XA_INTERVAL, interval_ms_s, "ms");
 240         interval_str = crm_strdup_printf(" %s", pair);
 241         free(pair);
 242     }
 243 
 244     if (print_timing) {
 245         char *last_change_str = NULL;
 246         char *exec_str = NULL;
 247         char *queue_str = NULL;
 248 
 249         const char *value = NULL;
 250 
 251         time_t epoch = 0;
 252 
 253         if ((crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
 254                                      &epoch) == pcmk_ok)
 255             && (epoch > 0)) {
 256             char *epoch_str = pcmk__epoch2str(&epoch, 0);
 257 
 258             last_change_str = crm_strdup_printf(" %s=\"%s\"",
 259                                                 PCMK_XA_LAST_RC_CHANGE,
 260                                                 pcmk__s(epoch_str, ""));
 261             free(epoch_str);
 262         }
 263 
 264         value = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
 265         if (value) {
 266             char *pair = pcmk__format_nvpair(PCMK_XA_EXEC_TIME, value, "ms");
 267             exec_str = crm_strdup_printf(" %s", pair);
 268             free(pair);
 269         }
 270 
 271         value = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME);
 272         if (value) {
 273             char *pair = pcmk__format_nvpair(PCMK_XA_QUEUE_TIME, value, "ms");
 274             queue_str = crm_strdup_printf(" %s", pair);
 275             free(pair);
 276         }
 277 
 278         buf = crm_strdup_printf("(%s) %s:%s%s%s%s rc=%d (%s)", call, task,
 279                                 interval_str ? interval_str : "",
 280                                 last_change_str ? last_change_str : "",
 281                                 exec_str ? exec_str : "",
 282                                 queue_str ? queue_str : "",
 283                                 rc, services_ocf_exitcode_str(rc));
 284 
 285         if (last_change_str) {
 286             free(last_change_str);
 287         }
 288 
 289         if (exec_str) {
 290             free(exec_str);
 291         }
 292 
 293         if (queue_str) {
 294             free(queue_str);
 295         }
 296     } else {
 297         buf = crm_strdup_printf("(%s) %s%s%s", call, task,
 298                                 interval_str ? ":" : "",
 299                                 interval_str ? interval_str : "");
 300     }
 301 
 302     if (interval_str) {
 303         free(interval_str);
 304     }
 305 
 306     return buf;
 307 }
 308 
 309 static char *
 310 resource_history_string(pcmk_resource_t *rsc, const char *rsc_id, bool all,
     /* [previous][next][first][last][top][bottom][index][help] */
 311                         int failcount, time_t last_failure) {
 312     char *buf = NULL;
 313 
 314     if (rsc == NULL) {
 315         buf = crm_strdup_printf("%s: orphan", rsc_id);
 316     } else if (all || failcount || last_failure > 0) {
 317         char *failcount_s = NULL;
 318         char *lastfail_s = NULL;
 319 
 320         if (failcount > 0) {
 321             failcount_s = crm_strdup_printf(" %s=%d",
 322                                             PCMK_XA_FAIL_COUNT, failcount);
 323         } else {
 324             failcount_s = strdup("");
 325         }
 326         if (last_failure > 0) {
 327             buf = pcmk__epoch2str(&last_failure, 0);
 328             lastfail_s = crm_strdup_printf(" %s='%s'",
 329                                            PCMK_XA_LAST_FAILURE, buf);
 330             free(buf);
 331         }
 332 
 333         buf = crm_strdup_printf("%s: " PCMK_META_MIGRATION_THRESHOLD "=%d%s%s",
 334                                 rsc_id, rsc->migration_threshold, failcount_s,
 335                                 lastfail_s? lastfail_s : "");
 336         free(failcount_s);
 337         free(lastfail_s);
 338     } else {
 339         buf = crm_strdup_printf("%s:", rsc_id);
 340     }
 341 
 342     return buf;
 343 }
 344 
 345 /*!
 346  * \internal
 347  * \brief Get a node's feature set for status display purposes
 348  *
 349  * \param[in] node  Node to check
 350  *
 351  * \return String representation of feature set if the node is fully up (using
 352  *         "<3.15.1" for older nodes that don't set the #feature-set attribute),
 353  *         otherwise NULL
 354  */
 355 static const char *
 356 get_node_feature_set(const pcmk_node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 357 {
 358     if (node->details->online && node->details->expected_up
 359         && !pcmk__is_pacemaker_remote_node(node)) {
 360 
 361         const char *feature_set = g_hash_table_lookup(node->details->attrs,
 362                                                       CRM_ATTR_FEATURE_SET);
 363 
 364         /* The feature set attribute is present since 3.15.1. If it is missing,
 365          * then the node must be running an earlier version.
 366          */
 367         return pcmk__s(feature_set, "<3.15.1");
 368     }
 369     return NULL;
 370 }
 371 
 372 static bool
 373 is_mixed_version(pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 374 {
 375     const char *feature_set = NULL;
 376     for (GList *gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
 377         pcmk_node_t *node = gIter->data;
 378         const char *node_feature_set = get_node_feature_set(node);
 379         if (node_feature_set != NULL) {
 380             if (feature_set == NULL) {
 381                 feature_set = node_feature_set;
 382             } else if (strcmp(feature_set, node_feature_set) != 0) {
 383                 return true;
 384             }
 385         }
 386     }
 387     return false;
 388 }
 389 
 390 static void
 391 formatted_xml_buf(const pcmk_resource_t *rsc, GString *xml_buf, bool raw)
     /* [previous][next][first][last][top][bottom][index][help] */
 392 {
 393     if (raw && (rsc->orig_xml != NULL)) {
 394         pcmk__xml_string(rsc->orig_xml, pcmk__xml_fmt_pretty, xml_buf, 0);
 395     } else {
 396         pcmk__xml_string(rsc->xml, pcmk__xml_fmt_pretty, xml_buf, 0);
 397     }
 398 }
 399 
 400 #define XPATH_DC_VERSION "//" PCMK_XE_NVPAIR    \
 401                          "[@" PCMK_XA_NAME "='" PCMK_OPT_DC_VERSION "']"
 402 
 403 PCMK__OUTPUT_ARGS("cluster-summary", "pcmk_scheduler_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
 404                   "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t")
 405 static int
 406 cluster_summary(pcmk__output_t *out, va_list args) {
 407     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
 408     enum pcmk_pacemakerd_state pcmkd_state =
 409         (enum pcmk_pacemakerd_state) va_arg(args, int);
 410     uint32_t section_opts = va_arg(args, uint32_t);
 411     uint32_t show_opts = va_arg(args, uint32_t);
 412 
 413     int rc = pcmk_rc_no_output;
 414     const char *stack_s = get_cluster_stack(scheduler);
 415 
 416     if (pcmk_is_set(section_opts, pcmk_section_stack)) {
 417         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 418         out->message(out, "cluster-stack", stack_s, pcmkd_state);
 419     }
 420 
 421     if (pcmk_is_set(section_opts, pcmk_section_dc)) {
 422         xmlNode *dc_version = get_xpath_object(XPATH_DC_VERSION,
 423                                                scheduler->input, LOG_DEBUG);
 424         const char *dc_version_s = dc_version?
 425                                    crm_element_value(dc_version, PCMK_XA_VALUE)
 426                                    : NULL;
 427         const char *quorum = crm_element_value(scheduler->input,
 428                                                PCMK_XA_HAVE_QUORUM);
 429         char *dc_name = scheduler->dc_node? pe__node_display_name(scheduler->dc_node, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL;
 430         bool mixed_version = is_mixed_version(scheduler);
 431 
 432         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 433         out->message(out, "cluster-dc", scheduler->dc_node, quorum,
 434                      dc_version_s, dc_name, mixed_version);
 435         free(dc_name);
 436     }
 437 
 438     if (pcmk_is_set(section_opts, pcmk_section_times)) {
 439         const char *last_written = crm_element_value(scheduler->input,
 440                                                      PCMK_XA_CIB_LAST_WRITTEN);
 441         const char *user = crm_element_value(scheduler->input,
 442                                              PCMK_XA_UPDATE_USER);
 443         const char *client = crm_element_value(scheduler->input,
 444                                                PCMK_XA_UPDATE_CLIENT);
 445         const char *origin = crm_element_value(scheduler->input,
 446                                                PCMK_XA_UPDATE_ORIGIN);
 447 
 448         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 449         out->message(out, "cluster-times",
 450                      scheduler->localhost, last_written, user, client, origin);
 451     }
 452 
 453     if (pcmk_is_set(section_opts, pcmk_section_counts)) {
 454         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 455         out->message(out, "cluster-counts", g_list_length(scheduler->nodes),
 456                      scheduler->ninstances, scheduler->disabled_resources,
 457                      scheduler->blocked_resources);
 458     }
 459 
 460     if (pcmk_is_set(section_opts, pcmk_section_options)) {
 461         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 462         out->message(out, "cluster-options", scheduler);
 463     }
 464 
 465     PCMK__OUTPUT_LIST_FOOTER(out, rc);
 466 
 467     if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) {
 468         if (out->message(out, "maint-mode", scheduler->flags) == pcmk_rc_ok) {
 469             rc = pcmk_rc_ok;
 470         }
 471     }
 472 
 473     return rc;
 474 }
 475 
 476 PCMK__OUTPUT_ARGS("cluster-summary", "pcmk_scheduler_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
 477                   "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t")
 478 static int
 479 cluster_summary_html(pcmk__output_t *out, va_list args) {
 480     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
 481     enum pcmk_pacemakerd_state pcmkd_state =
 482         (enum pcmk_pacemakerd_state) va_arg(args, int);
 483     uint32_t section_opts = va_arg(args, uint32_t);
 484     uint32_t show_opts = va_arg(args, uint32_t);
 485 
 486     int rc = pcmk_rc_no_output;
 487     const char *stack_s = get_cluster_stack(scheduler);
 488 
 489     if (pcmk_is_set(section_opts, pcmk_section_stack)) {
 490         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 491         out->message(out, "cluster-stack", stack_s, pcmkd_state);
 492     }
 493 
 494     /* Always print DC if none, even if not requested */
 495     if ((scheduler->dc_node == NULL)
 496         || pcmk_is_set(section_opts, pcmk_section_dc)) {
 497         xmlNode *dc_version = get_xpath_object(XPATH_DC_VERSION,
 498                                                scheduler->input, LOG_DEBUG);
 499         const char *dc_version_s = dc_version?
 500                                    crm_element_value(dc_version, PCMK_XA_VALUE)
 501                                    : NULL;
 502         const char *quorum = crm_element_value(scheduler->input,
 503                                                PCMK_XA_HAVE_QUORUM);
 504         char *dc_name = scheduler->dc_node? pe__node_display_name(scheduler->dc_node, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL;
 505         bool mixed_version = is_mixed_version(scheduler);
 506 
 507         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 508         out->message(out, "cluster-dc", scheduler->dc_node, quorum,
 509                      dc_version_s, dc_name, mixed_version);
 510         free(dc_name);
 511     }
 512 
 513     if (pcmk_is_set(section_opts, pcmk_section_times)) {
 514         const char *last_written = crm_element_value(scheduler->input,
 515                                                      PCMK_XA_CIB_LAST_WRITTEN);
 516         const char *user = crm_element_value(scheduler->input,
 517                                              PCMK_XA_UPDATE_USER);
 518         const char *client = crm_element_value(scheduler->input,
 519                                                PCMK_XA_UPDATE_CLIENT);
 520         const char *origin = crm_element_value(scheduler->input,
 521                                                PCMK_XA_UPDATE_ORIGIN);
 522 
 523         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 524         out->message(out, "cluster-times",
 525                      scheduler->localhost, last_written, user, client, origin);
 526     }
 527 
 528     if (pcmk_is_set(section_opts, pcmk_section_counts)) {
 529         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 530         out->message(out, "cluster-counts", g_list_length(scheduler->nodes),
 531                      scheduler->ninstances, scheduler->disabled_resources,
 532                      scheduler->blocked_resources);
 533     }
 534 
 535     if (pcmk_is_set(section_opts, pcmk_section_options)) {
 536         /* Kind of a hack - close the list we may have opened earlier in this
 537          * function so we can put all the options into their own list.  We
 538          * only want to do this on HTML output, though.
 539          */
 540         PCMK__OUTPUT_LIST_FOOTER(out, rc);
 541 
 542         out->begin_list(out, NULL, NULL, "Config Options");
 543         out->message(out, "cluster-options", scheduler);
 544     }
 545 
 546     PCMK__OUTPUT_LIST_FOOTER(out, rc);
 547 
 548     if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) {
 549         if (out->message(out, "maint-mode", scheduler->flags) == pcmk_rc_ok) {
 550             rc = pcmk_rc_ok;
 551         }
 552     }
 553 
 554     return rc;
 555 }
 556 
 557 char *
 558 pe__node_display_name(pcmk_node_t *node, bool print_detail)
     /* [previous][next][first][last][top][bottom][index][help] */
 559 {
 560     char *node_name;
 561     const char *node_host = NULL;
 562     const char *node_id = NULL;
 563     int name_len;
 564 
 565     pcmk__assert((node != NULL) && (node->details != NULL)
 566                  && (node->details->uname != NULL));
 567 
 568     /* Host is displayed only if this is a guest node and detail is requested */
 569     if (print_detail && pcmk__is_guest_or_bundle_node(node)) {
 570         const pcmk_resource_t *container = node->details->remote_rsc->container;
 571         const pcmk_node_t *host_node = pcmk__current_node(container);
 572 
 573         if (host_node && host_node->details) {
 574             node_host = host_node->details->uname;
 575         }
 576         if (node_host == NULL) {
 577             node_host = ""; /* so we at least get "uname@" to indicate guest */
 578         }
 579     }
 580 
 581     /* Node ID is displayed if different from uname and detail is requested */
 582     if (print_detail && !pcmk__str_eq(node->details->uname, node->details->id, pcmk__str_casei)) {
 583         node_id = node->details->id;
 584     }
 585 
 586     /* Determine name length */
 587     name_len = strlen(node->details->uname) + 1;
 588     if (node_host) {
 589         name_len += strlen(node_host) + 1; /* "@node_host" */
 590     }
 591     if (node_id) {
 592         name_len += strlen(node_id) + 3; /* + " (node_id)" */
 593     }
 594 
 595     /* Allocate and populate display name */
 596     node_name = pcmk__assert_alloc(name_len, sizeof(char));
 597     strcpy(node_name, node->details->uname);
 598     if (node_host) {
 599         strcat(node_name, "@");
 600         strcat(node_name, node_host);
 601     }
 602     if (node_id) {
 603         strcat(node_name, " (");
 604         strcat(node_name, node_id);
 605         strcat(node_name, ")");
 606     }
 607     return node_name;
 608 }
 609 
 610 int
 611 pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 612                          ...)
 613 {
 614     xmlNodePtr xml_node = NULL;
 615     va_list pairs;
 616 
 617     pcmk__assert(tag_name != NULL);
 618 
 619     xml_node = pcmk__output_xml_peek_parent(out);
 620     pcmk__assert(xml_node != NULL);
 621     xml_node = pcmk__xe_create(xml_node, tag_name);
 622 
 623     va_start(pairs, tag_name);
 624     pcmk__xe_set_propv(xml_node, pairs);
 625     va_end(pairs);
 626 
 627     if (is_list) {
 628         pcmk__output_xml_push_parent(out, xml_node);
 629     }
 630     return pcmk_rc_ok;
 631 }
 632 
 633 static const char *
 634 role_desc(enum rsc_role_e role)
     /* [previous][next][first][last][top][bottom][index][help] */
 635 {
 636     if (role == pcmk_role_promoted) {
 637 #ifdef PCMK__COMPAT_2_0
 638         return "as " PCMK__ROLE_PROMOTED_LEGACY " ";
 639 #else
 640         return "in " PCMK_ROLE_PROMOTED " role ";
 641 #endif
 642     }
 643     return "";
 644 }
 645 
 646 PCMK__OUTPUT_ARGS("ban", "pcmk_node_t *", "pcmk__location_t *", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
 647 static int
 648 ban_html(pcmk__output_t *out, va_list args) {
 649     pcmk_node_t *pe_node = va_arg(args, pcmk_node_t *);
 650     pcmk__location_t *location = va_arg(args, pcmk__location_t *);
 651     uint32_t show_opts = va_arg(args, uint32_t);
 652 
 653     char *node_name = pe__node_display_name(pe_node,
 654                                             pcmk_is_set(show_opts, pcmk_show_node_id));
 655     char *buf = crm_strdup_printf("%s\tprevents %s from running %son %s",
 656                                   location->id, location->rsc->id,
 657                                   role_desc(location->role_filter), node_name);
 658 
 659     pcmk__output_create_html_node(out, "li", NULL, NULL, buf);
 660 
 661     free(node_name);
 662     free(buf);
 663     return pcmk_rc_ok;
 664 }
 665 
 666 PCMK__OUTPUT_ARGS("ban", "pcmk_node_t *", "pcmk__location_t *", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
 667 static int
 668 ban_text(pcmk__output_t *out, va_list args) {
 669     pcmk_node_t *pe_node = va_arg(args, pcmk_node_t *);
 670     pcmk__location_t *location = va_arg(args, pcmk__location_t *);
 671     uint32_t show_opts = va_arg(args, uint32_t);
 672 
 673     char *node_name = pe__node_display_name(pe_node,
 674                                             pcmk_is_set(show_opts, pcmk_show_node_id));
 675     out->list_item(out, NULL, "%s\tprevents %s from running %son %s",
 676                    location->id, location->rsc->id,
 677                    role_desc(location->role_filter), node_name);
 678 
 679     free(node_name);
 680     return pcmk_rc_ok;
 681 }
 682 
 683 PCMK__OUTPUT_ARGS("ban", "pcmk_node_t *", "pcmk__location_t *", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
 684 static int
 685 ban_xml(pcmk__output_t *out, va_list args) {
 686     pcmk_node_t *pe_node = va_arg(args, pcmk_node_t *);
 687     pcmk__location_t *location = va_arg(args, pcmk__location_t *);
 688     uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
 689 
 690     const char *promoted_only = pcmk__btoa(location->role_filter == pcmk_role_promoted);
 691     char *weight_s = pcmk__itoa(pe_node->weight);
 692 
 693     pcmk__output_create_xml_node(out, PCMK_XE_BAN,
 694                                  PCMK_XA_ID, location->id,
 695                                  PCMK_XA_RESOURCE, location->rsc->id,
 696                                  PCMK_XA_NODE, pe_node->details->uname,
 697                                  PCMK_XA_WEIGHT, weight_s,
 698                                  PCMK_XA_PROMOTED_ONLY, promoted_only,
 699                                  /* This is a deprecated alias for
 700                                   * promoted_only. Removing it will break
 701                                   * backward compatibility of the API schema,
 702                                   * which will require an API schema major
 703                                   * version bump.
 704                                   */
 705                                  PCMK__XA_PROMOTED_ONLY_LEGACY, promoted_only,
 706                                  NULL);
 707 
 708     free(weight_s);
 709     return pcmk_rc_ok;
 710 }
 711 
 712 PCMK__OUTPUT_ARGS("ban-list", "pcmk_scheduler_t *", "const char *", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
 713                   "uint32_t", "bool")
 714 static int
 715 ban_list(pcmk__output_t *out, va_list args) {
 716     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
 717     const char *prefix = va_arg(args, const char *);
 718     GList *only_rsc = va_arg(args, GList *);
 719     uint32_t show_opts = va_arg(args, uint32_t);
 720     bool print_spacer = va_arg(args, int);
 721 
 722     GList *gIter, *gIter2;
 723     int rc = pcmk_rc_no_output;
 724 
 725     /* Print each ban */
 726     for (gIter = scheduler->placement_constraints;
 727          gIter != NULL; gIter = gIter->next) {
 728         pcmk__location_t *location = gIter->data;
 729         const pcmk_resource_t *rsc = location->rsc;
 730 
 731         if (prefix != NULL && !g_str_has_prefix(location->id, prefix)) {
 732             continue;
 733         }
 734 
 735         if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
 736                                pcmk__str_star_matches)
 737             && !pcmk__str_in_list(rsc_printable_id(pe__const_top_resource(rsc, false)),
 738                                   only_rsc, pcmk__str_star_matches)) {
 739             continue;
 740         }
 741 
 742         for (gIter2 = location->nodes; gIter2 != NULL; gIter2 = gIter2->next) {
 743             pcmk_node_t *node = (pcmk_node_t *) gIter2->data;
 744 
 745             if (node->weight < 0) {
 746                 PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Negative Location Constraints");
 747                 out->message(out, "ban", node, location, show_opts);
 748             }
 749         }
 750     }
 751 
 752     PCMK__OUTPUT_LIST_FOOTER(out, rc);
 753     return rc;
 754 }
 755 
 756 PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
 757 static int
 758 cluster_counts_html(pcmk__output_t *out, va_list args) {
 759     unsigned int nnodes = va_arg(args, unsigned int);
 760     int nresources = va_arg(args, int);
 761     int ndisabled = va_arg(args, int);
 762     int nblocked = va_arg(args, int);
 763 
 764     xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "li", NULL);
 765     xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "li", NULL);
 766     xmlNode *child = NULL;
 767 
 768     child = pcmk__html_create(nodes_node, PCMK__XE_SPAN, NULL, NULL);
 769     pcmk__xe_set_content(child, "%d node%s configured",
 770                          nnodes, pcmk__plural_s(nnodes));
 771 
 772     if (ndisabled && nblocked) {
 773         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
 774         pcmk__xe_set_content(child, "%d resource instance%s configured (%d ",
 775                              nresources, pcmk__plural_s(nresources), ndisabled);
 776 
 777         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL,
 778                                   PCMK__VALUE_BOLD);
 779         pcmk__xe_set_content(child, "DISABLED");
 780 
 781         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
 782         pcmk__xe_set_content(child, ", %d ", nblocked);
 783 
 784         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL,
 785                                   PCMK__VALUE_BOLD);
 786         pcmk__xe_set_content(child, "BLOCKED");
 787 
 788         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
 789         pcmk__xe_set_content(child, " from further action due to failure)");
 790 
 791     } else if (ndisabled && !nblocked) {
 792         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
 793         pcmk__xe_set_content(child, "%d resource instance%s configured (%d ",
 794                              nresources, pcmk__plural_s(nresources),
 795                              ndisabled);
 796 
 797         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL,
 798                                   PCMK__VALUE_BOLD);
 799         pcmk__xe_set_content(child, "DISABLED");
 800 
 801         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
 802         pcmk__xe_set_content(child, ")");
 803 
 804     } else if (!ndisabled && nblocked) {
 805         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
 806         pcmk__xe_set_content(child, "%d resource instance%s configured (%d ",
 807                              nresources, pcmk__plural_s(nresources),
 808                              nblocked);
 809 
 810         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL,
 811                                   PCMK__VALUE_BOLD);
 812         pcmk__xe_set_content(child, "BLOCKED");
 813 
 814         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
 815         pcmk__xe_set_content(child, " from further action due to failure)");
 816 
 817     } else {
 818         child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL);
 819         pcmk__xe_set_content(child, "%d resource instance%s configured",
 820                              nresources, pcmk__plural_s(nresources));
 821     }
 822 
 823     return pcmk_rc_ok;
 824 }
 825 
 826 PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
 827 static int
 828 cluster_counts_text(pcmk__output_t *out, va_list args) {
 829     unsigned int nnodes = va_arg(args, unsigned int);
 830     int nresources = va_arg(args, int);
 831     int ndisabled = va_arg(args, int);
 832     int nblocked = va_arg(args, int);
 833 
 834     out->list_item(out, NULL, "%d node%s configured",
 835                    nnodes, pcmk__plural_s(nnodes));
 836 
 837     if (ndisabled && nblocked) {
 838         out->list_item(out, NULL, "%d resource instance%s configured "
 839                                   "(%d DISABLED, %d BLOCKED from "
 840                                   "further action due to failure)",
 841                        nresources, pcmk__plural_s(nresources), ndisabled,
 842                        nblocked);
 843     } else if (ndisabled && !nblocked) {
 844         out->list_item(out, NULL, "%d resource instance%s configured "
 845                                   "(%d DISABLED)",
 846                        nresources, pcmk__plural_s(nresources), ndisabled);
 847     } else if (!ndisabled && nblocked) {
 848         out->list_item(out, NULL, "%d resource instance%s configured "
 849                                   "(%d BLOCKED from further action "
 850                                   "due to failure)",
 851                        nresources, pcmk__plural_s(nresources), nblocked);
 852     } else {
 853         out->list_item(out, NULL, "%d resource instance%s configured",
 854                        nresources, pcmk__plural_s(nresources));
 855     }
 856 
 857     return pcmk_rc_ok;
 858 }
 859 
 860 PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
 861 static int
 862 cluster_counts_xml(pcmk__output_t *out, va_list args) {
 863     unsigned int nnodes = va_arg(args, unsigned int);
 864     int nresources = va_arg(args, int);
 865     int ndisabled = va_arg(args, int);
 866     int nblocked = va_arg(args, int);
 867 
 868     xmlNodePtr nodes_node = NULL;
 869     xmlNodePtr resources_node = NULL;
 870     char *s = NULL;
 871 
 872     nodes_node = pcmk__output_create_xml_node(out, PCMK_XE_NODES_CONFIGURED,
 873                                               NULL);
 874     resources_node = pcmk__output_create_xml_node(out,
 875                                                   PCMK_XE_RESOURCES_CONFIGURED,
 876                                                   NULL);
 877 
 878     s = pcmk__itoa(nnodes);
 879     crm_xml_add(nodes_node, PCMK_XA_NUMBER, s);
 880     free(s);
 881 
 882     s = pcmk__itoa(nresources);
 883     crm_xml_add(resources_node, PCMK_XA_NUMBER, s);
 884     free(s);
 885 
 886     s = pcmk__itoa(ndisabled);
 887     crm_xml_add(resources_node, PCMK_XA_DISABLED, s);
 888     free(s);
 889 
 890     s = pcmk__itoa(nblocked);
 891     crm_xml_add(resources_node, PCMK_XA_BLOCKED, s);
 892     free(s);
 893 
 894     return pcmk_rc_ok;
 895 }
 896 
 897 PCMK__OUTPUT_ARGS("cluster-dc", "pcmk_node_t *", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 898                   "char *", "int")
 899 static int
 900 cluster_dc_html(pcmk__output_t *out, va_list args) {
 901     pcmk_node_t *dc = va_arg(args, pcmk_node_t *);
 902     const char *quorum = va_arg(args, const char *);
 903     const char *dc_version_s = va_arg(args, const char *);
 904     char *dc_name = va_arg(args, char *);
 905     bool mixed_version = va_arg(args, int);
 906 
 907     xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
 908     xmlNode *child = NULL;
 909 
 910     child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD);
 911     pcmk__xe_set_content(child, "Current DC: ");
 912 
 913     if (dc) {
 914         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
 915         pcmk__xe_set_content(child, "%s (version %s) -",
 916                              dc_name, pcmk__s(dc_version_s, "unknown"));
 917 
 918         if (mixed_version) {
 919             child = pcmk__html_create(node, PCMK__XE_SPAN, NULL,
 920                                       PCMK__VALUE_WARNING);
 921             pcmk__xe_set_content(child, " MIXED-VERSION");
 922         }
 923 
 924         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
 925         pcmk__xe_set_content(child, " partition");
 926 
 927         if (crm_is_true(quorum)) {
 928             child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
 929             pcmk__xe_set_content(child, " with");
 930 
 931         } else {
 932             child = pcmk__html_create(node, PCMK__XE_SPAN, NULL,
 933                                       PCMK__VALUE_WARNING);
 934             pcmk__xe_set_content(child, " WITHOUT");
 935         }
 936 
 937         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
 938         pcmk__xe_set_content(child, " quorum");
 939 
 940     } else {
 941         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL,
 942                                   PCMK__VALUE_WARNING);
 943         pcmk__xe_set_content(child, "NONE");
 944     }
 945 
 946     return pcmk_rc_ok;
 947 }
 948 
 949 PCMK__OUTPUT_ARGS("cluster-dc", "pcmk_node_t *", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 950                   "char *", "int")
 951 static int
 952 cluster_dc_text(pcmk__output_t *out, va_list args) {
 953     pcmk_node_t *dc = va_arg(args, pcmk_node_t *);
 954     const char *quorum = va_arg(args, const char *);
 955     const char *dc_version_s = va_arg(args, const char *);
 956     char *dc_name = va_arg(args, char *);
 957     bool mixed_version = va_arg(args, int);
 958 
 959     if (dc) {
 960         out->list_item(out, "Current DC",
 961                        "%s (version %s) - %spartition %s quorum",
 962                        dc_name, dc_version_s ? dc_version_s : "unknown",
 963                        mixed_version ? "MIXED-VERSION " : "",
 964                        crm_is_true(quorum) ? "with" : "WITHOUT");
 965     } else {
 966         out->list_item(out, "Current DC", "NONE");
 967     }
 968 
 969     return pcmk_rc_ok;
 970 }
 971 
 972 PCMK__OUTPUT_ARGS("cluster-dc", "pcmk_node_t *", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 973                   "char *", "int")
 974 static int
 975 cluster_dc_xml(pcmk__output_t *out, va_list args) {
 976     pcmk_node_t *dc = va_arg(args, pcmk_node_t *);
 977     const char *quorum = va_arg(args, const char *);
 978     const char *dc_version_s = va_arg(args, const char *);
 979     char *dc_name G_GNUC_UNUSED = va_arg(args, char *);
 980     bool mixed_version = va_arg(args, int);
 981 
 982     if (dc) {
 983         const char *with_quorum = pcmk__btoa(crm_is_true(quorum));
 984         const char *mixed_version_s = pcmk__btoa(mixed_version);
 985 
 986         pcmk__output_create_xml_node(out, PCMK_XE_CURRENT_DC,
 987                                      PCMK_XA_PRESENT, PCMK_VALUE_TRUE,
 988                                      PCMK_XA_VERSION, pcmk__s(dc_version_s, ""),
 989                                      PCMK_XA_NAME, dc->details->uname,
 990                                      PCMK_XA_ID, dc->details->id,
 991                                      PCMK_XA_WITH_QUORUM, with_quorum,
 992                                      PCMK_XA_MIXED_VERSION, mixed_version_s,
 993                                      NULL);
 994     } else {
 995         pcmk__output_create_xml_node(out, PCMK_XE_CURRENT_DC,
 996                                      PCMK_XA_PRESENT, PCMK_VALUE_FALSE,
 997                                      NULL);
 998     }
 999 
1000     return pcmk_rc_ok;
1001 }
1002 
1003 PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int")
     /* [previous][next][first][last][top][bottom][index][help] */
1004 static int
1005 cluster_maint_mode_text(pcmk__output_t *out, va_list args) {
1006     unsigned long long flags = va_arg(args, unsigned long long);
1007 
1008     if (pcmk_is_set(flags, pcmk_sched_in_maintenance)) {
1009         pcmk__formatted_printf(out, "\n              *** Resource management is DISABLED ***\n");
1010         pcmk__formatted_printf(out, "  The cluster will not attempt to start, stop or recover services\n");
1011         return pcmk_rc_ok;
1012     } else if (pcmk_is_set(flags, pcmk_sched_stop_all)) {
1013         pcmk__formatted_printf(out, "\n    *** Resource management is DISABLED ***\n");
1014         pcmk__formatted_printf(out, "  The cluster will keep all resources stopped\n");
1015         return pcmk_rc_ok;
1016     } else {
1017         return pcmk_rc_no_output;
1018     }
1019 }
1020 
1021 PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
1022 static int
1023 cluster_options_html(pcmk__output_t *out, va_list args) {
1024     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
1025 
1026     if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
1027         out->list_item(out, NULL, "STONITH of failed nodes enabled");
1028     } else {
1029         out->list_item(out, NULL, "STONITH of failed nodes disabled");
1030     }
1031 
1032     if (pcmk_is_set(scheduler->flags, pcmk_sched_symmetric_cluster)) {
1033         out->list_item(out, NULL, "Cluster is symmetric");
1034     } else {
1035         out->list_item(out, NULL, "Cluster is asymmetric");
1036     }
1037 
1038     switch (scheduler->no_quorum_policy) {
1039         case pcmk_no_quorum_freeze:
1040             out->list_item(out, NULL, "No quorum policy: Freeze resources");
1041             break;
1042 
1043         case pcmk_no_quorum_stop:
1044             out->list_item(out, NULL, "No quorum policy: Stop ALL resources");
1045             break;
1046 
1047         case pcmk_no_quorum_demote:
1048             out->list_item(out, NULL, "No quorum policy: Demote promotable "
1049                            "resources and stop all other resources");
1050             break;
1051 
1052         case pcmk_no_quorum_ignore:
1053             out->list_item(out, NULL, "No quorum policy: Ignore");
1054             break;
1055 
1056         case pcmk_no_quorum_fence:
1057             out->list_item(out, NULL, "No quorum policy: Suicide");
1058             break;
1059     }
1060 
1061     if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
1062         xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
1063         xmlNode *child = NULL;
1064 
1065         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
1066         pcmk__xe_set_content(child, "Resource management: ");
1067 
1068         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD);
1069         pcmk__xe_set_content(child, "DISABLED");
1070 
1071         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
1072         pcmk__xe_set_content(child,
1073                              " (the cluster will not attempt to start, stop,"
1074                              " or recover services)");
1075 
1076     } else if (pcmk_is_set(scheduler->flags, pcmk_sched_stop_all)) {
1077         xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
1078         xmlNode *child = NULL;
1079 
1080         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
1081         pcmk__xe_set_content(child, "Resource management: ");
1082 
1083         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD);
1084         pcmk__xe_set_content(child, "STOPPED");
1085 
1086         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
1087         pcmk__xe_set_content(child,
1088                              " (the cluster will keep all resources stopped)");
1089 
1090     } else {
1091         out->list_item(out, NULL, "Resource management: enabled");
1092     }
1093 
1094     return pcmk_rc_ok;
1095 }
1096 
1097 PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
1098 static int
1099 cluster_options_log(pcmk__output_t *out, va_list args) {
1100     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
1101 
1102     if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
1103         return out->info(out, "Resource management is DISABLED.  The cluster will not attempt to start, stop or recover services.");
1104     } else if (pcmk_is_set(scheduler->flags, pcmk_sched_stop_all)) {
1105         return out->info(out, "Resource management is DISABLED.  The cluster has stopped all resources.");
1106     } else {
1107         return pcmk_rc_no_output;
1108     }
1109 }
1110 
1111 PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
1112 static int
1113 cluster_options_text(pcmk__output_t *out, va_list args) {
1114     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
1115 
1116     if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
1117         out->list_item(out, NULL, "STONITH of failed nodes enabled");
1118     } else {
1119         out->list_item(out, NULL, "STONITH of failed nodes disabled");
1120     }
1121 
1122     if (pcmk_is_set(scheduler->flags, pcmk_sched_symmetric_cluster)) {
1123         out->list_item(out, NULL, "Cluster is symmetric");
1124     } else {
1125         out->list_item(out, NULL, "Cluster is asymmetric");
1126     }
1127 
1128     switch (scheduler->no_quorum_policy) {
1129         case pcmk_no_quorum_freeze:
1130             out->list_item(out, NULL, "No quorum policy: Freeze resources");
1131             break;
1132 
1133         case pcmk_no_quorum_stop:
1134             out->list_item(out, NULL, "No quorum policy: Stop ALL resources");
1135             break;
1136 
1137         case pcmk_no_quorum_demote:
1138             out->list_item(out, NULL, "No quorum policy: Demote promotable "
1139                            "resources and stop all other resources");
1140             break;
1141 
1142         case pcmk_no_quorum_ignore:
1143             out->list_item(out, NULL, "No quorum policy: Ignore");
1144             break;
1145 
1146         case pcmk_no_quorum_fence:
1147             out->list_item(out, NULL, "No quorum policy: Suicide");
1148             break;
1149     }
1150 
1151     return pcmk_rc_ok;
1152 }
1153 
1154 /*!
1155  * \internal
1156  * \brief Get readable string representation of a no-quorum policy
1157  *
1158  * \param[in] policy  No-quorum policy
1159  *
1160  * \return String representation of \p policy
1161  */
1162 static const char *
1163 no_quorum_policy_text(enum pe_quorum_policy policy)
     /* [previous][next][first][last][top][bottom][index][help] */
1164 {
1165     switch (policy) {
1166         case pcmk_no_quorum_freeze:
1167             return PCMK_VALUE_FREEZE;
1168 
1169         case pcmk_no_quorum_stop:
1170             return PCMK_VALUE_STOP;
1171 
1172         case pcmk_no_quorum_demote:
1173             return PCMK_VALUE_DEMOTE;
1174 
1175         case pcmk_no_quorum_ignore:
1176             return PCMK_VALUE_IGNORE;
1177 
1178         case pcmk_no_quorum_fence:
1179             return PCMK_VALUE_FENCE_LEGACY;
1180 
1181         default:
1182             return PCMK_VALUE_UNKNOWN;
1183     }
1184 }
1185 
1186 PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
1187 static int
1188 cluster_options_xml(pcmk__output_t *out, va_list args) {
1189     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
1190 
1191     const char *stonith_enabled = pcmk__flag_text(scheduler->flags,
1192                                                   pcmk_sched_fencing_enabled);
1193     const char *symmetric_cluster =
1194         pcmk__flag_text(scheduler->flags, pcmk_sched_symmetric_cluster);
1195     const char *no_quorum_policy =
1196         no_quorum_policy_text(scheduler->no_quorum_policy);
1197     const char *maintenance_mode = pcmk__flag_text(scheduler->flags,
1198                                                    pcmk_sched_in_maintenance);
1199     const char *stop_all_resources = pcmk__flag_text(scheduler->flags,
1200                                                      pcmk_sched_stop_all);
1201     char *stonith_timeout_ms_s = pcmk__itoa(scheduler->stonith_timeout);
1202     char *priority_fencing_delay_ms_s =
1203         pcmk__itoa(scheduler->priority_fencing_delay * 1000);
1204 
1205     pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_OPTIONS,
1206                                  PCMK_XA_STONITH_ENABLED, stonith_enabled,
1207                                  PCMK_XA_SYMMETRIC_CLUSTER, symmetric_cluster,
1208                                  PCMK_XA_NO_QUORUM_POLICY, no_quorum_policy,
1209                                  PCMK_XA_MAINTENANCE_MODE, maintenance_mode,
1210                                  PCMK_XA_STOP_ALL_RESOURCES, stop_all_resources,
1211                                  PCMK_XA_STONITH_TIMEOUT_MS,
1212                                      stonith_timeout_ms_s,
1213                                  PCMK_XA_PRIORITY_FENCING_DELAY_MS,
1214                                      priority_fencing_delay_ms_s,
1215                                  NULL);
1216     free(stonith_timeout_ms_s);
1217     free(priority_fencing_delay_ms_s);
1218 
1219     return pcmk_rc_ok;
1220 }
1221 
1222 PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
     /* [previous][next][first][last][top][bottom][index][help] */
1223 static int
1224 cluster_stack_html(pcmk__output_t *out, va_list args) {
1225     const char *stack_s = va_arg(args, const char *);
1226     enum pcmk_pacemakerd_state pcmkd_state =
1227         (enum pcmk_pacemakerd_state) va_arg(args, int);
1228 
1229     xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
1230     xmlNode *child = NULL;
1231 
1232     child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD);
1233     pcmk__xe_set_content(child, "Stack: ");
1234 
1235     child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
1236     pcmk__xe_set_content(child, "%s", stack_s);
1237 
1238     if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
1239         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
1240         pcmk__xe_set_content(child, " (");
1241 
1242         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
1243         pcmk__xe_set_content(child, "%s",
1244                              pcmk__pcmkd_state_enum2friendly(pcmkd_state));
1245 
1246         child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL);
1247         pcmk__xe_set_content(child, ")");
1248     }
1249     return pcmk_rc_ok;
1250 }
1251 
1252 PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
     /* [previous][next][first][last][top][bottom][index][help] */
1253 static int
1254 cluster_stack_text(pcmk__output_t *out, va_list args) {
1255     const char *stack_s = va_arg(args, const char *);
1256     enum pcmk_pacemakerd_state pcmkd_state =
1257         (enum pcmk_pacemakerd_state) va_arg(args, int);
1258 
1259     if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
1260         out->list_item(out, "Stack", "%s (%s)",
1261                        stack_s, pcmk__pcmkd_state_enum2friendly(pcmkd_state));
1262     } else {
1263         out->list_item(out, "Stack", "%s", stack_s);
1264     }
1265 
1266     return pcmk_rc_ok;
1267 }
1268 
1269 PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
     /* [previous][next][first][last][top][bottom][index][help] */
1270 static int
1271 cluster_stack_xml(pcmk__output_t *out, va_list args) {
1272     const char *stack_s = va_arg(args, const char *);
1273     enum pcmk_pacemakerd_state pcmkd_state =
1274         (enum pcmk_pacemakerd_state) va_arg(args, int);
1275 
1276     const char *state_s = NULL;
1277 
1278     if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
1279         state_s = pcmk_pacemakerd_api_daemon_state_enum2text(pcmkd_state);
1280     }
1281 
1282     pcmk__output_create_xml_node(out, PCMK_XE_STACK,
1283                                  PCMK_XA_TYPE, stack_s,
1284                                  PCMK_XA_PACEMAKERD_STATE, state_s,
1285                                  NULL);
1286 
1287     return pcmk_rc_ok;
1288 }
1289 
1290 PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
1291                   "const char *", "const char *", "const char *")
1292 static int
1293 cluster_times_html(pcmk__output_t *out, va_list args) {
1294     const char *our_nodename = va_arg(args, const char *);
1295     const char *last_written = va_arg(args, const char *);
1296     const char *user = va_arg(args, const char *);
1297     const char *client = va_arg(args, const char *);
1298     const char *origin = va_arg(args, const char *);
1299 
1300     xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "li", NULL);
1301     xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "li", NULL);
1302     xmlNode *child = NULL;
1303 
1304     char *time_s = NULL;
1305 
1306     child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL,
1307                               PCMK__VALUE_BOLD);
1308     pcmk__xe_set_content(child, "Last updated: ");
1309 
1310     child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL);
1311     time_s = pcmk__epoch2str(NULL, 0);
1312     pcmk__xe_set_content(child, "%s", time_s);
1313     free(time_s);
1314 
1315     if (our_nodename != NULL) {
1316         child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL);
1317         pcmk__xe_set_content(child, " on ");
1318 
1319         child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL);
1320         pcmk__xe_set_content(child, "%s", our_nodename);
1321     }
1322 
1323     child = pcmk__html_create(changed_node, PCMK__XE_SPAN, NULL,
1324                               PCMK__VALUE_BOLD);
1325     pcmk__xe_set_content(child, "Last change: ");
1326 
1327     child = pcmk__html_create(changed_node, PCMK__XE_SPAN, NULL, NULL);
1328     time_s = last_changed_string(last_written, user, client, origin);
1329     pcmk__xe_set_content(child, "%s", time_s);
1330     free(time_s);
1331 
1332     return pcmk_rc_ok;
1333 }
1334 
1335 PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
1336                   "const char *", "const char *", "const char *")
1337 static int
1338 cluster_times_xml(pcmk__output_t *out, va_list args) {
1339     const char *our_nodename = va_arg(args, const char *);
1340     const char *last_written = va_arg(args, const char *);
1341     const char *user = va_arg(args, const char *);
1342     const char *client = va_arg(args, const char *);
1343     const char *origin = va_arg(args, const char *);
1344 
1345     char *time_s = pcmk__epoch2str(NULL, 0);
1346 
1347     pcmk__output_create_xml_node(out, PCMK_XE_LAST_UPDATE,
1348                                  PCMK_XA_TIME, time_s,
1349                                  PCMK_XA_ORIGIN, our_nodename,
1350                                  NULL);
1351 
1352     pcmk__output_create_xml_node(out, PCMK_XE_LAST_CHANGE,
1353                                  PCMK_XA_TIME, pcmk__s(last_written, ""),
1354                                  PCMK_XA_USER, pcmk__s(user, ""),
1355                                  PCMK_XA_CLIENT, pcmk__s(client, ""),
1356                                  PCMK_XA_ORIGIN, pcmk__s(origin, ""),
1357                                  NULL);
1358 
1359     free(time_s);
1360     return pcmk_rc_ok;
1361 }
1362 
1363 PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
1364                   "const char *", "const char *", "const char *")
1365 static int
1366 cluster_times_text(pcmk__output_t *out, va_list args) {
1367     const char *our_nodename = va_arg(args, const char *);
1368     const char *last_written = va_arg(args, const char *);
1369     const char *user = va_arg(args, const char *);
1370     const char *client = va_arg(args, const char *);
1371     const char *origin = va_arg(args, const char *);
1372 
1373     char *time_s = pcmk__epoch2str(NULL, 0);
1374 
1375     out->list_item(out, "Last updated", "%s%s%s",
1376                    time_s, (our_nodename != NULL)? " on " : "",
1377                    pcmk__s(our_nodename, ""));
1378 
1379     free(time_s);
1380     time_s = last_changed_string(last_written, user, client, origin);
1381 
1382     out->list_item(out, "Last change", " %s", time_s);
1383 
1384     free(time_s);
1385     return pcmk_rc_ok;
1386 }
1387 
1388 /*!
1389  * \internal
1390  * \brief Display a failed action in less-technical natural language
1391  *
1392  * \param[in,out] out          Output object to use for display
1393  * \param[in]     xml_op       XML containing failed action
1394  * \param[in]     op_key       Operation key of failed action
1395  * \param[in]     node_name    Where failed action occurred
1396  * \param[in]     rc           OCF exit code of failed action
1397  * \param[in]     status       Execution status of failed action
1398  * \param[in]     exit_reason  Exit reason given for failed action
1399  * \param[in]     exec_time    String containing execution time in milliseconds
1400  */
1401 static void
1402 failed_action_friendly(pcmk__output_t *out, const xmlNode *xml_op,
     /* [previous][next][first][last][top][bottom][index][help] */
1403                        const char *op_key, const char *node_name, int rc,
1404                        int status, const char *exit_reason,
1405                        const char *exec_time)
1406 {
1407     char *rsc_id = NULL;
1408     char *task = NULL;
1409     guint interval_ms = 0;
1410     time_t last_change_epoch = 0;
1411     GString *str = NULL;
1412 
1413     if (pcmk__str_empty(op_key)
1414         || !parse_op_key(op_key, &rsc_id, &task, &interval_ms)) {
1415 
1416         pcmk__str_update(&rsc_id, "unknown resource");
1417         pcmk__str_update(&task, "unknown action");
1418         interval_ms = 0;
1419     }
1420     pcmk__assert((rsc_id != NULL) && (task != NULL));
1421 
1422     str = g_string_sized_new(256); // Should be sufficient for most messages
1423 
1424     pcmk__g_strcat(str, rsc_id, " ", NULL);
1425 
1426     if (interval_ms != 0) {
1427         pcmk__g_strcat(str, pcmk__readable_interval(interval_ms), "-interval ",
1428                        NULL);
1429     }
1430     pcmk__g_strcat(str, pcmk__readable_action(task, interval_ms), " on ",
1431                    node_name, NULL);
1432 
1433     if (status == PCMK_EXEC_DONE) {
1434         pcmk__g_strcat(str, " returned '", services_ocf_exitcode_str(rc), "'",
1435                        NULL);
1436         if (!pcmk__str_empty(exit_reason)) {
1437             pcmk__g_strcat(str, " (", exit_reason, ")", NULL);
1438         }
1439 
1440     } else {
1441         pcmk__g_strcat(str, " could not be executed (",
1442                        pcmk_exec_status_str(status), NULL);
1443         if (!pcmk__str_empty(exit_reason)) {
1444             pcmk__g_strcat(str, ": ", exit_reason, NULL);
1445         }
1446         g_string_append_c(str, ')');
1447     }
1448 
1449 
1450     if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
1451                                 &last_change_epoch) == pcmk_ok) {
1452         char *s = pcmk__epoch2str(&last_change_epoch, 0);
1453 
1454         pcmk__g_strcat(str, " at ", s, NULL);
1455         free(s);
1456     }
1457     if (!pcmk__str_empty(exec_time)) {
1458         int exec_time_ms = 0;
1459 
1460         if ((pcmk__scan_min_int(exec_time, &exec_time_ms, 0) == pcmk_rc_ok)
1461             && (exec_time_ms > 0)) {
1462 
1463             pcmk__g_strcat(str, " after ",
1464                            pcmk__readable_interval(exec_time_ms), NULL);
1465         }
1466     }
1467 
1468     out->list_item(out, NULL, "%s", str->str);
1469     g_string_free(str, TRUE);
1470     free(rsc_id);
1471     free(task);
1472 }
1473 
1474 /*!
1475  * \internal
1476  * \brief Display a failed action with technical details
1477  *
1478  * \param[in,out] out          Output object to use for display
1479  * \param[in]     xml_op       XML containing failed action
1480  * \param[in]     op_key       Operation key of failed action
1481  * \param[in]     node_name    Where failed action occurred
1482  * \param[in]     rc           OCF exit code of failed action
1483  * \param[in]     status       Execution status of failed action
1484  * \param[in]     exit_reason  Exit reason given for failed action
1485  * \param[in]     exec_time    String containing execution time in milliseconds
1486  */
1487 static void
1488 failed_action_technical(pcmk__output_t *out, const xmlNode *xml_op,
     /* [previous][next][first][last][top][bottom][index][help] */
1489                         const char *op_key, const char *node_name, int rc,
1490                         int status, const char *exit_reason,
1491                         const char *exec_time)
1492 {
1493     const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID);
1494     const char *queue_time = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME);
1495     const char *exit_status = services_ocf_exitcode_str(rc);
1496     const char *lrm_status = pcmk_exec_status_str(status);
1497     time_t last_change_epoch = 0;
1498     GString *str = NULL;
1499 
1500     if (pcmk__str_empty(op_key)) {
1501         op_key = "unknown operation";
1502     }
1503     if (pcmk__str_empty(exit_status)) {
1504         exit_status = "unknown exit status";
1505     }
1506     if (pcmk__str_empty(call_id)) {
1507         call_id = "unknown";
1508     }
1509 
1510     str = g_string_sized_new(256);
1511 
1512     g_string_append_printf(str, "%s on %s '%s' (%d): call=%s, status='%s'",
1513                            op_key, node_name, exit_status, rc, call_id,
1514                            lrm_status);
1515 
1516     if (!pcmk__str_empty(exit_reason)) {
1517         pcmk__g_strcat(str, ", exitreason='", exit_reason, "'", NULL);
1518     }
1519 
1520     if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
1521                                 &last_change_epoch) == pcmk_ok) {
1522         char *last_change_str = pcmk__epoch2str(&last_change_epoch, 0);
1523 
1524         pcmk__g_strcat(str,
1525                        ", " PCMK_XA_LAST_RC_CHANGE "="
1526                        "'", last_change_str, "'", NULL);
1527         free(last_change_str);
1528     }
1529     if (!pcmk__str_empty(queue_time)) {
1530         pcmk__g_strcat(str, ", queued=", queue_time, "ms", NULL);
1531     }
1532     if (!pcmk__str_empty(exec_time)) {
1533         pcmk__g_strcat(str, ", exec=", exec_time, "ms", NULL);
1534     }
1535 
1536     out->list_item(out, NULL, "%s", str->str);
1537     g_string_free(str, TRUE);
1538 }
1539 
1540 PCMK__OUTPUT_ARGS("failed-action", "xmlNode *", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
1541 static int
1542 failed_action_default(pcmk__output_t *out, va_list args)
1543 {
1544     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
1545     uint32_t show_opts = va_arg(args, uint32_t);
1546 
1547     const char *op_key = pcmk__xe_history_key(xml_op);
1548     const char *node_name = crm_element_value(xml_op, PCMK_XA_UNAME);
1549     const char *exit_reason = crm_element_value(xml_op, PCMK_XA_EXIT_REASON);
1550     const char *exec_time = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
1551 
1552     int rc;
1553     int status;
1554 
1555     pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_RC_CODE), &rc, 0);
1556 
1557     pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &status,
1558                        0);
1559 
1560     if (pcmk__str_empty(node_name)) {
1561         node_name = "unknown node";
1562     }
1563 
1564     if (pcmk_is_set(show_opts, pcmk_show_failed_detail)) {
1565         failed_action_technical(out, xml_op, op_key, node_name, rc, status,
1566                                 exit_reason, exec_time);
1567     } else {
1568         failed_action_friendly(out, xml_op, op_key, node_name, rc, status,
1569                                exit_reason, exec_time);
1570     }
1571     return pcmk_rc_ok;
1572 }
1573 
1574 PCMK__OUTPUT_ARGS("failed-action", "xmlNode *", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
1575 static int
1576 failed_action_xml(pcmk__output_t *out, va_list args) {
1577     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
1578     uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
1579 
1580     const char *op_key = pcmk__xe_history_key(xml_op);
1581     const char *op_key_name = PCMK_XA_OP_KEY;
1582     int rc;
1583     int status;
1584     const char *uname = crm_element_value(xml_op, PCMK_XA_UNAME);
1585     const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID);
1586     const char *exitstatus = NULL;
1587     const char *exit_reason = pcmk__s(crm_element_value(xml_op,
1588                                                         PCMK_XA_EXIT_REASON),
1589                                       "none");
1590     const char *status_s = NULL;
1591 
1592     time_t epoch = 0;
1593     gchar *exit_reason_esc = NULL;
1594     char *rc_s = NULL;
1595     xmlNodePtr node = NULL;
1596 
1597     if (pcmk__xml_needs_escape(exit_reason, pcmk__xml_escape_attr)) {
1598         exit_reason_esc = pcmk__xml_escape(exit_reason, pcmk__xml_escape_attr);
1599         exit_reason = exit_reason_esc;
1600     }
1601     pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_RC_CODE), &rc, 0);
1602     pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &status,
1603                        0);
1604 
1605     if (crm_element_value(xml_op, PCMK__XA_OPERATION_KEY) == NULL) {
1606         op_key_name = PCMK_XA_ID;
1607     }
1608     exitstatus = services_ocf_exitcode_str(rc);
1609     rc_s = pcmk__itoa(rc);
1610     status_s = pcmk_exec_status_str(status);
1611     node = pcmk__output_create_xml_node(out, PCMK_XE_FAILURE,
1612                                         op_key_name, op_key,
1613                                         PCMK_XA_NODE, uname,
1614                                         PCMK_XA_EXITSTATUS, exitstatus,
1615                                         PCMK_XA_EXITREASON, exit_reason,
1616                                         PCMK_XA_EXITCODE, rc_s,
1617                                         PCMK_XA_CALL, call_id,
1618                                         PCMK_XA_STATUS, status_s,
1619                                         NULL);
1620     free(rc_s);
1621 
1622     if ((crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
1623                                  &epoch) == pcmk_ok) && (epoch > 0)) {
1624 
1625         const char *queue_time = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME);
1626         const char *exec = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
1627         const char *task = crm_element_value(xml_op, PCMK_XA_OPERATION);
1628         guint interval_ms = 0;
1629         char *interval_ms_s = NULL;
1630         char *rc_change = pcmk__epoch2str(&epoch,
1631                                           crm_time_log_date
1632                                           |crm_time_log_timeofday
1633                                           |crm_time_log_with_timezone);
1634 
1635         crm_element_value_ms(xml_op, PCMK_META_INTERVAL, &interval_ms);
1636         interval_ms_s = crm_strdup_printf("%u", interval_ms);
1637 
1638         pcmk__xe_set_props(node,
1639                            PCMK_XA_LAST_RC_CHANGE, rc_change,
1640                            PCMK_XA_QUEUED, queue_time,
1641                            PCMK_XA_EXEC, exec,
1642                            PCMK_XA_INTERVAL, interval_ms_s,
1643                            PCMK_XA_TASK, task,
1644                            NULL);
1645 
1646         free(interval_ms_s);
1647         free(rc_change);
1648     }
1649 
1650     g_free(exit_reason_esc);
1651     return pcmk_rc_ok;
1652 }
1653 
1654 PCMK__OUTPUT_ARGS("failed-action-list", "pcmk_scheduler_t *", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
1655                   "GList *", "uint32_t", "bool")
1656 static int
1657 failed_action_list(pcmk__output_t *out, va_list args) {
1658     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
1659     GList *only_node = va_arg(args, GList *);
1660     GList *only_rsc = va_arg(args, GList *);
1661     uint32_t show_opts = va_arg(args, uint32_t);
1662     bool print_spacer = va_arg(args, int);
1663 
1664     xmlNode *xml_op = NULL;
1665     int rc = pcmk_rc_no_output;
1666 
1667     if (xmlChildElementCount(scheduler->failed) == 0) {
1668         return rc;
1669     }
1670 
1671     for (xml_op = pcmk__xe_first_child(scheduler->failed, NULL, NULL, NULL);
1672          xml_op != NULL; xml_op = pcmk__xe_next(xml_op)) {
1673 
1674         char *rsc = NULL;
1675 
1676         if (!pcmk__str_in_list(crm_element_value(xml_op, PCMK_XA_UNAME),
1677                                only_node,
1678                                pcmk__str_star_matches|pcmk__str_casei)) {
1679             continue;
1680         }
1681 
1682         if (pcmk_xe_mask_probe_failure(xml_op)) {
1683             continue;
1684         }
1685 
1686         if (!parse_op_key(pcmk__xe_history_key(xml_op), &rsc, NULL, NULL)) {
1687             continue;
1688         }
1689 
1690         if (!pcmk__str_in_list(rsc, only_rsc, pcmk__str_star_matches)) {
1691             free(rsc);
1692             continue;
1693         }
1694 
1695         free(rsc);
1696 
1697         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Failed Resource Actions");
1698         out->message(out, "failed-action", xml_op, show_opts);
1699     }
1700 
1701     PCMK__OUTPUT_LIST_FOOTER(out, rc);
1702     return rc;
1703 }
1704 
1705 static void
1706 status_node(pcmk_node_t *node, xmlNodePtr parent, uint32_t show_opts)
     /* [previous][next][first][last][top][bottom][index][help] */
1707 {
1708     int health = pe__node_health(node);
1709     xmlNode *child = NULL;
1710 
1711     // Cluster membership
1712     if (node->details->online) {
1713         child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
1714                                   PCMK_VALUE_ONLINE);
1715         pcmk__xe_set_content(child, " online");
1716 
1717     } else {
1718         child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
1719                                   PCMK_VALUE_OFFLINE);
1720         pcmk__xe_set_content(child, " OFFLINE");
1721     }
1722 
1723     // Standby mode
1724     if (node->details->standby_onfail && (node->details->running_rsc != NULL)) {
1725         child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
1726                                   PCMK_VALUE_STANDBY);
1727         pcmk__xe_set_content(child,
1728                              " (in standby due to " PCMK_META_ON_FAIL ","
1729                              " with active resources)");
1730 
1731     } else if (node->details->standby_onfail) {
1732         child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
1733                                   PCMK_VALUE_STANDBY);
1734         pcmk__xe_set_content(child,
1735                              " (in standby due to " PCMK_META_ON_FAIL ")");
1736 
1737     } else if (node->details->standby && (node->details->running_rsc != NULL)) {
1738         child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
1739                                   PCMK_VALUE_STANDBY);
1740         pcmk__xe_set_content(child,
1741                              " (in standby, with active resources)");
1742 
1743     } else if (node->details->standby) {
1744         child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
1745                                   PCMK_VALUE_STANDBY);
1746         pcmk__xe_set_content(child, " (in standby)");
1747     }
1748 
1749     // Maintenance mode
1750     if (node->details->maintenance) {
1751         child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
1752                                   PCMK__VALUE_MAINT);
1753         pcmk__xe_set_content(child, " (in maintenance mode)");
1754     }
1755 
1756     // Node health
1757     if (health < 0) {
1758         child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
1759                                   PCMK__VALUE_HEALTH_RED);
1760         pcmk__xe_set_content(child, " (health is RED)");
1761 
1762     } else if (health == 0) {
1763         child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL,
1764                                   PCMK__VALUE_HEALTH_YELLOW);
1765         pcmk__xe_set_content(child, " (health is YELLOW)");
1766     }
1767 
1768     // Feature set
1769     if (pcmk_is_set(show_opts, pcmk_show_feature_set)) {
1770         const char *feature_set = get_node_feature_set(node);
1771         if (feature_set != NULL) {
1772             child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, NULL);
1773             pcmk__xe_set_content(child, ", feature set %s", feature_set);
1774         }
1775     }
1776 }
1777 
1778 PCMK__OUTPUT_ARGS("node", "pcmk_node_t *", "uint32_t", "bool",
     /* [previous][next][first][last][top][bottom][index][help] */
1779                   "GList *", "GList *")
1780 static int
1781 node_html(pcmk__output_t *out, va_list args) {
1782     pcmk_node_t *node = va_arg(args, pcmk_node_t *);
1783     uint32_t show_opts = va_arg(args, uint32_t);
1784     bool full = va_arg(args, int);
1785     GList *only_node = va_arg(args, GList *);
1786     GList *only_rsc = va_arg(args, GList *);
1787 
1788     char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
1789 
1790     if (full) {
1791         xmlNode *item_node = NULL;
1792         xmlNode *child = NULL;
1793 
1794         if (pcmk_all_flags_set(show_opts, pcmk_show_brief | pcmk_show_rscs_by_node)) {
1795             GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc);
1796 
1797             out->begin_list(out, NULL, NULL, "%s:", node_name);
1798             item_node = pcmk__output_xml_create_parent(out, "li", NULL);
1799             child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL);
1800             pcmk__xe_set_content(child, "Status:");
1801             status_node(node, item_node, show_opts);
1802 
1803             if (rscs != NULL) {
1804                 uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs;
1805                 out->begin_list(out, NULL, NULL, "Resources");
1806                 pe__rscs_brief_output(out, rscs, new_show_opts);
1807                 out->end_list(out);
1808             }
1809 
1810             pcmk__output_xml_pop_parent(out);
1811             out->end_list(out);
1812 
1813         } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
1814             GList *lpc2 = NULL;
1815             int rc = pcmk_rc_no_output;
1816 
1817             out->begin_list(out, NULL, NULL, "%s:", node_name);
1818             item_node = pcmk__output_xml_create_parent(out, "li", NULL);
1819             child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL);
1820             pcmk__xe_set_content(child, "Status:");
1821             status_node(node, item_node, show_opts);
1822 
1823             for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
1824                 pcmk_resource_t *rsc = (pcmk_resource_t *) lpc2->data;
1825                 PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Resources");
1826 
1827                 show_opts |= pcmk_show_rsc_only;
1828                 out->message(out, pcmk__map_element_name(rsc->xml), show_opts,
1829                              rsc, only_node, only_rsc);
1830             }
1831 
1832             PCMK__OUTPUT_LIST_FOOTER(out, rc);
1833             pcmk__output_xml_pop_parent(out);
1834             out->end_list(out);
1835 
1836         } else {
1837             item_node = pcmk__output_create_xml_node(out, "li", NULL);
1838             child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL,
1839                                       PCMK__VALUE_BOLD);
1840             pcmk__xe_set_content(child, "%s:", node_name);
1841             status_node(node, item_node, show_opts);
1842         }
1843     } else {
1844         out->begin_list(out, NULL, NULL, "%s:", node_name);
1845     }
1846 
1847     free(node_name);
1848     return pcmk_rc_ok;
1849 }
1850 
1851 /*!
1852  * \internal
1853  * \brief Get a human-friendly textual description of a node's status
1854  *
1855  * \param[in] node  Node to check
1856  *
1857  * \return String representation of node's status
1858  */
1859 static const char *
1860 node_text_status(const pcmk_node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
1861 {
1862     if (node->details->unclean) {
1863         if (node->details->online) {
1864             return "UNCLEAN (online)";
1865 
1866         } else if (node->details->pending) {
1867             return "UNCLEAN (pending)";
1868 
1869         } else {
1870             return "UNCLEAN (offline)";
1871         }
1872 
1873     } else if (node->details->pending) {
1874         return "pending";
1875 
1876     } else if (node->details->standby_onfail && node->details->online) {
1877         return "standby (" PCMK_META_ON_FAIL ")";
1878 
1879     } else if (node->details->standby) {
1880         if (node->details->online) {
1881             if (node->details->running_rsc) {
1882                 return "standby (with active resources)";
1883             } else {
1884                 return "standby";
1885             }
1886         } else {
1887             return "OFFLINE (standby)";
1888         }
1889 
1890     } else if (node->details->maintenance) {
1891         if (node->details->online) {
1892             return "maintenance";
1893         } else {
1894             return "OFFLINE (maintenance)";
1895         }
1896 
1897     } else if (node->details->online) {
1898         return "online";
1899     }
1900 
1901     return "OFFLINE";
1902 }
1903 
1904 PCMK__OUTPUT_ARGS("node", "pcmk_node_t *", "uint32_t", "bool", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
1905                   "GList *")
1906 static int
1907 node_text(pcmk__output_t *out, va_list args) {
1908     pcmk_node_t *node = va_arg(args, pcmk_node_t *);
1909     uint32_t show_opts = va_arg(args, uint32_t);
1910     bool full = va_arg(args, int);
1911     GList *only_node = va_arg(args, GList *);
1912     GList *only_rsc = va_arg(args, GList *);
1913 
1914     if (full) {
1915         char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
1916         GString *str = g_string_sized_new(64);
1917         int health = pe__node_health(node);
1918 
1919         // Create a summary line with node type, name, and status
1920         if (pcmk__is_guest_or_bundle_node(node)) {
1921             g_string_append(str, "GuestNode");
1922         } else if (pcmk__is_remote_node(node)) {
1923             g_string_append(str, "RemoteNode");
1924         } else {
1925             g_string_append(str, "Node");
1926         }
1927         pcmk__g_strcat(str, " ", node_name, ": ", node_text_status(node), NULL);
1928 
1929         if (health < 0) {
1930             g_string_append(str, " (health is RED)");
1931         } else if (health == 0) {
1932             g_string_append(str, " (health is YELLOW)");
1933         }
1934         if (pcmk_is_set(show_opts, pcmk_show_feature_set)) {
1935             const char *feature_set = get_node_feature_set(node);
1936             if (feature_set != NULL) {
1937                 pcmk__g_strcat(str, ", feature set ", feature_set, NULL);
1938             }
1939         }
1940 
1941         /* If we're grouping by node, print its resources */
1942         if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
1943             if (pcmk_is_set(show_opts, pcmk_show_brief)) {
1944                 GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc);
1945 
1946                 if (rscs != NULL) {
1947                     uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs;
1948                     out->begin_list(out, NULL, NULL, "%s", str->str);
1949                     out->begin_list(out, NULL, NULL, "Resources");
1950 
1951                     pe__rscs_brief_output(out, rscs, new_show_opts);
1952 
1953                     out->end_list(out);
1954                     out->end_list(out);
1955 
1956                     g_list_free(rscs);
1957                 }
1958 
1959             } else {
1960                 GList *gIter2 = NULL;
1961 
1962                 out->begin_list(out, NULL, NULL, "%s", str->str);
1963                 out->begin_list(out, NULL, NULL, "Resources");
1964 
1965                 for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) {
1966                     pcmk_resource_t *rsc = (pcmk_resource_t *) gIter2->data;
1967 
1968                     show_opts |= pcmk_show_rsc_only;
1969                     out->message(out, pcmk__map_element_name(rsc->xml),
1970                                  show_opts, rsc, only_node, only_rsc);
1971                 }
1972 
1973                 out->end_list(out);
1974                 out->end_list(out);
1975             }
1976         } else {
1977             out->list_item(out, NULL, "%s", str->str);
1978         }
1979 
1980         g_string_free(str, TRUE);
1981         free(node_name);
1982     } else {
1983         char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
1984         out->begin_list(out, NULL, NULL, "Node: %s", node_name);
1985         free(node_name);
1986     }
1987 
1988     return pcmk_rc_ok;
1989 }
1990 
1991 /*!
1992  * \internal
1993  * \brief Convert an integer health value to a string representation
1994  *
1995  * \param[in] health  Integer health value
1996  *
1997  * \retval \c PCMK_VALUE_RED if \p health is less than 0
1998  * \retval \c PCMK_VALUE_YELLOW if \p health is equal to 0
1999  * \retval \c PCMK_VALUE_GREEN if \p health is greater than 0
2000  */
2001 static const char *
2002 health_text(int health)
     /* [previous][next][first][last][top][bottom][index][help] */
2003 {
2004     if (health < 0) {
2005         return PCMK_VALUE_RED;
2006     } else if (health == 0) {
2007         return PCMK_VALUE_YELLOW;
2008     } else {
2009         return PCMK_VALUE_GREEN;
2010     }
2011 }
2012 
2013 /*!
2014  * \internal
2015  * \brief Convert a node type to a string representation
2016  *
2017  * \param[in] type  Node type
2018  *
2019  * \retval \c PCMK_VALUE_MEMBER if \p node_type is \c pcmk_node_variant_cluster
2020  * \retval \c PCMK_VALUE_REMOTE if \p node_type is \c pcmk_node_variant_remote
2021  * \retval \c PCMK__VALUE_PING if \p node_type is \c node_ping
2022  * \retval \c PCMK_VALUE_UNKNOWN otherwise
2023  */
2024 static const char *
2025 node_type_str(enum node_type type)
     /* [previous][next][first][last][top][bottom][index][help] */
2026 {
2027     switch (type) {
2028         case pcmk_node_variant_cluster:
2029             return PCMK_VALUE_MEMBER;
2030         case pcmk_node_variant_remote:
2031             return PCMK_VALUE_REMOTE;
2032         case node_ping:
2033             return PCMK__VALUE_PING;
2034         default:
2035             return PCMK_VALUE_UNKNOWN;
2036     }
2037 }
2038 
2039 PCMK__OUTPUT_ARGS("node", "pcmk_node_t *", "uint32_t", "bool", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
2040                   "GList *")
2041 static int
2042 node_xml(pcmk__output_t *out, va_list args) {
2043     pcmk_node_t *node = va_arg(args, pcmk_node_t *);
2044     uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
2045     bool full = va_arg(args, int);
2046     GList *only_node = va_arg(args, GList *);
2047     GList *only_rsc = va_arg(args, GList *);
2048 
2049     if (full) {
2050         const char *online = pcmk__btoa(node->details->online);
2051         const char *standby = pcmk__btoa(node->details->standby);
2052         const char *standby_onfail = pcmk__btoa(node->details->standby_onfail);
2053         const char *maintenance = pcmk__btoa(node->details->maintenance);
2054         const char *pending = pcmk__btoa(node->details->pending);
2055         const char *unclean = pcmk__btoa(node->details->unclean);
2056         const char *health = health_text(pe__node_health(node));
2057         const char *feature_set = get_node_feature_set(node);
2058         const char *shutdown = pcmk__btoa(node->details->shutdown);
2059         const char *expected_up = pcmk__btoa(node->details->expected_up);
2060         const char *is_dc = pcmk__btoa(node->details->is_dc);
2061         int length = g_list_length(node->details->running_rsc);
2062         char *resources_running = pcmk__itoa(length);
2063         const char *node_type = node_type_str(node->details->type);
2064 
2065         int rc = pcmk_rc_ok;
2066 
2067         rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_NODE,
2068                                       PCMK_XA_NAME, node->details->uname,
2069                                       PCMK_XA_ID, node->details->id,
2070                                       PCMK_XA_ONLINE, online,
2071                                       PCMK_XA_STANDBY, standby,
2072                                       PCMK_XA_STANDBY_ONFAIL, standby_onfail,
2073                                       PCMK_XA_MAINTENANCE, maintenance,
2074                                       PCMK_XA_PENDING, pending,
2075                                       PCMK_XA_UNCLEAN, unclean,
2076                                       PCMK_XA_HEALTH, health,
2077                                       PCMK_XA_FEATURE_SET, feature_set,
2078                                       PCMK_XA_SHUTDOWN, shutdown,
2079                                       PCMK_XA_EXPECTED_UP, expected_up,
2080                                       PCMK_XA_IS_DC, is_dc,
2081                                       PCMK_XA_RESOURCES_RUNNING, resources_running,
2082                                       PCMK_XA_TYPE, node_type,
2083                                       NULL);
2084 
2085         free(resources_running);
2086         pcmk__assert(rc == pcmk_rc_ok);
2087 
2088         if (pcmk__is_guest_or_bundle_node(node)) {
2089             xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out);
2090             crm_xml_add(xml_node, PCMK_XA_ID_AS_RESOURCE,
2091                         node->details->remote_rsc->container->id);
2092         }
2093 
2094         if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
2095             GList *lpc = NULL;
2096 
2097             for (lpc = node->details->running_rsc; lpc != NULL; lpc = lpc->next) {
2098                 pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data;
2099 
2100                 show_opts |= pcmk_show_rsc_only;
2101                 out->message(out, pcmk__map_element_name(rsc->xml), show_opts,
2102                              rsc, only_node, only_rsc);
2103             }
2104         }
2105 
2106         out->end_list(out);
2107     } else {
2108         pcmk__output_xml_create_parent(out, PCMK_XE_NODE,
2109                                        PCMK_XA_NAME, node->details->uname,
2110                                        NULL);
2111     }
2112 
2113     return pcmk_rc_ok;
2114 }
2115 
2116 PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
2117 static int
2118 node_attribute_text(pcmk__output_t *out, va_list args) {
2119     const char *name = va_arg(args, const char *);
2120     const char *value = va_arg(args, const char *);
2121     bool add_extra = va_arg(args, int);
2122     int expected_score = va_arg(args, int);
2123 
2124     if (add_extra) {
2125         int v;
2126 
2127         if (value == NULL) {
2128             v = 0;
2129         } else {
2130             pcmk__scan_min_int(value, &v, INT_MIN);
2131         }
2132         if (v <= 0) {
2133             out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is lost", name, value);
2134         } else if (v < expected_score) {
2135             out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is degraded (Expected=%d)", name, value, expected_score);
2136         } else {
2137             out->list_item(out, NULL, "%-32s\t: %-10s", name, value);
2138         }
2139     } else {
2140         out->list_item(out, NULL, "%-32s\t: %-10s", name, value);
2141     }
2142 
2143     return pcmk_rc_ok;
2144 }
2145 
2146 PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
2147 static int
2148 node_attribute_html(pcmk__output_t *out, va_list args) {
2149     const char *name = va_arg(args, const char *);
2150     const char *value = va_arg(args, const char *);
2151     bool add_extra = va_arg(args, int);
2152     int expected_score = va_arg(args, int);
2153 
2154     if (add_extra) {
2155         int v = 0;
2156         xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li", NULL);
2157         xmlNode *child = NULL;
2158 
2159         if (value != NULL) {
2160             pcmk__scan_min_int(value, &v, INT_MIN);
2161         }
2162 
2163         child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL);
2164         pcmk__xe_set_content(child, "%s: %s", name, value);
2165 
2166         if (v <= 0) {
2167             child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL,
2168                                       PCMK__VALUE_BOLD);
2169             pcmk__xe_set_content(child, "(connectivity is lost)");
2170 
2171         } else if (v < expected_score) {
2172             child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL,
2173                                       PCMK__VALUE_BOLD);
2174             pcmk__xe_set_content(child,
2175                                  "(connectivity is degraded -- expected %d)",
2176                                  expected_score);
2177         }
2178     } else {
2179         out->list_item(out, NULL, "%s: %s", name, value);
2180     }
2181 
2182     return pcmk_rc_ok;
2183 }
2184 
2185 PCMK__OUTPUT_ARGS("node-and-op", "pcmk_scheduler_t *", "xmlNode *")
     /* [previous][next][first][last][top][bottom][index][help] */
2186 static int
2187 node_and_op(pcmk__output_t *out, va_list args) {
2188     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
2189     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
2190 
2191     pcmk_resource_t *rsc = NULL;
2192     gchar *node_str = NULL;
2193     char *last_change_str = NULL;
2194 
2195     const char *op_rsc = crm_element_value(xml_op, PCMK_XA_RESOURCE);
2196     int status;
2197     time_t last_change = 0;
2198 
2199     pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &status,
2200                        PCMK_EXEC_UNKNOWN);
2201 
2202     rsc = pe_find_resource(scheduler->resources, op_rsc);
2203 
2204     if (rsc) {
2205         const pcmk_node_t *node = pcmk__current_node(rsc);
2206         const char *target_role = g_hash_table_lookup(rsc->meta,
2207                                                       PCMK_META_TARGET_ROLE);
2208         uint32_t show_opts = pcmk_show_rsc_only | pcmk_show_pending;
2209 
2210         if (node == NULL) {
2211             node = rsc->pending_node;
2212         }
2213 
2214         node_str = pcmk__native_output_string(rsc, rsc_printable_id(rsc), node,
2215                                               show_opts, target_role, false);
2216     } else {
2217         node_str = crm_strdup_printf("Unknown resource %s", op_rsc);
2218     }
2219 
2220     if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
2221                                 &last_change) == pcmk_ok) {
2222         const char *exec_time = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
2223 
2224         last_change_str = crm_strdup_printf(", %s='%s', exec=%sms",
2225                                             PCMK_XA_LAST_RC_CHANGE,
2226                                             pcmk__trim(ctime(&last_change)),
2227                                             exec_time);
2228     }
2229 
2230     out->list_item(out, NULL, "%s: %s (node=%s, call=%s, rc=%s%s): %s",
2231                    node_str, pcmk__xe_history_key(xml_op),
2232                    crm_element_value(xml_op, PCMK_XA_UNAME),
2233                    crm_element_value(xml_op, PCMK__XA_CALL_ID),
2234                    crm_element_value(xml_op, PCMK__XA_RC_CODE),
2235                    last_change_str ? last_change_str : "",
2236                    pcmk_exec_status_str(status));
2237 
2238     g_free(node_str);
2239     free(last_change_str);
2240     return pcmk_rc_ok;
2241 }
2242 
2243 PCMK__OUTPUT_ARGS("node-and-op", "pcmk_scheduler_t *", "xmlNode *")
     /* [previous][next][first][last][top][bottom][index][help] */
2244 static int
2245 node_and_op_xml(pcmk__output_t *out, va_list args) {
2246     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
2247     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
2248 
2249     pcmk_resource_t *rsc = NULL;
2250     const char *uname = crm_element_value(xml_op, PCMK_XA_UNAME);
2251     const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID);
2252     const char *rc_s = crm_element_value(xml_op, PCMK__XA_RC_CODE);
2253     const char *status_s = NULL;
2254     const char *op_rsc = crm_element_value(xml_op, PCMK_XA_RESOURCE);
2255     int status;
2256     time_t last_change = 0;
2257     xmlNode *node = NULL;
2258 
2259     pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS),
2260                        &status, PCMK_EXEC_UNKNOWN);
2261     status_s = pcmk_exec_status_str(status);
2262 
2263     node = pcmk__output_create_xml_node(out, PCMK_XE_OPERATION,
2264                                         PCMK_XA_OP, pcmk__xe_history_key(xml_op),
2265                                         PCMK_XA_NODE, uname,
2266                                         PCMK_XA_CALL, call_id,
2267                                         PCMK_XA_RC, rc_s,
2268                                         PCMK_XA_STATUS, status_s,
2269                                         NULL);
2270 
2271     rsc = pe_find_resource(scheduler->resources, op_rsc);
2272 
2273     if (rsc) {
2274         const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
2275         const char *provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER);
2276         const char *kind = crm_element_value(rsc->xml, PCMK_XA_TYPE);
2277         bool has_provider = pcmk_is_set(pcmk_get_ra_caps(class),
2278                                         pcmk_ra_cap_provider);
2279 
2280         char *agent_tuple = crm_strdup_printf("%s:%s:%s",
2281                                               class,
2282                                               (has_provider? provider : ""),
2283                                               kind);
2284 
2285         pcmk__xe_set_props(node,
2286                            PCMK_XA_RSC, rsc_printable_id(rsc),
2287                            PCMK_XA_AGENT, agent_tuple,
2288                            NULL);
2289         free(agent_tuple);
2290     }
2291 
2292     if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
2293                                 &last_change) == pcmk_ok) {
2294         const char *last_rc_change = pcmk__trim(ctime(&last_change));
2295         const char *exec_time = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
2296 
2297         pcmk__xe_set_props(node,
2298                            PCMK_XA_LAST_RC_CHANGE, last_rc_change,
2299                            PCMK_XA_EXEC_TIME, exec_time,
2300                            NULL);
2301     }
2302 
2303     return pcmk_rc_ok;
2304 }
2305 
2306 PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
2307 static int
2308 node_attribute_xml(pcmk__output_t *out, va_list args) {
2309     const char *name = va_arg(args, const char *);
2310     const char *value = va_arg(args, const char *);
2311     bool add_extra = va_arg(args, int);
2312     int expected_score = va_arg(args, int);
2313 
2314     xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_ATTRIBUTE,
2315                                                    PCMK_XA_NAME, name,
2316                                                    PCMK_XA_VALUE, value,
2317                                                    NULL);
2318 
2319     if (add_extra) {
2320         char *buf = pcmk__itoa(expected_score);
2321         crm_xml_add(node, PCMK_XA_EXPECTED, buf);
2322         free(buf);
2323     }
2324 
2325     return pcmk_rc_ok;
2326 }
2327 
2328 PCMK__OUTPUT_ARGS("node-attribute-list", "pcmk_scheduler_t *", "uint32_t",
     /* [previous][next][first][last][top][bottom][index][help] */
2329                   "bool", "GList *", "GList *")
2330 static int
2331 node_attribute_list(pcmk__output_t *out, va_list args) {
2332     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
2333     uint32_t show_opts = va_arg(args, uint32_t);
2334     bool print_spacer = va_arg(args, int);
2335     GList *only_node = va_arg(args, GList *);
2336     GList *only_rsc = va_arg(args, GList *);
2337 
2338     int rc = pcmk_rc_no_output;
2339 
2340     /* Display each node's attributes */
2341     for (GList *gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
2342         pcmk_node_t *node = gIter->data;
2343 
2344         GList *attr_list = NULL;
2345         GHashTableIter iter;
2346         gpointer key;
2347 
2348         if (!node || !node->details || !node->details->online) {
2349             continue;
2350         }
2351 
2352         g_hash_table_iter_init(&iter, node->details->attrs);
2353         while (g_hash_table_iter_next (&iter, &key, NULL)) {
2354             attr_list = filter_attr_list(attr_list, key);
2355         }
2356 
2357         if (attr_list == NULL) {
2358             continue;
2359         }
2360 
2361         if (!pcmk__str_in_list(node->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
2362             g_list_free(attr_list);
2363             continue;
2364         }
2365 
2366         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node Attributes");
2367 
2368         out->message(out, "node", node, show_opts, false, only_node, only_rsc);
2369 
2370         for (GList *aIter = attr_list; aIter != NULL; aIter = aIter->next) {
2371             const char *name = aIter->data;
2372             const char *value = NULL;
2373             int expected_score = 0;
2374             bool add_extra = false;
2375 
2376             value = pcmk__node_attr(node, name, NULL, pcmk__rsc_node_current);
2377 
2378             add_extra = add_extra_info(node, node->details->running_rsc,
2379                                        scheduler, name, &expected_score);
2380 
2381             /* Print attribute name and value */
2382             out->message(out, "node-attribute", name, value, add_extra,
2383                          expected_score);
2384         }
2385 
2386         g_list_free(attr_list);
2387         out->end_list(out);
2388     }
2389 
2390     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2391     return rc;
2392 }
2393 
2394 PCMK__OUTPUT_ARGS("node-capacity", "const pcmk_node_t *", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
2395 static int
2396 node_capacity(pcmk__output_t *out, va_list args)
2397 {
2398     const pcmk_node_t *node = va_arg(args, pcmk_node_t *);
2399     const char *comment = va_arg(args, const char *);
2400 
2401     char *dump_text = crm_strdup_printf("%s: %s capacity:",
2402                                         comment, pcmk__node_name(node));
2403 
2404     g_hash_table_foreach(node->details->utilization, append_dump_text, &dump_text);
2405     out->list_item(out, NULL, "%s", dump_text);
2406     free(dump_text);
2407 
2408     return pcmk_rc_ok;
2409 }
2410 
2411 PCMK__OUTPUT_ARGS("node-capacity", "const pcmk_node_t *", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
2412 static int
2413 node_capacity_xml(pcmk__output_t *out, va_list args)
2414 {
2415     const pcmk_node_t *node = va_arg(args, pcmk_node_t *);
2416     const char *uname = node->details->uname;
2417     const char *comment = va_arg(args, const char *);
2418 
2419     xmlNodePtr xml_node = pcmk__output_create_xml_node(out, PCMK_XE_CAPACITY,
2420                                                        PCMK_XA_NODE, uname,
2421                                                        PCMK_XA_COMMENT, comment,
2422                                                        NULL);
2423     g_hash_table_foreach(node->details->utilization, add_dump_node, xml_node);
2424 
2425     return pcmk_rc_ok;
2426 }
2427 
2428 PCMK__OUTPUT_ARGS("node-history-list", "pcmk_scheduler_t *", "pcmk_node_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
2429                   "xmlNode *", "GList *", "GList *", "uint32_t", "uint32_t")
2430 static int
2431 node_history_list(pcmk__output_t *out, va_list args) {
2432     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
2433     pcmk_node_t *node = va_arg(args, pcmk_node_t *);
2434     xmlNode *node_state = va_arg(args, xmlNode *);
2435     GList *only_node = va_arg(args, GList *);
2436     GList *only_rsc = va_arg(args, GList *);
2437     uint32_t section_opts = va_arg(args, uint32_t);
2438     uint32_t show_opts = va_arg(args, uint32_t);
2439 
2440     xmlNode *lrm_rsc = NULL;
2441     xmlNode *rsc_entry = NULL;
2442     int rc = pcmk_rc_no_output;
2443 
2444     lrm_rsc = pcmk__xe_first_child(node_state, PCMK__XE_LRM, NULL, NULL);
2445     lrm_rsc = pcmk__xe_first_child(lrm_rsc, PCMK__XE_LRM_RESOURCES, NULL, NULL);
2446 
2447     /* Print history of each of the node's resources */
2448     for (rsc_entry = pcmk__xe_first_child(lrm_rsc, PCMK__XE_LRM_RESOURCE, NULL,
2449                                           NULL);
2450          rsc_entry != NULL; rsc_entry = pcmk__xe_next_same(rsc_entry)) {
2451 
2452         const char *rsc_id = crm_element_value(rsc_entry, PCMK_XA_ID);
2453         pcmk_resource_t *rsc = NULL;
2454         const pcmk_resource_t *parent = NULL;
2455 
2456         if (rsc_id == NULL) {
2457             continue; // Malformed entry
2458         }
2459 
2460         rsc = pe_find_resource(scheduler->resources, rsc_id);
2461         if (rsc == NULL) {
2462             continue; // Resource was removed from configuration
2463         }
2464 
2465         /* We can't use is_filtered here to filter group resources.  For is_filtered,
2466          * we have to decide whether to check the parent or not.  If we check the
2467          * parent, all elements of a group will always be printed because that's how
2468          * is_filtered works for groups.  If we do not check the parent, sometimes
2469          * this will filter everything out.
2470          *
2471          * For other resource types, is_filtered is okay.
2472          */
2473         parent = pe__const_top_resource(rsc, false);
2474         if (pcmk__is_group(parent)) {
2475             if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
2476                                    pcmk__str_star_matches)
2477                 && !pcmk__str_in_list(rsc_printable_id(parent), only_rsc,
2478                                       pcmk__str_star_matches)) {
2479                 continue;
2480             }
2481         } else {
2482             if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
2483                 continue;
2484             }
2485         }
2486 
2487         if (!pcmk_is_set(section_opts, pcmk_section_operations)) {
2488             time_t last_failure = 0;
2489             int failcount = pe_get_failcount(node, rsc, &last_failure,
2490                                              pcmk__fc_default, NULL);
2491 
2492             if (failcount <= 0) {
2493                 continue;
2494             }
2495 
2496             if (rc == pcmk_rc_no_output) {
2497                 rc = pcmk_rc_ok;
2498                 out->message(out, "node", node, show_opts, false, only_node,
2499                              only_rsc);
2500             }
2501 
2502             out->message(out, "resource-history", rsc, rsc_id, false,
2503                          failcount, last_failure, false);
2504         } else {
2505             GList *op_list = get_operation_list(rsc_entry);
2506             pcmk_resource_t *rsc = NULL;
2507 
2508             if (op_list == NULL) {
2509                 continue;
2510             }
2511 
2512             rsc = pe_find_resource(scheduler->resources,
2513                                    crm_element_value(rsc_entry, PCMK_XA_ID));
2514 
2515             if (rc == pcmk_rc_no_output) {
2516                 rc = pcmk_rc_ok;
2517                 out->message(out, "node", node, show_opts, false, only_node,
2518                              only_rsc);
2519             }
2520 
2521             out->message(out, "resource-operation-list", scheduler, rsc, node,
2522                          op_list, show_opts);
2523         }
2524     }
2525 
2526     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2527     return rc;
2528 }
2529 
2530 PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2531 static int
2532 node_list_html(pcmk__output_t *out, va_list args) {
2533     GList *nodes = va_arg(args, GList *);
2534     GList *only_node = va_arg(args, GList *);
2535     GList *only_rsc = va_arg(args, GList *);
2536     uint32_t show_opts = va_arg(args, uint32_t);
2537     bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
2538 
2539     int rc = pcmk_rc_no_output;
2540 
2541     for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
2542         pcmk_node_t *node = (pcmk_node_t *) gIter->data;
2543 
2544         if (!pcmk__str_in_list(node->details->uname, only_node,
2545                                pcmk__str_star_matches|pcmk__str_casei)) {
2546             continue;
2547         }
2548 
2549         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Node List");
2550 
2551         out->message(out, "node", node, show_opts, true, only_node, only_rsc);
2552     }
2553 
2554     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2555     return rc;
2556 }
2557 
2558 PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2559 static int
2560 node_list_text(pcmk__output_t *out, va_list args) {
2561     GList *nodes = va_arg(args, GList *);
2562     GList *only_node = va_arg(args, GList *);
2563     GList *only_rsc = va_arg(args, GList *);
2564     uint32_t show_opts = va_arg(args, uint32_t);
2565     bool print_spacer = va_arg(args, int);
2566 
2567     /* space-separated lists of node names */
2568     GString *online_nodes = NULL;
2569     GString *online_remote_nodes = NULL;
2570     GString *online_guest_nodes = NULL;
2571     GString *offline_nodes = NULL;
2572     GString *offline_remote_nodes = NULL;
2573 
2574     int rc = pcmk_rc_no_output;
2575 
2576     for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
2577         pcmk_node_t *node = (pcmk_node_t *) gIter->data;
2578         char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
2579 
2580         if (!pcmk__str_in_list(node->details->uname, only_node,
2581                                pcmk__str_star_matches|pcmk__str_casei)) {
2582             free(node_name);
2583             continue;
2584         }
2585 
2586         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node List");
2587 
2588         // Determine whether to display node individually or in a list
2589         if (node->details->unclean || node->details->pending
2590             || (node->details->standby_onfail && node->details->online)
2591             || node->details->standby || node->details->maintenance
2592             || pcmk_is_set(show_opts, pcmk_show_rscs_by_node)
2593             || pcmk_is_set(show_opts, pcmk_show_feature_set)
2594             || (pe__node_health(node) <= 0)) {
2595             // Display node individually
2596 
2597         } else if (node->details->online) {
2598             // Display online node in a list
2599             if (pcmk__is_guest_or_bundle_node(node)) {
2600                 pcmk__add_word(&online_guest_nodes, 1024, node_name);
2601 
2602             } else if (pcmk__is_remote_node(node)) {
2603                 pcmk__add_word(&online_remote_nodes, 1024, node_name);
2604 
2605             } else {
2606                 pcmk__add_word(&online_nodes, 1024, node_name);
2607             }
2608             free(node_name);
2609             continue;
2610 
2611         } else {
2612             // Display offline node in a list
2613             if (pcmk__is_remote_node(node)) {
2614                 pcmk__add_word(&offline_remote_nodes, 1024, node_name);
2615 
2616             } else if (pcmk__is_guest_or_bundle_node(node)) {
2617                 /* ignore offline guest nodes */
2618 
2619             } else {
2620                 pcmk__add_word(&offline_nodes, 1024, node_name);
2621             }
2622             free(node_name);
2623             continue;
2624         }
2625 
2626         /* If we get here, node is in bad state, or we're grouping by node */
2627         out->message(out, "node", node, show_opts, true, only_node, only_rsc);
2628         free(node_name);
2629     }
2630 
2631     /* If we're not grouping by node, summarize nodes by status */
2632     if (online_nodes != NULL) {
2633         out->list_item(out, "Online", "[ %s ]",
2634                        (const char *) online_nodes->str);
2635         g_string_free(online_nodes, TRUE);
2636     }
2637     if (offline_nodes != NULL) {
2638         out->list_item(out, "OFFLINE", "[ %s ]",
2639                        (const char *) offline_nodes->str);
2640         g_string_free(offline_nodes, TRUE);
2641     }
2642     if (online_remote_nodes) {
2643         out->list_item(out, "RemoteOnline", "[ %s ]",
2644                        (const char *) online_remote_nodes->str);
2645         g_string_free(online_remote_nodes, TRUE);
2646     }
2647     if (offline_remote_nodes) {
2648         out->list_item(out, "RemoteOFFLINE", "[ %s ]",
2649                        (const char *) offline_remote_nodes->str);
2650         g_string_free(offline_remote_nodes, TRUE);
2651     }
2652     if (online_guest_nodes != NULL) {
2653         out->list_item(out, "GuestOnline", "[ %s ]",
2654                        (const char *) online_guest_nodes->str);
2655         g_string_free(online_guest_nodes, TRUE);
2656     }
2657 
2658     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2659     return rc;
2660 }
2661 
2662 PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2663 static int
2664 node_list_xml(pcmk__output_t *out, va_list args) {
2665     GList *nodes = va_arg(args, GList *);
2666     GList *only_node = va_arg(args, GList *);
2667     GList *only_rsc = va_arg(args, GList *);
2668     uint32_t show_opts = va_arg(args, uint32_t);
2669     bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
2670 
2671     /* PCMK_XE_NODES acts as the list's element name for CLI tools that use
2672      * pcmk__output_enable_list_element.  Otherwise PCMK_XE_NODES is the
2673      * value of the list's PCMK_XA_NAME attribute.
2674      */
2675     out->begin_list(out, NULL, NULL, PCMK_XE_NODES);
2676     for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
2677         pcmk_node_t *node = (pcmk_node_t *) gIter->data;
2678 
2679         if (!pcmk__str_in_list(node->details->uname, only_node,
2680                                pcmk__str_star_matches|pcmk__str_casei)) {
2681             continue;
2682         }
2683 
2684         out->message(out, "node", node, show_opts, true, only_node, only_rsc);
2685     }
2686     out->end_list(out);
2687 
2688     return pcmk_rc_ok;
2689 }
2690 
2691 PCMK__OUTPUT_ARGS("node-summary", "pcmk_scheduler_t *", "GList *", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
2692                   "uint32_t", "uint32_t", "bool")
2693 static int
2694 node_summary(pcmk__output_t *out, va_list args) {
2695     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
2696     GList *only_node = va_arg(args, GList *);
2697     GList *only_rsc = va_arg(args, GList *);
2698     uint32_t section_opts = va_arg(args, uint32_t);
2699     uint32_t show_opts = va_arg(args, uint32_t);
2700     bool print_spacer = va_arg(args, int);
2701 
2702     xmlNode *node_state = NULL;
2703     xmlNode *cib_status = pcmk_find_cib_element(scheduler->input,
2704                                                 PCMK_XE_STATUS);
2705     int rc = pcmk_rc_no_output;
2706 
2707     if (xmlChildElementCount(cib_status) == 0) {
2708         return rc;
2709     }
2710 
2711     for (node_state = pcmk__xe_first_child(cib_status, PCMK__XE_NODE_STATE,
2712                                            NULL, NULL);
2713          node_state != NULL; node_state = pcmk__xe_next_same(node_state)) {
2714 
2715         pcmk_node_t *node = pe_find_node_id(scheduler->nodes,
2716                                             pcmk__xe_id(node_state));
2717 
2718         if (!node || !node->details || !node->details->online) {
2719             continue;
2720         }
2721 
2722         if (!pcmk__str_in_list(node->details->uname, only_node,
2723                                pcmk__str_star_matches|pcmk__str_casei)) {
2724             continue;
2725         }
2726 
2727         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc,
2728                                  pcmk_is_set(section_opts, pcmk_section_operations) ? "Operations" : "Migration Summary");
2729 
2730         out->message(out, "node-history-list", scheduler, node, node_state,
2731                      only_node, only_rsc, section_opts, show_opts);
2732     }
2733 
2734     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2735     return rc;
2736 }
2737 
2738 PCMK__OUTPUT_ARGS("node-weight", "const pcmk_resource_t *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
2739                   "const char *", "const char *")
2740 static int
2741 node_weight(pcmk__output_t *out, va_list args)
2742 {
2743     const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *);
2744     const char *prefix = va_arg(args, const char *);
2745     const char *uname = va_arg(args, const char *);
2746     const char *score = va_arg(args, const char *);
2747 
2748     if (rsc) {
2749         out->list_item(out, NULL, "%s: %s allocation score on %s: %s",
2750                        prefix, rsc->id, uname, score);
2751     } else {
2752         out->list_item(out, NULL, "%s: %s = %s", prefix, uname, score);
2753     }
2754 
2755     return pcmk_rc_ok;
2756 }
2757 
2758 PCMK__OUTPUT_ARGS("node-weight", "const pcmk_resource_t *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
2759                   "const char *", "const char *")
2760 static int
2761 node_weight_xml(pcmk__output_t *out, va_list args)
2762 {
2763     const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *);
2764     const char *prefix = va_arg(args, const char *);
2765     const char *uname = va_arg(args, const char *);
2766     const char *score = va_arg(args, const char *);
2767 
2768     xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_NODE_WEIGHT,
2769                                                    PCMK_XA_FUNCTION, prefix,
2770                                                    PCMK_XA_NODE, uname,
2771                                                    PCMK_XA_SCORE, score,
2772                                                    NULL);
2773 
2774     if (rsc) {
2775         crm_xml_add(node, PCMK_XA_ID, rsc->id);
2776     }
2777 
2778     return pcmk_rc_ok;
2779 }
2780 
2781 PCMK__OUTPUT_ARGS("op-history", "xmlNode *", "const char *", "const char *", "int", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
2782 static int
2783 op_history_text(pcmk__output_t *out, va_list args) {
2784     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
2785     const char *task = va_arg(args, const char *);
2786     const char *interval_ms_s = va_arg(args, const char *);
2787     int rc = va_arg(args, int);
2788     uint32_t show_opts = va_arg(args, uint32_t);
2789 
2790     char *buf = op_history_string(xml_op, task, interval_ms_s, rc,
2791                                   pcmk_is_set(show_opts, pcmk_show_timing));
2792 
2793     out->list_item(out, NULL, "%s", buf);
2794 
2795     free(buf);
2796     return pcmk_rc_ok;
2797 }
2798 
2799 PCMK__OUTPUT_ARGS("op-history", "xmlNode *", "const char *", "const char *", "int", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
2800 static int
2801 op_history_xml(pcmk__output_t *out, va_list args) {
2802     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
2803     const char *task = va_arg(args, const char *);
2804     const char *interval_ms_s = va_arg(args, const char *);
2805     int rc = va_arg(args, int);
2806     uint32_t show_opts = va_arg(args, uint32_t);
2807 
2808     const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID);
2809     char *rc_s = pcmk__itoa(rc);
2810     const char *rc_text = services_ocf_exitcode_str(rc);
2811     xmlNodePtr node = NULL;
2812 
2813     node = pcmk__output_create_xml_node(out, PCMK_XE_OPERATION_HISTORY,
2814                                         PCMK_XA_CALL, call_id,
2815                                         PCMK_XA_TASK, task,
2816                                         PCMK_XA_RC, rc_s,
2817                                         PCMK_XA_RC_TEXT, rc_text,
2818                                         NULL);
2819     free(rc_s);
2820 
2821     if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) {
2822         char *s = crm_strdup_printf("%sms", interval_ms_s);
2823         crm_xml_add(node, PCMK_XA_INTERVAL, s);
2824         free(s);
2825     }
2826 
2827     if (pcmk_is_set(show_opts, pcmk_show_timing)) {
2828         const char *value = NULL;
2829         time_t epoch = 0;
2830 
2831         if ((crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
2832                                      &epoch) == pcmk_ok) && (epoch > 0)) {
2833             char *s = pcmk__epoch2str(&epoch, 0);
2834             crm_xml_add(node, PCMK_XA_LAST_RC_CHANGE, s);
2835             free(s);
2836         }
2837 
2838         value = crm_element_value(xml_op, PCMK_XA_EXEC_TIME);
2839         if (value) {
2840             char *s = crm_strdup_printf("%sms", value);
2841             crm_xml_add(node, PCMK_XA_EXEC_TIME, s);
2842             free(s);
2843         }
2844         value = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME);
2845         if (value) {
2846             char *s = crm_strdup_printf("%sms", value);
2847             crm_xml_add(node, PCMK_XA_QUEUE_TIME, s);
2848             free(s);
2849         }
2850     }
2851 
2852     return pcmk_rc_ok;
2853 }
2854 
2855 PCMK__OUTPUT_ARGS("promotion-score", "pcmk_resource_t *", "pcmk_node_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
2856                   "const char *")
2857 static int
2858 promotion_score(pcmk__output_t *out, va_list args)
2859 {
2860     pcmk_resource_t *child_rsc = va_arg(args, pcmk_resource_t *);
2861     pcmk_node_t *chosen = va_arg(args, pcmk_node_t *);
2862     const char *score = va_arg(args, const char *);
2863 
2864     if (chosen == NULL) {
2865         out->list_item(out, NULL, "%s promotion score (inactive): %s",
2866                        child_rsc->id, score);
2867     } else {
2868         out->list_item(out, NULL, "%s promotion score on %s: %s",
2869                        child_rsc->id, pcmk__node_name(chosen), score);
2870     }
2871     return pcmk_rc_ok;
2872 }
2873 
2874 PCMK__OUTPUT_ARGS("promotion-score", "pcmk_resource_t *", "pcmk_node_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
2875                   "const char *")
2876 static int
2877 promotion_score_xml(pcmk__output_t *out, va_list args)
2878 {
2879     pcmk_resource_t *child_rsc = va_arg(args, pcmk_resource_t *);
2880     pcmk_node_t *chosen = va_arg(args, pcmk_node_t *);
2881     const char *score = va_arg(args, const char *);
2882 
2883     xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_PROMOTION_SCORE,
2884                                                    PCMK_XA_ID, child_rsc->id,
2885                                                    PCMK_XA_SCORE, score,
2886                                                    NULL);
2887 
2888     if (chosen) {
2889         crm_xml_add(node, PCMK_XA_NODE, chosen->details->uname);
2890     }
2891 
2892     return pcmk_rc_ok;
2893 }
2894 
2895 PCMK__OUTPUT_ARGS("resource-config", "const pcmk_resource_t *", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2896 static int
2897 resource_config(pcmk__output_t *out, va_list args) {
2898     const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *);
2899     GString *xml_buf = g_string_sized_new(1024);
2900     bool raw = va_arg(args, int);
2901 
2902     formatted_xml_buf(rsc, xml_buf, raw);
2903 
2904     out->output_xml(out, PCMK_XE_XML, xml_buf->str);
2905 
2906     g_string_free(xml_buf, TRUE);
2907     return pcmk_rc_ok;
2908 }
2909 
2910 PCMK__OUTPUT_ARGS("resource-config", "const pcmk_resource_t *", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2911 static int
2912 resource_config_text(pcmk__output_t *out, va_list args) {
2913     pcmk__formatted_printf(out, "Resource XML:\n");
2914     return resource_config(out, args);
2915 }
2916 
2917 PCMK__OUTPUT_ARGS("resource-history", "pcmk_resource_t *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
2918                   "bool", "int", "time_t", "bool")
2919 static int
2920 resource_history_text(pcmk__output_t *out, va_list args) {
2921     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
2922     const char *rsc_id = va_arg(args, const char *);
2923     bool all = va_arg(args, int);
2924     int failcount = va_arg(args, int);
2925     time_t last_failure = va_arg(args, time_t);
2926     bool as_header = va_arg(args, int);
2927 
2928     char *buf = resource_history_string(rsc, rsc_id, all, failcount, last_failure);
2929 
2930     if (as_header) {
2931         out->begin_list(out, NULL, NULL, "%s", buf);
2932     } else {
2933         out->list_item(out, NULL, "%s", buf);
2934     }
2935 
2936     free(buf);
2937     return pcmk_rc_ok;
2938 }
2939 
2940 PCMK__OUTPUT_ARGS("resource-history", "pcmk_resource_t *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
2941                   "bool", "int", "time_t", "bool")
2942 static int
2943 resource_history_xml(pcmk__output_t *out, va_list args) {
2944     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
2945     const char *rsc_id = va_arg(args, const char *);
2946     bool all = va_arg(args, int);
2947     int failcount = va_arg(args, int);
2948     time_t last_failure = va_arg(args, time_t);
2949     bool as_header = va_arg(args, int);
2950 
2951     xmlNodePtr node = pcmk__output_xml_create_parent(out,
2952                                                      PCMK_XE_RESOURCE_HISTORY,
2953                                                      PCMK_XA_ID, rsc_id,
2954                                                      NULL);
2955 
2956     if (rsc == NULL) {
2957         pcmk__xe_set_bool_attr(node, PCMK_XA_ORPHAN, true);
2958     } else if (all || failcount || last_failure > 0) {
2959         char *migration_s = pcmk__itoa(rsc->migration_threshold);
2960 
2961         pcmk__xe_set_props(node,
2962                            PCMK_XA_ORPHAN, PCMK_VALUE_FALSE,
2963                            PCMK_META_MIGRATION_THRESHOLD, migration_s,
2964                            NULL);
2965         free(migration_s);
2966 
2967         if (failcount > 0) {
2968             char *s = pcmk__itoa(failcount);
2969 
2970             crm_xml_add(node, PCMK_XA_FAIL_COUNT, s);
2971             free(s);
2972         }
2973 
2974         if (last_failure > 0) {
2975             char *s = pcmk__epoch2str(&last_failure, 0);
2976 
2977             crm_xml_add(node, PCMK_XA_LAST_FAILURE, s);
2978             free(s);
2979         }
2980     }
2981 
2982     if (!as_header) {
2983         pcmk__output_xml_pop_parent(out);
2984     }
2985 
2986     return pcmk_rc_ok;
2987 }
2988 
2989 static void
2990 print_resource_header(pcmk__output_t *out, uint32_t show_opts)
     /* [previous][next][first][last][top][bottom][index][help] */
2991 {
2992     if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
2993         /* Active resources have already been printed by node */
2994         out->begin_list(out, NULL, NULL, "Inactive Resources");
2995     } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
2996         out->begin_list(out, NULL, NULL, "Full List of Resources");
2997     } else {
2998         out->begin_list(out, NULL, NULL, "Active Resources");
2999     }
3000 }
3001 
3002 
3003 PCMK__OUTPUT_ARGS("resource-list", "pcmk_scheduler_t *", "uint32_t", "bool",
     /* [previous][next][first][last][top][bottom][index][help] */
3004                   "GList *", "GList *", "bool")
3005 static int
3006 resource_list(pcmk__output_t *out, va_list args)
3007 {
3008     pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *);
3009     uint32_t show_opts = va_arg(args, uint32_t);
3010     bool print_summary = va_arg(args, int);
3011     GList *only_node = va_arg(args, GList *);
3012     GList *only_rsc = va_arg(args, GList *);
3013     bool print_spacer = va_arg(args, int);
3014 
3015     GList *rsc_iter;
3016     int rc = pcmk_rc_no_output;
3017     bool printed_header = false;
3018 
3019     /* If we already showed active resources by node, and
3020      * we're not showing inactive resources, we have nothing to do
3021      */
3022     if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node) &&
3023         !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
3024         return rc;
3025     }
3026 
3027     /* If we haven't already printed resources grouped by node,
3028      * and brief output was requested, print resource summary */
3029     if (pcmk_is_set(show_opts, pcmk_show_brief)
3030         && !pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
3031         GList *rscs = pe__filter_rsc_list(scheduler->resources, only_rsc);
3032 
3033         PCMK__OUTPUT_SPACER_IF(out, print_spacer);
3034         print_resource_header(out, show_opts);
3035         printed_header = true;
3036 
3037         rc = pe__rscs_brief_output(out, rscs, show_opts);
3038         g_list_free(rscs);
3039     }
3040 
3041     /* For each resource, display it if appropriate */
3042     for (rsc_iter = scheduler->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) {
3043         pcmk_resource_t *rsc = (pcmk_resource_t *) rsc_iter->data;
3044         int x;
3045 
3046         /* Complex resources may have some sub-resources active and some inactive */
3047         gboolean is_active = rsc->fns->active(rsc, TRUE);
3048         gboolean partially_active = rsc->fns->active(rsc, FALSE);
3049 
3050         /* Skip inactive orphans (deleted but still in CIB) */
3051         if (pcmk_is_set(rsc->flags, pcmk_rsc_removed) && !is_active) {
3052             continue;
3053 
3054         /* Skip active resources if we already displayed them by node */
3055         } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
3056             if (is_active) {
3057                 continue;
3058             }
3059 
3060         /* Skip primitives already counted in a brief summary */
3061         } else if (pcmk_is_set(show_opts, pcmk_show_brief)
3062                    && pcmk__is_primitive(rsc)) {
3063             continue;
3064 
3065         /* Skip resources that aren't at least partially active,
3066          * unless we're displaying inactive resources
3067          */
3068         } else if (!partially_active && !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
3069             continue;
3070 
3071         } else if (partially_active && !pe__rsc_running_on_any(rsc, only_node)) {
3072             continue;
3073         }
3074 
3075         if (!printed_header) {
3076             PCMK__OUTPUT_SPACER_IF(out, print_spacer);
3077             print_resource_header(out, show_opts);
3078             printed_header = true;
3079         }
3080 
3081         /* Print this resource */
3082         x = out->message(out, pcmk__map_element_name(rsc->xml), show_opts, rsc,
3083                          only_node, only_rsc);
3084         if (x == pcmk_rc_ok) {
3085             rc = pcmk_rc_ok;
3086         }
3087     }
3088 
3089     if (print_summary && rc != pcmk_rc_ok) {
3090         if (!printed_header) {
3091             PCMK__OUTPUT_SPACER_IF(out, print_spacer);
3092             print_resource_header(out, show_opts);
3093             printed_header = true;
3094         }
3095 
3096         if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
3097             out->list_item(out, NULL, "No inactive resources");
3098         } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
3099             out->list_item(out, NULL, "No resources");
3100         } else {
3101             out->list_item(out, NULL, "No active resources");
3102         }
3103     }
3104 
3105     if (printed_header) {
3106         out->end_list(out);
3107     }
3108 
3109     return rc;
3110 }
3111 
3112 PCMK__OUTPUT_ARGS("resource-operation-list", "pcmk_scheduler_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
3113                   "pcmk_resource_t *", "pcmk_node_t *", "GList *", "uint32_t")
3114 static int
3115 resource_operation_list(pcmk__output_t *out, va_list args)
3116 {
3117     pcmk_scheduler_t *scheduler G_GNUC_UNUSED = va_arg(args,
3118                                                        pcmk_scheduler_t *);
3119     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
3120     pcmk_node_t *node = va_arg(args, pcmk_node_t *);
3121     GList *op_list = va_arg(args, GList *);
3122     uint32_t show_opts = va_arg(args, uint32_t);
3123 
3124     GList *gIter = NULL;
3125     int rc = pcmk_rc_no_output;
3126 
3127     /* Print each operation */
3128     for (gIter = op_list; gIter != NULL; gIter = gIter->next) {
3129         xmlNode *xml_op = (xmlNode *) gIter->data;
3130         const char *task = crm_element_value(xml_op, PCMK_XA_OPERATION);
3131         const char *interval_ms_s = crm_element_value(xml_op,
3132                                                       PCMK_META_INTERVAL);
3133         const char *op_rc = crm_element_value(xml_op, PCMK__XA_RC_CODE);
3134         int op_rc_i;
3135 
3136         pcmk__scan_min_int(op_rc, &op_rc_i, 0);
3137 
3138         /* Display 0-interval monitors as "probe" */
3139         if (pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_casei)
3140             && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) {
3141             task = "probe";
3142         }
3143 
3144         /* If this is the first printed operation, print heading for resource */
3145         if (rc == pcmk_rc_no_output) {
3146             time_t last_failure = 0;
3147             int failcount = pe_get_failcount(node, rsc, &last_failure,
3148                                              pcmk__fc_default, NULL);
3149 
3150             out->message(out, "resource-history", rsc, rsc_printable_id(rsc), true,
3151                          failcount, last_failure, true);
3152             rc = pcmk_rc_ok;
3153         }
3154 
3155         /* Print the operation */
3156         out->message(out, "op-history", xml_op, task, interval_ms_s,
3157                      op_rc_i, show_opts);
3158     }
3159 
3160     /* Free the list we created (no need to free the individual items) */
3161     g_list_free(op_list);
3162 
3163     PCMK__OUTPUT_LIST_FOOTER(out, rc);
3164     return rc;
3165 }
3166 
3167 PCMK__OUTPUT_ARGS("resource-util", "pcmk_resource_t *", "pcmk_node_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
3168                   "const char *")
3169 static int
3170 resource_util(pcmk__output_t *out, va_list args)
3171 {
3172     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
3173     pcmk_node_t *node = va_arg(args, pcmk_node_t *);
3174     const char *fn = va_arg(args, const char *);
3175 
3176     char *dump_text = crm_strdup_printf("%s: %s utilization on %s:",
3177                                         fn, rsc->id, pcmk__node_name(node));
3178 
3179     g_hash_table_foreach(rsc->utilization, append_dump_text, &dump_text);
3180     out->list_item(out, NULL, "%s", dump_text);
3181     free(dump_text);
3182 
3183     return pcmk_rc_ok;
3184 }
3185 
3186 PCMK__OUTPUT_ARGS("resource-util", "pcmk_resource_t *", "pcmk_node_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
3187                   "const char *")
3188 static int
3189 resource_util_xml(pcmk__output_t *out, va_list args)
3190 {
3191     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
3192     pcmk_node_t *node = va_arg(args, pcmk_node_t *);
3193     const char *uname = node->details->uname;
3194     const char *fn = va_arg(args, const char *);
3195 
3196     xmlNodePtr xml_node = NULL;
3197 
3198     xml_node = pcmk__output_create_xml_node(out, PCMK_XE_UTILIZATION,
3199                                             PCMK_XA_RESOURCE, rsc->id,
3200                                             PCMK_XA_NODE, uname,
3201                                             PCMK_XA_FUNCTION, fn,
3202                                             NULL);
3203     g_hash_table_foreach(rsc->utilization, add_dump_node, xml_node);
3204 
3205     return pcmk_rc_ok;
3206 }
3207 
3208 PCMK__OUTPUT_ARGS("ticket", "pcmk_ticket_t *", "bool", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
3209 static int
3210 ticket_default(pcmk__output_t *out, va_list args) {
3211     pcmk_ticket_t *ticket = va_arg(args, pcmk_ticket_t *);
3212     bool raw = va_arg(args, int);
3213     bool details = va_arg(args, int);
3214 
3215     GString *detail_str = NULL;
3216 
3217     if (raw) {
3218         out->list_item(out, ticket->id, "%s", ticket->id);
3219         return pcmk_rc_ok;
3220     }
3221 
3222     if (details && g_hash_table_size(ticket->state) > 0) {
3223         GHashTableIter iter;
3224         const char *name = NULL;
3225         const char *value = NULL;
3226         bool already_added = false;
3227 
3228         detail_str = g_string_sized_new(100);
3229         pcmk__g_strcat(detail_str, "\t(", NULL);
3230 
3231         g_hash_table_iter_init(&iter, ticket->state);
3232         while (g_hash_table_iter_next(&iter, (void **) &name, (void **) &value)) {
3233             if (already_added) {
3234                 g_string_append_printf(detail_str, ", %s=", name);
3235             } else {
3236                 g_string_append_printf(detail_str, "%s=", name);
3237                 already_added = true;
3238             }
3239 
3240             if (pcmk__str_any_of(name, PCMK_XA_LAST_GRANTED, "expires", NULL)) {
3241                 char *epoch_str = NULL;
3242                 long long time_ll;
3243 
3244                 (void) pcmk__scan_ll(value, &time_ll, 0);
3245                 epoch_str = pcmk__epoch2str((const time_t *) &time_ll, 0);
3246                 pcmk__g_strcat(detail_str, epoch_str, NULL);
3247                 free(epoch_str);
3248             } else {
3249                 pcmk__g_strcat(detail_str, value, NULL);
3250             }
3251         }
3252 
3253         pcmk__g_strcat(detail_str, ")", NULL);
3254     }
3255 
3256     if (ticket->last_granted > -1) {
3257         /* Prior to the introduction of the details & raw arguments to this
3258          * function, last-granted would always be added in this block.  We need
3259          * to preserve that behavior.  At the same time, we also need to preserve
3260          * the existing behavior from crm_ticket, which would include last-granted
3261          * as part of the (...) detail string.
3262          *
3263          * Luckily we can check detail_str - if it's NULL, either there were no
3264          * details, or we are preserving the previous behavior of this function.
3265          * If it's not NULL, we are either preserving the previous behavior of
3266          * crm_ticket or we were given details=true as an argument.
3267          */
3268         if (detail_str == NULL) {
3269             char *epoch_str = pcmk__epoch2str(&(ticket->last_granted), 0);
3270 
3271             out->list_item(out, NULL, "%s\t%s%s last-granted=\"%s\"",
3272                            ticket->id,
3273                            (ticket->granted? "granted" : "revoked"),
3274                            (ticket->standby? " [standby]" : ""),
3275                            pcmk__s(epoch_str, ""));
3276             free(epoch_str);
3277         } else {
3278             out->list_item(out, NULL, "%s\t%s%s %s",
3279                            ticket->id,
3280                            (ticket->granted? "granted" : "revoked"),
3281                            (ticket->standby? " [standby]" : ""),
3282                            detail_str->str);
3283         }
3284     } else {
3285         out->list_item(out, NULL, "%s\t%s%s%s", ticket->id,
3286                        ticket->granted ? "granted" : "revoked",
3287                        ticket->standby ? " [standby]" : "",
3288                        detail_str != NULL ? detail_str->str : "");
3289     }
3290 
3291     if (detail_str != NULL) {
3292         g_string_free(detail_str, TRUE);
3293     }
3294 
3295     return pcmk_rc_ok;
3296 }
3297 
3298 PCMK__OUTPUT_ARGS("ticket", "pcmk_ticket_t *", "bool", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
3299 static int
3300 ticket_xml(pcmk__output_t *out, va_list args) {
3301     pcmk_ticket_t *ticket = va_arg(args, pcmk_ticket_t *);
3302     bool raw G_GNUC_UNUSED = va_arg(args, int);
3303     bool details G_GNUC_UNUSED = va_arg(args, int);
3304 
3305     const char *status = NULL;
3306     const char *standby = pcmk__btoa(ticket->standby);
3307 
3308     xmlNodePtr node = NULL;
3309     GHashTableIter iter;
3310     const char *name = NULL;
3311     const char *value = NULL;
3312 
3313     status = ticket->granted? PCMK_VALUE_GRANTED : PCMK_VALUE_REVOKED;
3314 
3315     node = pcmk__output_create_xml_node(out, PCMK_XE_TICKET,
3316                                         PCMK_XA_ID, ticket->id,
3317                                         PCMK_XA_STATUS, status,
3318                                         PCMK_XA_STANDBY, standby,
3319                                         NULL);
3320 
3321     if (ticket->last_granted > -1) {
3322         char *buf = pcmk__epoch2str(&ticket->last_granted, 0);
3323 
3324         crm_xml_add(node, PCMK_XA_LAST_GRANTED, buf);
3325         free(buf);
3326     }
3327 
3328     g_hash_table_iter_init(&iter, ticket->state);
3329     while (g_hash_table_iter_next(&iter, (void **) &name, (void **) &value)) {
3330         /* PCMK_XA_LAST_GRANTED and "expires" are already added by the check
3331          * for ticket->last_granted above.
3332          */
3333         if (pcmk__str_any_of(name, PCMK_XA_LAST_GRANTED, PCMK_XA_EXPIRES,
3334                              NULL)) {
3335             continue;
3336         }
3337 
3338         crm_xml_add(node, name, value);
3339     }
3340 
3341     return pcmk_rc_ok;
3342 }
3343 
3344 PCMK__OUTPUT_ARGS("ticket-list", "GHashTable *", "bool", "bool", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
3345 static int
3346 ticket_list(pcmk__output_t *out, va_list args) {
3347     GHashTable *tickets = va_arg(args, GHashTable *);
3348     bool print_spacer = va_arg(args, int);
3349     bool raw = va_arg(args, int);
3350     bool details = va_arg(args, int);
3351 
3352     GHashTableIter iter;
3353     gpointer value;
3354 
3355     if (g_hash_table_size(tickets) == 0) {
3356         return pcmk_rc_no_output;
3357     }
3358 
3359     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
3360 
3361     /* Print section heading */
3362     out->begin_list(out, NULL, NULL, "Tickets");
3363 
3364     /* Print each ticket */
3365     g_hash_table_iter_init(&iter, tickets);
3366     while (g_hash_table_iter_next(&iter, NULL, &value)) {
3367         pcmk_ticket_t *ticket = (pcmk_ticket_t *) value;
3368         out->message(out, "ticket", ticket, raw, details);
3369     }
3370 
3371     /* Close section */
3372     out->end_list(out);
3373     return pcmk_rc_ok;
3374 }
3375 
3376 static pcmk__message_entry_t fmt_functions[] = {
3377     { "ban", "default", ban_text },
3378     { "ban", "html", ban_html },
3379     { "ban", "xml", ban_xml },
3380     { "ban-list", "default", ban_list },
3381     { "bundle", "default", pe__bundle_text },
3382     { "bundle", "xml",  pe__bundle_xml },
3383     { "bundle", "html",  pe__bundle_html },
3384     { "clone", "default", pe__clone_default },
3385     { "clone", "xml",  pe__clone_xml },
3386     { "cluster-counts", "default", cluster_counts_text },
3387     { "cluster-counts", "html", cluster_counts_html },
3388     { "cluster-counts", "xml", cluster_counts_xml },
3389     { "cluster-dc", "default", cluster_dc_text },
3390     { "cluster-dc", "html", cluster_dc_html },
3391     { "cluster-dc", "xml", cluster_dc_xml },
3392     { "cluster-options", "default", cluster_options_text },
3393     { "cluster-options", "html", cluster_options_html },
3394     { "cluster-options", "log", cluster_options_log },
3395     { "cluster-options", "xml", cluster_options_xml },
3396     { "cluster-summary", "default", cluster_summary },
3397     { "cluster-summary", "html", cluster_summary_html },
3398     { "cluster-stack", "default", cluster_stack_text },
3399     { "cluster-stack", "html", cluster_stack_html },
3400     { "cluster-stack", "xml", cluster_stack_xml },
3401     { "cluster-times", "default", cluster_times_text },
3402     { "cluster-times", "html", cluster_times_html },
3403     { "cluster-times", "xml", cluster_times_xml },
3404     { "failed-action", "default", failed_action_default },
3405     { "failed-action", "xml", failed_action_xml },
3406     { "failed-action-list", "default", failed_action_list },
3407     { "group", "default",  pe__group_default},
3408     { "group", "xml",  pe__group_xml },
3409     { "maint-mode", "text", cluster_maint_mode_text },
3410     { "node", "default", node_text },
3411     { "node", "html", node_html },
3412     { "node", "xml", node_xml },
3413     { "node-and-op", "default", node_and_op },
3414     { "node-and-op", "xml", node_and_op_xml },
3415     { "node-capacity", "default", node_capacity },
3416     { "node-capacity", "xml", node_capacity_xml },
3417     { "node-history-list", "default", node_history_list },
3418     { "node-list", "default", node_list_text },
3419     { "node-list", "html", node_list_html },
3420     { "node-list", "xml", node_list_xml },
3421     { "node-weight", "default", node_weight },
3422     { "node-weight", "xml", node_weight_xml },
3423     { "node-attribute", "default", node_attribute_text },
3424     { "node-attribute", "html", node_attribute_html },
3425     { "node-attribute", "xml", node_attribute_xml },
3426     { "node-attribute-list", "default", node_attribute_list },
3427     { "node-summary", "default", node_summary },
3428     { "op-history", "default", op_history_text },
3429     { "op-history", "xml", op_history_xml },
3430     { "primitive", "default",  pe__resource_text },
3431     { "primitive", "xml",  pe__resource_xml },
3432     { "primitive", "html",  pe__resource_html },
3433     { "promotion-score", "default", promotion_score },
3434     { "promotion-score", "xml", promotion_score_xml },
3435     { "resource-config", "default", resource_config },
3436     { "resource-config", "text", resource_config_text },
3437     { "resource-history", "default", resource_history_text },
3438     { "resource-history", "xml", resource_history_xml },
3439     { "resource-list", "default", resource_list },
3440     { "resource-operation-list", "default", resource_operation_list },
3441     { "resource-util", "default", resource_util },
3442     { "resource-util", "xml", resource_util_xml },
3443     { "ticket", "default", ticket_default },
3444     { "ticket", "xml", ticket_xml },
3445     { "ticket-list", "default", ticket_list },
3446 
3447     { NULL, NULL, NULL }
3448 };
3449 
3450 void
3451 pe__register_messages(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
3452     pcmk__register_messages(out, fmt_functions);
3453 }

/* [previous][next][first][last][top][bottom][index][help] */