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