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