1    	/*
2    	 * Copyright 2004-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 General Public License version 2
7    	 * or later (GPLv2+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdbool.h>
13   	#include <stdint.h>
14   	#include <stdio.h>
15   	#include <unistd.h>
16   	#include <stdlib.h>
17   	#include <errno.h>
18   	#include <fcntl.h>
19   	#include <libgen.h>
20   	#include <time.h>
21   	
22   	#include <sys/param.h>
23   	#include <sys/types.h>
24   	
25   	#include <crm/crm.h>
26   	#include <crm/common/xml.h>
27   	#include <crm/common/ipc.h>
28   	#include <crm/common/util.h>
29   	#include <crm/cluster.h>
30   	
31   	#include <crm/cib.h>
32   	#include <crm/cib/internal.h>
33   	#include <crm/common/ipc_controld.h>
34   	#include <sys/utsname.h>
35   	
36   	#include <pacemaker-internal.h>
37   	
38   	#define SUMMARY "crm_attribute - query and update Pacemaker cluster options and node attributes"
39   	
40   	enum attr_cmd {
41   	    attr_cmd_none,
42   	    attr_cmd_delete,
43   	    attr_cmd_list,
44   	    attr_cmd_query,
45   	    attr_cmd_update,
46   	};
47   	
48   	GError *error = NULL;
49   	crm_exit_t exit_code = CRM_EX_OK;
50   	uint64_t cib_opts = cib_sync_call;
51   	
52   	static pcmk__supported_format_t formats[] = {
53   	    PCMK__SUPPORTED_FORMAT_NONE,
54   	    PCMK__SUPPORTED_FORMAT_TEXT,
55   	    PCMK__SUPPORTED_FORMAT_XML,
56   	    { NULL, NULL, NULL }
57   	};
58   	
59   	struct {
60   	    enum attr_cmd command;
61   	    gchar *attr_default;
62   	    gchar *attr_id;
63   	    gchar *attr_name;
64   	    uint32_t attr_options;
65   	    gchar *attr_pattern;
66   	    char *attr_value;
67   	    char *dest_node;
68   	    gchar *dest_uname;
69   	    gchar *set_name;
70   	    char *set_type;
71   	    gchar *type;
72   	    char *opt_list;
73   	    gboolean all;
74   	    bool promotion_score;
75   	    gboolean score_update;
76   	} options = {
77   	    .command = attr_cmd_query,
78   	};
79   	
80   	#define INDENT "                               "
81   	
82   	static gboolean
83   	list_cb(const gchar *option_name, const gchar *optarg, gpointer data,
84   	        GError **error) {
85   	    options.command = attr_cmd_list;
86   	    pcmk__str_update(&options.opt_list, optarg);
87   	    return TRUE;
88   	}
89   	
90   	static gboolean
91   	delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
92   	    options.command = attr_cmd_delete;
93   	    g_clear_pointer(&options.attr_value, free);
94   	    return TRUE;
95   	}
96   	
97   	static gboolean
98   	attr_name_cb(const gchar *option_name, const gchar *optarg, gpointer data,
99   	             GError **error)
100  	{
101  	    options.promotion_score = false;
102  	
103  	    if (options.attr_name != NULL) {
104  	        g_free(options.attr_name);
105  	    }
106  	    options.attr_name = g_strdup(optarg);
107  	    return TRUE;
108  	}
109  	
110  	static gboolean
111  	promotion_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
112  	    char *score_name = NULL;
113  	
114  	    options.promotion_score = true;
115  	
116  	    if (options.attr_name) {
117  	        g_free(options.attr_name);
118  	    }
119  	
120  	    score_name = pcmk_promotion_score_name(optarg);
121  	    if (score_name != NULL) {
122  	        options.attr_name = g_strdup(score_name);
123  	        free(score_name);
124  	    } else {
125  	        options.attr_name = NULL;
126  	    }
127  	
128  	    return TRUE;
129  	}
130  	
131  	static gboolean
132  	update_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
133  	    options.command = attr_cmd_update;
134  	    pcmk__str_update(&options.attr_value, optarg);
135  	    return TRUE;
136  	}
137  	
138  	static gboolean
139  	utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
140  	    if (options.type) {
141  	        g_free(options.type);
142  	    }
143  	
144  	    options.type = g_strdup(PCMK_XE_NODES);
145  	    pcmk__str_update(&options.set_type, PCMK_XE_UTILIZATION);
146  	    return TRUE;
147  	}
148  	
149  	static gboolean
150  	value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
151  	    options.command = attr_cmd_query;
CID (unavailable; MK=8dd03ede8931ff524ac30f5ced4c7218) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(1) Event assign_union_field: The union field "in" of "_pp" is written.
(2) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
152  	    g_clear_pointer(&options.attr_value, free);
153  	    return TRUE;
154  	}
155  	
156  	static gboolean
157  	wait_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
158  	    if (pcmk__str_eq(optarg, "no", pcmk__str_none)) {
159  	        pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
160  	        return TRUE;
161  	    } else if (pcmk__str_eq(optarg, PCMK__VALUE_LOCAL, pcmk__str_none)) {
162  	        pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
163  	        pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local);
164  	        return TRUE;
165  	    } else if (pcmk__str_eq(optarg, PCMK__VALUE_CLUSTER, pcmk__str_none)) {
166  	        pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster);
167  	        pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_cluster);
168  	        return TRUE;
169  	    } else {
170  	        g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE,
171  	                    "--wait= must be one of 'no', 'local', 'cluster'");
172  	        return FALSE;
173  	    }
174  	}
175  	
176  	static GOptionEntry selecting_entries[] = {
177  	    { "all", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.all,
178  	      "With -L/--list-options, include advanced and deprecated options in the\n"
179  	      INDENT "output. This is always treated as true when --output-as=xml is\n"
180  	      INDENT "specified.",
181  	      NULL,
182  	    },
183  	
184  	    { "id", 'i', 0, G_OPTION_ARG_STRING, &options.attr_id,
185  	      "(Advanced) Operate on instance of specified attribute with this\n"
186  	      INDENT "XML ID",
187  	      "XML_ID"
188  	    },
189  	
190  	    { "name", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, attr_name_cb,
191  	      "Operate on attribute or option with this name.  For queries, this\n"
192  	      INDENT "is optional, in which case all matching attributes will be\n"
193  	      INDENT "returned.",
194  	      "NAME"
195  	    },
196  	
197  	    { "pattern", 'P', 0, G_OPTION_ARG_STRING, &options.attr_pattern,
198  	      "Operate on all attributes matching this pattern\n"
199  	      INDENT "(with -v, -D, or -G)",
200  	      "PATTERN"
201  	    },
202  	
203  	    { "promotion", 'p', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, promotion_cb,
204  	      "Operate on node attribute used as promotion score for specified\n"
205  	      INDENT "resource, or resource given in OCF_RESOURCE_INSTANCE environment\n"
206  	      INDENT "variable if none is specified; this also defaults -l/--lifetime\n"
207  	      INDENT "to reboot (normally invoked from an OCF resource agent)",
208  	      "RESOURCE"
209  	    },
210  	
211  	    { "set-name", 's', 0, G_OPTION_ARG_STRING, &options.set_name,
212  	      "(Advanced) Operate on instance of specified attribute that is\n"
213  	      INDENT "within set with this XML ID",
214  	      "NAME"
215  	    },
216  	
217  	    { NULL }
218  	};
219  	
220  	static GOptionEntry command_entries[] = {
221  	    { "list-options", 'L', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, list_cb,
222  	      "List all available options of the given type.\n"
223  	      INDENT "Allowed values: " PCMK__VALUE_CLUSTER,
224  	      "TYPE"
225  	    },
226  	
227  	    { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb,
228  	      "Delete the attribute/option (with -n or -P)",
229  	      NULL
230  	    },
231  	
232  	    { "query", 'G', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb,
233  	      "Query the current value of the attribute/option.\n"
234  	      INDENT "See also: -n, -P",
235  	      NULL
236  	    },
237  	
238  	    { "update", 'v', 0, G_OPTION_ARG_CALLBACK, update_cb,
239  	      "Update the value of the attribute/option (with -n or -P)",
240  	      "VALUE"
241  	    },
242  	
243  	    { NULL }
244  	};
245  	
246  	static GOptionEntry addl_entries[] = {
247  	    { "default", 'd', 0, G_OPTION_ARG_STRING, &options.attr_default,
248  	      "(Advanced) Default value to display if none is found in configuration",
249  	      "VALUE"
250  	    },
251  	
252  	    { "lifetime", 'l', 0, G_OPTION_ARG_STRING, &options.type,
253  	      "Lifetime of the node attribute.\n"
254  	      INDENT "Valid values: reboot, forever",
255  	      "LIFETIME"
256  	    },
257  	
258  	    { "node", 'N', 0, G_OPTION_ARG_STRING, &options.dest_uname,
259  	      "Set a node attribute for named node (instead of a cluster option).\n"
260  	      INDENT "See also: -l",
261  	      "NODE"
262  	    },
263  	
264  	    { "type", 't', 0, G_OPTION_ARG_STRING, &options.type,
265  	      "Which part of the configuration to update/delete/query the option in.\n"
266  	      INDENT "Valid values: crm_config, rsc_defaults, op_defaults, tickets",
267  	      "SECTION"
268  	    },
269  	
270  	    { "score", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.score_update,
271  	      "Treat new attribute values as atomic score updates where possible\n"
272  	      INDENT "(with --update/-v, when running against a CIB file or updating\n"
273  	      INDENT "an attribute outside the " PCMK_XE_STATUS " section; enabled\n"
274  	      INDENT "by default if --promotion/-p is specified)\n\n"
275  	
276  	      INDENT "This currently happens by default and cannot be disabled, but\n"
277  	      INDENT "this default behavior is deprecated and will be removed in a\n"
278  	      INDENT "future release (exception: this will remain the default with\n"
279  	      INDENT "--promotion/-p). Set this flag if this behavior is desired.\n\n"
280  	
281  	      INDENT "This option takes effect when updating XML attributes. For an\n"
282  	      INDENT "attribute named \"name\", if the new value is \"name++\" or\n"
283  	      INDENT "\"name+=X\" for some score X, the new value is set as follows:\n"
284  	      INDENT " * If attribute \"name\" is not already set to some value in\n"
285  	      INDENT "   the element being updated, the new value is set as a literal\n"
286  	      INDENT "   string.\n"
287  	      INDENT " * If the new value is \"name++\", then the attribute is set to\n"
288  	      INDENT "   its existing value (parsed as a score) plus 1.\n"
289  	      INDENT " * If the new value is \"name+=X\" for some score X, then the\n"
290  	      INDENT "   attribute is set to its existing value plus X, where the\n"
291  	      INDENT "   existing value and X are parsed and added as scores.\n\n"
292  	
293  	      INDENT "Scores are integer values capped at INFINITY and -INFINITY.\n"
294  	      INDENT "Refer to Pacemaker Explained for more details on scores,\n"
295  	      INDENT "including how they are parsed and added.",
296  	      NULL },
297  	
298  	    { "wait", 'W', 0, G_OPTION_ARG_CALLBACK, wait_cb,
299  	      "Wait for some event to occur before returning.  Values are 'no' (wait\n"
300  	      INDENT "only for the attribute daemon to acknowledge the request),\n"
301  	      INDENT "'local' (wait until the change has propagated to where a local\n"
302  	      INDENT "query will return the request value, or the value set by a\n"
303  	      INDENT "later request), or 'cluster' (wait until the change has propagated\n"
304  	      INDENT "to where a query anywhere on the cluster will return the requested\n"
305  	      INDENT "value, or the value set by a later request).  Default is 'no'.\n"
306  	      INDENT "(with -N, and one of -D or -u)",
307  	      "UNTIL" },
308  	
309  	    { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb,
310  	      "Set an utilization attribute for the node.",
311  	      NULL
312  	    },
313  	
314  	    { NULL }
315  	};
316  	
317  	static GOptionEntry deprecated_entries[] = {
318  	    // NOTE: resource-agents <4.2.0 (2018-10-24) uses this option
319  	    { "attr-name", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, attr_name_cb,
320  	      NULL, NULL
321  	    },
322  	
323  	    // NOTE: resource-agents <4.2.0 (2018-10-24) uses this option
324  	    { "get-value", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb,
325  	      NULL, NULL
326  	    },
327  	
328  	    // NOTE: resource-agents <4.2.0 (2018-10-24) uses this option
329  	    { "node-uname", 'U', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.dest_uname,
330  	      NULL, NULL
331  	    },
332  	
333  	    { NULL }
334  	};
335  	
336  	static void
337  	get_node_name_from_local(void)
338  	{
339  	    struct utsname hostinfo;
340  	
341  	    g_free(options.dest_uname);
342  	
343  	    if (uname(&hostinfo) == 0) {
344  	        options.dest_uname = g_strdup(hostinfo.nodename);
345  	    } else {
346  	        options.dest_uname = NULL;
347  	    }
348  	}
349  	
350  	static int
351  	send_attrd_update(enum attr_cmd command, const char *attr_node,
352  	                  const char *attr_name, const char *attr_value,
353  	                  const char *attr_set, const char *attr_dampen,
354  	                  uint32_t attr_options)
355  	{
356  	    int rc = pcmk_rc_ok;
357  	    uint32_t opts = attr_options;
358  	
359  	    switch (command) {
360  	        case attr_cmd_delete:
361  	            rc = pcmk__attrd_api_delete(NULL, attr_node, attr_name, opts);
362  	            break;
363  	
364  	        case attr_cmd_update:
365  	            rc = pcmk__attrd_api_update(NULL, attr_node, attr_name,
366  	                                        attr_value, NULL, attr_set, NULL,
367  	                                        opts | pcmk__node_attr_value);
368  	            break;
369  	
370  	        default:
371  	            break;
372  	    }
373  	
374  	    if (rc != pcmk_rc_ok) {
375  	        g_set_error(&error, PCMK__RC_ERROR, rc, "Could not update %s=%s: %s (%d)",
376  	                    attr_name, attr_value, pcmk_rc_str(rc), rc);
377  	    }
378  	
379  	    return rc;
380  	}
381  	
382  	struct delete_data_s {
383  	    pcmk__output_t *out;
384  	    cib_t *cib;
385  	};
386  	
387  	static int
388  	delete_attr_on_node(xmlNode *child, void *userdata)
389  	{
390  	    struct delete_data_s *dd = (struct delete_data_s *) userdata;
391  	
392  	    const char *attr_name = pcmk__xe_get(child, PCMK_XA_NAME);
393  	    int rc = pcmk_rc_ok;
394  	
395  	    if (!pcmk__str_eq(attr_name, options.attr_pattern, pcmk__str_regex)) {
396  	        return pcmk_rc_ok;
397  	    }
398  	
399  	    rc = cib__delete_node_attr(dd->out, dd->cib, cib_opts, options.type,
400  	                               options.dest_node, options.set_type,
401  	                               options.set_name, options.attr_id,
402  	                               attr_name, options.attr_value, NULL);
403  	
404  	    if (rc == ENXIO) {
405  	        rc = pcmk_rc_ok;
406  	    }
407  	
408  	    return rc;
409  	}
410  	
411  	static void
412  	command_list(pcmk__output_t *out)
413  	{
414  	    if (pcmk__str_eq(options.opt_list, PCMK__VALUE_CLUSTER, pcmk__str_none)) {
415  	        exit_code = pcmk_rc2exitc(pcmk__list_cluster_options(out, options.all));
416  	
417  	    } else {
418  	        // @TODO Improve usage messages to reduce duplication
419  	        exit_code = CRM_EX_USAGE;
420  	        g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
421  	                    "Invalid --list-options value '%s'. Allowed values: "
422  	                    PCMK__VALUE_CLUSTER,
423  	                    pcmk__s(options.opt_list, "(BUG: none)"));
424  	    }
425  	}
426  	
427  	static int
428  	command_delete(pcmk__output_t *out, cib_t *cib)
429  	{
430  	    int rc = pcmk_rc_ok;
431  	
432  	    xmlNode *result = NULL;
433  	    bool use_pattern = options.attr_pattern != NULL;
434  	
435  	    /* See the comment in command_query regarding xpath and regular expressions. */
436  	    if (use_pattern) {
437  	        struct delete_data_s dd = { out, cib };
438  	
439  	        rc = cib__get_node_attrs(out, cib, options.type, options.dest_node,
440  	                                 options.set_type, options.set_name, NULL, NULL,
441  	                                 NULL, &result);
442  	
443  	        if (rc != pcmk_rc_ok) {
444  	            goto done_deleting;
445  	        }
446  	
447  	        rc = pcmk__xe_foreach_child(result, NULL, delete_attr_on_node, &dd);
448  	
449  	    } else {
450  	        rc = cib__delete_node_attr(out, cib, cib_opts, options.type, options.dest_node,
451  	                                   options.set_type, options.set_name, options.attr_id,
452  	                                   options.attr_name, options.attr_value, NULL);
453  	    }
454  	
455  	done_deleting:
456  	    pcmk__xml_free(result);
457  	
458  	    if (rc == ENXIO) {
459  	        /* Nothing to delete...
460  	         * which means it's not there...
461  	         * which is what the admin wanted
462  	         */
463  	        rc = pcmk_rc_ok;
464  	    }
465  	
466  	    return rc;
467  	}
468  	
469  	struct update_data_s {
470  	    pcmk__output_t *out;
471  	    cib_t *cib;
472  	    int is_remote_node;
473  	};
474  	
475  	static int
476  	update_attr_on_node(xmlNode *child, void *userdata)
477  	{
478  	    struct update_data_s *ud = (struct update_data_s *) userdata;
479  	
480  	    const char *attr_name = pcmk__xe_get(child, PCMK_XA_NAME);
481  	
482  	    if (!pcmk__str_eq(attr_name, options.attr_pattern, pcmk__str_regex)) {
483  	        return pcmk_rc_ok;
484  	    }
485  	
486  	    return cib__update_node_attr(ud->out, ud->cib, cib_opts, options.type,
487  	                                 options.dest_node, options.set_type,
488  	                                 options.set_name, options.attr_id,
489  	                                 attr_name, options.attr_value, NULL,
490  	                                 ud->is_remote_node? PCMK_VALUE_REMOTE : NULL);
491  	}
492  	
493  	static int
494  	command_update(pcmk__output_t *out, cib_t *cib, int is_remote_node)
495  	{
496  	    int rc = pcmk_rc_ok;
497  	
498  	    xmlNode *result = NULL;
499  	    bool use_pattern = options.attr_pattern != NULL;
500  	
501  	    /* @COMPAT When we drop default support for expansion in crm_attribute,
502  	     * guard with `if (options.score_update)`
503  	     */
504  	    cib__set_call_options(cib_opts, crm_system_name, cib_score_update);
505  	
506  	    /* See the comment in command_query regarding xpath and regular expressions. */
507  	    if (use_pattern) {
508  	        struct update_data_s ud = { out, cib, is_remote_node };
509  	
510  	        rc = cib__get_node_attrs(out, cib, options.type, options.dest_node,
511  	                                 options.set_type, options.set_name, NULL, NULL,
512  	                                 NULL, &result);
513  	
514  	        if (rc != pcmk_rc_ok) {
515  	            goto done_updating;
516  	        }
517  	
518  	        rc = pcmk__xe_foreach_child(result, NULL, update_attr_on_node, &ud);
519  	
520  	    } else {
521  	        rc = cib__update_node_attr(out, cib, cib_opts, options.type,
522  	                                   options.dest_node, options.set_type,
523  	                                   options.set_name, options.attr_id,
524  	                                   options.attr_name, options.attr_value, NULL,
525  	                                   is_remote_node? PCMK_VALUE_REMOTE : NULL);
526  	    }
527  	
528  	done_updating:
529  	    pcmk__xml_free(result);
530  	    return rc;
531  	}
532  	
533  	struct output_data_s {
534  	    pcmk__output_t *out;
535  	    bool use_pattern;
536  	    bool did_output;
537  	};
538  	
539  	static int
540  	output_one_attribute(xmlNode *node, void *userdata)
541  	{
542  	    struct output_data_s *od = (struct output_data_s *) userdata;
543  	
544  	    const char *name = pcmk__xe_get(node, PCMK_XA_NAME);
545  	    const char *value = pcmk__xe_get(node, PCMK_XA_VALUE);
546  	
547  	    const char *type = options.type;
548  	    const char *attr_id = options.attr_id;
549  	
550  	    if (od->use_pattern && !pcmk__str_eq(name, options.attr_pattern, pcmk__str_regex)) {
551  	        return pcmk_rc_ok;
552  	    }
553  	
554  	    od->out->message(od->out, "attribute", type, attr_id, name, value, NULL,
555  	                     od->out->quiet, true);
556  	    od->did_output = true;
557  	    pcmk__info("Read %s='%s' %s%s", pcmk__s(name, "<null>"), pcmk__s(value, ""),
558  	               ((options.set_name != NULL)? "in " : ""),
559  	               pcmk__s(options.set_name, ""));
560  	
561  	    return pcmk_rc_ok;
562  	}
563  	
564  	static int
565  	command_query(pcmk__output_t *out, cib_t *cib)
566  	{
567  	    int rc = pcmk_rc_ok;
568  	
569  	    xmlNode *result = NULL;
570  	    bool use_pattern = options.attr_pattern != NULL;
571  	
572  	    /* libxml2 doesn't support regular expressions in xpath queries (which is how
573  	     * cib__get_node_attrs -> find_attr finds attributes).  So instead, we'll just
574  	     * find all the attributes for a given node here by passing NULL for attr_id
575  	     * and attr_name, and then later see if they match the given pattern.
576  	     */
577  	    if (use_pattern) {
578  	        rc = cib__get_node_attrs(out, cib, options.type, options.dest_node,
579  	                                 options.set_type, options.set_name, NULL,
580  	                                 NULL, NULL, &result);
581  	    } else {
582  	        rc = cib__get_node_attrs(out, cib, options.type, options.dest_node,
583  	                                 options.set_type, options.set_name, options.attr_id,
584  	                                 options.attr_name, NULL, &result);
585  	    }
586  	
587  	    if (rc == ENXIO && options.attr_default) {
588  	        /* Make static analysis happy */
589  	        const char *type = options.type;
590  	        const char *attr_id = options.attr_id;
591  	        const char *attr_name = options.attr_name;
592  	        const char *attr_default = options.attr_default;
593  	
594  	        out->message(out, "attribute", type, attr_id, attr_name, attr_default,
595  	                     NULL, out->quiet, true);
596  	        rc = pcmk_rc_ok;
597  	
598  	    } else if (rc != pcmk_rc_ok) {
599  	        // Don't do anything.
600  	
601  	    } else if (result->children != NULL) {
602  	        struct output_data_s od = { out, use_pattern, false };
603  	
604  	        pcmk__xe_foreach_child(result, NULL, output_one_attribute, &od);
605  	
606  	        if (!od.did_output) {
607  	            rc = ENXIO;
608  	        }
609  	
610  	    } else {
611  	        struct output_data_s od = { out, use_pattern, false };
612  	        output_one_attribute(result, &od);
613  	    }
614  	
615  	    pcmk__xml_free(result);
616  	    return rc;
617  	}
618  	
619  	static void
620  	set_type(void)
621  	{
622  	    if (options.type == NULL) {
623  	        if (options.promotion_score) {
624  	            // Updating a promotion score node attribute
625  	            options.type = g_strdup(PCMK_XE_STATUS);
626  	
627  	        } else if (options.dest_uname != NULL) {
628  	            // Updating some other node attribute
629  	            options.type = g_strdup(PCMK_XE_NODES);
630  	
631  	        } else {
632  	            // Updating cluster options
633  	            options.type = g_strdup(PCMK_XE_CRM_CONFIG);
634  	        }
635  	
636  	    } else if (pcmk__str_eq(options.type, PCMK_VALUE_REBOOT, pcmk__str_casei)) {
637  	        options.type = g_strdup(PCMK_XE_STATUS);
638  	
639  	    } else if (pcmk__str_eq(options.type, "forever", pcmk__str_casei)) {
640  	        options.type = g_strdup(PCMK_XE_NODES);
641  	    }
642  	}
643  	
644  	static bool
645  	use_attrd(void)
646  	{
647  	    /* Only go through the attribute manager for transient attributes, and
648  	     * then only if we're not using a file as the CIB.
649  	     */
650  	    return pcmk__str_eq(options.type, PCMK_XE_STATUS, pcmk__str_casei) &&
651  	           getenv("CIB_file") == NULL && getenv("CIB_shadow") == NULL;
652  	}
653  	
654  	static bool
655  	try_ipc_update(void)
656  	{
657  	    return use_attrd()
658  	           && ((options.command == attr_cmd_delete)
659  	               || (options.command == attr_cmd_update));
660  	}
661  	
662  	static bool
663  	pattern_used_correctly(void)
664  	{
665  	    /* --pattern can only be used with:
666  	     * -G (query), -v (update), or -D (delete)
667  	     */
668  	    switch (options.command) {
669  	        case attr_cmd_delete:
670  	        case attr_cmd_query:
671  	        case attr_cmd_update:
672  	            return true;
673  	        default:
674  	            return false;
675  	    }
676  	}
677  	
678  	static bool
679  	delete_used_correctly(void)
680  	{
681  	    return (options.command != attr_cmd_delete)
682  	           || (options.attr_name != NULL)
683  	           || (options.attr_pattern != NULL);
684  	}
685  	
686  	static bool
687  	update_used_correctly(void)
688  	{
689  	    return (options.command != attr_cmd_update)
690  	           || (options.attr_name != NULL)
691  	           || (options.attr_pattern != NULL);
692  	}
693  	
694  	static GOptionContext *
695  	build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
696  	    GOptionContext *context = NULL;
697  	
698  	    GOptionEntry extra_prog_entries[] = {
699  	        { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet),
700  	          "Print only the value on stdout",
701  	          NULL },
702  	
703  	        // NOTE: resource-agents <4.2.0 (2018-10-24) uses -Q
704  	        { "quiet", 'Q', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &(args->quiet),
705  	          NULL, NULL
706  	        },
707  	
708  	        { NULL }
709  	    };
710  	
711  	    const char *description = "Examples:\n\n"
712  	                              "Add new node attribute called 'location' with the value of 'office' for host 'myhost':\n\n"
713  	                              "\tcrm_attribute --node myhost --name location --update office\n\n"
714  	                              "Query the value of the 'location' node attribute for host 'myhost':\n\n"
715  	                              "\tcrm_attribute --node myhost --name location --query\n\n"
716  	                              "Change the value of the 'location' node attribute for host 'myhost':\n\n"
717  	                              "\tcrm_attribute --node myhost --name location --update backoffice\n\n"
718  	                              "Delete the 'location' node attribute for host 'myhost':\n\n"
719  	                              "\tcrm_attribute --node myhost --name location --delete\n\n"
720  	                              "Query the value of the '" PCMK_OPT_CLUSTER_DELAY
721  	                                "' cluster option:\n\n"
722  	                              "\tcrm_attribute --type crm_config --name "
723  	                                PCMK_OPT_CLUSTER_DELAY " --query\n\n"
724  	                              "Query value of the '" PCMK_OPT_CLUSTER_DELAY
725  	                                "' cluster option and print only the value:\n\n"
726  	                              "\tcrm_attribute --type crm_config --name "
727  	                                PCMK_OPT_CLUSTER_DELAY " --query --quiet\n\n";
728  	
729  	    context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
730  	    pcmk__add_main_args(context, extra_prog_entries);
731  	    g_option_context_set_description(context, description);
732  	
733  	    pcmk__add_arg_group(context, "selections", "Selecting attributes:",
734  	                        "Show selecting options", selecting_entries);
735  	    pcmk__add_arg_group(context, "command", "Commands:",
736  	                        "Show command options", command_entries);
737  	    pcmk__add_arg_group(context, "additional", "Additional options:",
738  	                        "Show additional options", addl_entries);
739  	    pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
740  	                        "Show deprecated options", deprecated_entries);
741  	
742  	    return context;
743  	}
744  	
745  	int
746  	main(int argc, char **argv)
747  	{
748  	    cib_t *the_cib = NULL;
749  	    int is_remote_node = 0;
750  	
751  	    int rc = pcmk_rc_ok;
752  	
753  	    pcmk__output_t *out = NULL;
754  	
755  	    GOptionGroup *output_group = NULL;
756  	    pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
757  	    gchar **processed_args = pcmk__cmdline_preproc(argv, "NPUdilnpstv");
758  	    GOptionContext *context = build_arg_context(args, &output_group);
759  	
760  	    pcmk__register_formats(output_group, formats);
761  	    if (!g_option_context_parse_strv(context, &processed_args, &error)) {
762  	        exit_code = CRM_EX_USAGE;
763  	        goto done;
764  	    }
765  	
766  	    pcmk__cli_init_logging("crm_attribute", args->verbosity);
767  	
768  	    rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
769  	    if (rc != pcmk_rc_ok) {
770  	        exit_code = CRM_EX_ERROR;
771  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
772  	                    args->output_ty, pcmk_rc_str(rc));
773  	        goto done;
774  	    }
775  	
776  	    pcmk__register_lib_messages(out);
777  	
778  	    if (args->version) {
779  	        out->version(out);
780  	        goto done;
781  	    }
782  	
783  	    out->quiet = args->quiet;
784  	
785  	    if (options.command == attr_cmd_list) {
786  	        command_list(out);
787  	        goto done;
788  	    }
789  	
790  	    if (options.promotion_score && options.attr_name == NULL) {
791  	        exit_code = CRM_EX_USAGE;
792  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
793  	                    "-p/--promotion must be called from an OCF resource agent "
794  	                    "or with a resource ID specified");
795  	        goto done;
796  	    }
797  	
798  	    rc = cib__create_signon(&the_cib);
799  	    if (rc != pcmk_rc_ok) {
800  	        exit_code = pcmk_rc2exitc(rc);
801  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
802  	                    "Could not connect to the CIB: %s", pcmk_rc_str(rc));
803  	        goto done;
804  	    }
805  	
806  	    set_type();
807  	
808  	    // Use default node if not given (except for cluster options and tickets)
809  	    if (!pcmk__strcase_any_of(options.type,
810  	                              PCMK_XE_CRM_CONFIG, PCMK_XE_TICKETS, NULL)) {
811  	        /* If we are being called from a resource agent via the cluster,
812  	         * the correct local node name will be passed as an environment
813  	         * variable. Otherwise, we have to ask the cluster.
814  	         */
815  	        const char *target = pcmk__node_attr_target(options.dest_uname);
816  	
817  	        if (target != NULL) {
818  	            /* If options.dest_uname is "auto" or "localhost", then
819  	             * pcmk__node_attr_target() may return it, depending on environment
820  	             * variables. In that case, attribute lookups will fail for "auto"
821  	             * (unless there's a node named "auto"). attrd maps "localhost" to
822  	             * the true local node name for queries.
823  	             *
824  	             * @TODO
825  	             * * Investigate whether "localhost" is mapped to a real node name
826  	             *   for non-query commands. If not, possibly modify it so that it
827  	             *   is.
828  	             * * Map "auto" to "localhost" (probably).
829  	             */
830  	            if (target != (const char *) options.dest_uname) {
831  	                g_free(options.dest_uname);
832  	                options.dest_uname = g_strdup(target);
833  	            }
834  	
835  	        } else if (getenv("CIB_file") != NULL && options.dest_uname == NULL) {
836  	            get_node_name_from_local();
837  	        }
838  	
839  	        if (options.dest_uname == NULL) {
840  	            char *node_name = NULL;
841  	
842  	            rc = pcmk__query_node_name(out, 0, &node_name, 0);
843  	
844  	            if (rc != pcmk_rc_ok) {
845  	                exit_code = pcmk_rc2exitc(rc);
846  	                free(node_name);
847  	                goto done;
848  	            }
849  	            options.dest_uname = g_strdup(node_name);
850  	            free(node_name);
851  	        }
852  	
853  	        rc = query_node_uuid(the_cib, options.dest_uname, &options.dest_node, &is_remote_node);
854  	        rc = pcmk_legacy2rc(rc);
855  	
856  	        if (rc != pcmk_rc_ok) {
857  	            exit_code = pcmk_rc2exitc(rc);
858  	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
859  	                        "Could not map name=%s to a UUID", options.dest_uname);
860  	            goto done;
861  	        }
862  	    }
863  	
864  	    if (!delete_used_correctly()) {
865  	        exit_code = CRM_EX_USAGE;
866  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
867  	                    "Error: must specify attribute name or pattern to delete");
868  	        goto done;
869  	    }
870  	
871  	    if (!update_used_correctly()) {
872  	        exit_code = CRM_EX_USAGE;
873  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
874  	                    "Error: must specify attribute name or pattern to update");
875  	        goto done;
876  	    }
877  	
878  	    if (options.attr_pattern) {
879  	        if (options.attr_name) {
880  	            exit_code = CRM_EX_USAGE;
881  	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
882  	                        "Error: --name and --pattern cannot be used at the same time");
883  	            goto done;
884  	        }
885  	
886  	        if (!pattern_used_correctly()) {
887  	            exit_code = CRM_EX_USAGE;
888  	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
889  	                        "Error: pattern can only be used with delete, query, or update");
890  	            goto done;
891  	        }
892  	
893  	        g_free(options.attr_name);
894  	        options.attr_name = options.attr_pattern;
895  	        options.attr_options |= pcmk__node_attr_pattern;
896  	    }
897  	
898  	    if (is_remote_node) {
899  	        options.attr_options |= pcmk__node_attr_remote;
900  	    }
901  	
902  	    if (pcmk__str_eq(options.set_type, PCMK_XE_UTILIZATION, pcmk__str_none)) {
903  	        options.attr_options |= pcmk__node_attr_utilization;
904  	    }
905  	
906  	    if (try_ipc_update() &&
907  	        (send_attrd_update(options.command, options.dest_uname, options.attr_name,
908  	                           options.attr_value, options.set_name, NULL, options.attr_options) == pcmk_rc_ok)) {
909  	
910  	        const char *update = options.attr_value;
911  	
912  	        if (options.command == attr_cmd_delete) {
913  	            update = "<none>";
914  	        }
915  	        pcmk__info("Update %s=%s sent to the attribute manager",
916  	                   options.attr_name, update);
917  	
918  	    } else if (options.command == attr_cmd_delete) {
919  	        rc = command_delete(out, the_cib);
920  	
921  	    } else if (options.command == attr_cmd_update) {
922  	        rc = command_update(out, the_cib, is_remote_node);
923  	
924  	    } else {
925  	        rc = command_query(out, the_cib);
926  	    }
927  	
928  	    if (rc == ENOTUNIQ) {
929  	        exit_code = pcmk_rc2exitc(rc);
930  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
931  	                    "Please choose from one of the matches below and supply the 'id' with --attr-id");
932  	
933  	    } else if (rc != pcmk_rc_ok) {
934  	        exit_code = pcmk_rc2exitc(rc);
935  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
936  	                    "Error performing operation: %s", pcmk_rc_str(rc));
937  	    }
938  	
939  	done:
940  	    g_strfreev(processed_args);
941  	    pcmk__free_arg_context(context);
942  	
943  	    free(options.attr_default);
944  	    g_free(options.attr_id);
945  	    g_free(options.attr_name);
946  	    free(options.attr_value);
947  	    free(options.dest_node);
948  	    g_free(options.dest_uname);
949  	    g_free(options.set_name);
950  	    free(options.set_type);
951  	    g_free(options.type);
952  	
953  	    cib__clean_up_connection(&the_cib);
954  	
955  	    pcmk__output_and_clear_error(&error, out);
956  	
957  	    if (out != NULL) {
958  	        out->finish(out, exit_code, true, NULL);
959  	        pcmk__output_free(out);
960  	    }
961  	
962  	    pcmk__unregister_formats();
963  	    return crm_exit(exit_code);
964  	}
965