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_variant_text
  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. ticket_status
  83. ticket_standby_text
  84. PCMK__OUTPUT_ARGS
  85. PCMK__OUTPUT_ARGS
  86. PCMK__OUTPUT_ARGS
  87. pe__register_messages

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

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