root/tools/crm_attribute.c

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

DEFINITIONS

This source file includes following definitions.
  1. list_cb
  2. delete_cb
  3. attr_name_cb
  4. promotion_cb
  5. update_cb
  6. utilization_cb
  7. value_cb
  8. wait_cb
  9. get_node_name_from_local
  10. send_attrd_update
  11. delete_attr_on_node
  12. command_list
  13. command_delete
  14. update_attr_on_node
  15. command_update
  16. output_one_attribute
  17. command_query
  18. set_type
  19. use_attrd
  20. try_ipc_update
  21. pattern_used_correctly
  22. delete_used_correctly
  23. update_used_correctly
  24. build_arg_context
  25. main

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

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