root/tools/cibadmin.c

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

DEFINITIONS

This source file includes following definitions.
  1. print_xml_output
  2. report_schema_unchanged
  3. cib_action_is_dangerous
  4. scope_is_valid
  5. command_cb
  6. show_access_cb
  7. section_cb
  8. build_arg_context
  9. main
  10. do_work
  11. do_init
  12. cibadmin_op_callback

   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 #include <stdio.h>
  12 #include <crm/crm.h>
  13 #include <crm/common/cmdline_internal.h>
  14 #include <crm/common/ipc.h>
  15 #include <crm/common/xml.h>
  16 #include <crm/cib/internal.h>
  17 
  18 #include <pacemaker-internal.h>
  19 
  20 #define SUMMARY "query and edit the Pacemaker configuration"
  21 
  22 #define INDENT "                                "
  23 
  24 enum cibadmin_section_type {
  25     cibadmin_section_all = 0,
  26     cibadmin_section_scope,
  27     cibadmin_section_xpath,
  28 };
  29 
  30 static int request_id = 0;
  31 
  32 static cib_t *the_cib = NULL;
  33 static GMainLoop *mainloop = NULL;
  34 static crm_exit_t exit_code = CRM_EX_OK;
  35 
  36 static struct {
  37     const char *cib_action;
  38     int cmd_options;
  39     enum cibadmin_section_type section_type;
  40     char *cib_section;
  41     char *validate_with;
  42     gint message_timeout_sec;
  43     enum pcmk__acl_render_how acl_render_mode;
  44     gchar *cib_user;
  45     gchar *dest_node;
  46     gchar *input_file;
  47     gchar *input_xml;
  48     gboolean input_stdin;
  49     bool delete_all;
  50     gboolean allow_create;
  51     gboolean force;
  52     gboolean get_node_path;
  53     gboolean local;
  54     gboolean no_children;
  55     gboolean score_update;
  56     gboolean sync_call;
  57 
  58     /* @COMPAT: For "-!" version option. Not advertised nor marked as
  59      * deprecated, but accepted.
  60      */
  61     gboolean extended_version;
  62 
  63     //! \deprecated
  64     gboolean no_bcast;
  65 } options;
  66 
  67 int do_init(void);
  68 static int do_work(xmlNode *input, xmlNode **output);
  69 void cibadmin_op_callback(xmlNode *msg, int call_id, int rc, xmlNode *output,
  70                           void *user_data);
  71 
  72 static void
  73 print_xml_output(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
  74 {
  75     if (!xml) {
  76         return;
  77     } else if (xml->type != XML_ELEMENT_NODE) {
  78         return;
  79     }
  80 
  81     if (pcmk_is_set(options.cmd_options, cib_xpath_address)) {
  82         const char *id = crm_element_value(xml, PCMK_XA_ID);
  83 
  84         if (pcmk__xe_is(xml, PCMK__XE_XPATH_QUERY)) {
  85             xmlNode *child = NULL;
  86 
  87             for (child = xml->children; child; child = child->next) {
  88                 print_xml_output(child);
  89             }
  90 
  91         } else if (id) {
  92             printf("%s\n", id);
  93         }
  94 
  95     } else {
  96         GString *buf = g_string_sized_new(1024);
  97 
  98         pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buf, 0);
  99 
 100         fprintf(stdout, "%s", buf->str);
 101         g_string_free(buf, TRUE);
 102     }
 103 }
 104 
 105 // Upgrade requested but already at latest schema
 106 static void
 107 report_schema_unchanged(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 108 {
 109     const char *err = pcmk_rc_str(pcmk_rc_schema_unchanged);
 110 
 111     crm_info("Upgrade unnecessary: %s\n", err);
 112     printf("Upgrade unnecessary: %s\n", err);
 113     exit_code = CRM_EX_OK;
 114 }
 115 
 116 /*!
 117  * \internal
 118  * \brief Check whether the current CIB action is dangerous
 119  * \return true if \p options.cib_action is dangerous, or false otherwise
 120  */
 121 static inline bool
 122 cib_action_is_dangerous(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 123 {
 124     return options.no_bcast || options.delete_all
 125            || pcmk__str_any_of(options.cib_action,
 126                                PCMK__CIB_REQUEST_UPGRADE,
 127                                PCMK__CIB_REQUEST_ERASE,
 128                                NULL);
 129 }
 130 
 131 /*!
 132  * \internal
 133  * \brief Determine whether the given CIB scope is valid for \p cibadmin
 134  *
 135  * \param[in] scope  Scope to validate
 136  *
 137  * \return true if \p scope is valid, or false otherwise
 138  * \note An invalid scope applies the operation to the entire CIB.
 139  */
 140 static inline bool
 141 scope_is_valid(const char *scope)
     /* [previous][next][first][last][top][bottom][index][help] */
 142 {
 143     return pcmk__str_any_of(scope,
 144                             PCMK_XE_CONFIGURATION,
 145                             PCMK_XE_NODES,
 146                             PCMK_XE_RESOURCES,
 147                             PCMK_XE_CONSTRAINTS,
 148                             PCMK_XE_CRM_CONFIG,
 149                             PCMK_XE_RSC_DEFAULTS,
 150                             PCMK_XE_OP_DEFAULTS,
 151                             PCMK_XE_ACLS,
 152                             PCMK_XE_FENCING_TOPOLOGY,
 153                             PCMK_XE_TAGS,
 154                             PCMK_XE_ALERTS,
 155                             PCMK_XE_STATUS,
 156                             NULL);
 157 }
 158 
 159 static gboolean
 160 command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
 161            GError **error)
 162 {
 163     options.delete_all = false;
 164 
 165     if (pcmk__str_any_of(option_name, "-u", "--upgrade", NULL)) {
 166         options.cib_action = PCMK__CIB_REQUEST_UPGRADE;
 167 
 168     } else if (pcmk__str_any_of(option_name, "-Q", "--query", NULL)) {
 169         options.cib_action = PCMK__CIB_REQUEST_QUERY;
 170 
 171     } else if (pcmk__str_any_of(option_name, "-E", "--erase", NULL)) {
 172         options.cib_action = PCMK__CIB_REQUEST_ERASE;
 173 
 174     } else if (pcmk__str_any_of(option_name, "-B", "--bump", NULL)) {
 175         options.cib_action = PCMK__CIB_REQUEST_BUMP;
 176 
 177     } else if (pcmk__str_any_of(option_name, "-C", "--create", NULL)) {
 178         options.cib_action = PCMK__CIB_REQUEST_CREATE;
 179 
 180     } else if (pcmk__str_any_of(option_name, "-M", "--modify", NULL)) {
 181         options.cib_action = PCMK__CIB_REQUEST_MODIFY;
 182 
 183     } else if (pcmk__str_any_of(option_name, "-P", "--patch", NULL)) {
 184         options.cib_action = PCMK__CIB_REQUEST_APPLY_PATCH;
 185 
 186     } else if (pcmk__str_any_of(option_name, "-R", "--replace", NULL)) {
 187         options.cib_action = PCMK__CIB_REQUEST_REPLACE;
 188 
 189     } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
 190         options.cib_action = PCMK__CIB_REQUEST_DELETE;
 191 
 192     } else if (pcmk__str_any_of(option_name, "-d", "--delete-all", NULL)) {
 193         options.cib_action = PCMK__CIB_REQUEST_DELETE;
 194         options.delete_all = true;
 195 
 196     } else if (pcmk__str_any_of(option_name, "-a", "--empty", NULL)) {
 197         options.cib_action = "empty";
 198         pcmk__str_update(&options.validate_with, optarg);
 199 
 200     } else if (pcmk__str_any_of(option_name, "-5", "--md5-sum", NULL)) {
 201         options.cib_action = "md5-sum";
 202 
 203     } else if (pcmk__str_any_of(option_name, "-6", "--md5-sum-versioned",
 204                                 NULL)) {
 205         options.cib_action = "md5-sum-versioned";
 206 
 207     } else {
 208         // Should be impossible
 209         return FALSE;
 210     }
 211 
 212     return TRUE;
 213 }
 214 
 215 static gboolean
 216 show_access_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
 217                GError **error)
 218 {
 219     if (pcmk__str_eq(optarg, "auto", pcmk__str_null_matches)) {
 220         options.acl_render_mode = pcmk__acl_render_default;
 221 
 222     } else if (g_strcmp0(optarg, "namespace") == 0) {
 223         options.acl_render_mode = pcmk__acl_render_namespace;
 224 
 225     } else if (g_strcmp0(optarg, "text") == 0) {
 226         options.acl_render_mode = pcmk__acl_render_text;
 227 
 228     } else if (g_strcmp0(optarg, "color") == 0) {
 229         options.acl_render_mode = pcmk__acl_render_color;
 230 
 231     } else {
 232         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
 233                     "Invalid value '%s' for option '%s'",
 234                     optarg, option_name);
 235         return FALSE;
 236     }
 237     return TRUE;
 238 }
 239 
 240 static gboolean
 241 section_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
 242            GError **error)
 243 {
 244     if (pcmk__str_any_of(option_name, "-o", "--scope", NULL)) {
 245         options.section_type = cibadmin_section_scope;
 246 
 247     } else if (pcmk__str_any_of(option_name, "-A", "--xpath", NULL)) {
 248         options.section_type = cibadmin_section_xpath;
 249 
 250     } else {
 251         // Should be impossible
 252         return FALSE;
 253     }
 254 
 255     pcmk__str_update(&options.cib_section, optarg);
 256     return TRUE;
 257 }
 258 
 259 static GOptionEntry command_entries[] = {
 260     { "upgrade", 'u', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 261       "Upgrade the configuration to the latest syntax", NULL },
 262 
 263     { "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 264       "Query the contents of the CIB", NULL },
 265 
 266     { "erase", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 267       "Erase the contents of the whole CIB", NULL },
 268 
 269     { "bump", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 270       "Increase the CIB's epoch value by 1", NULL },
 271 
 272     { "create", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 273       "Create an object in the CIB (will fail if object already exists)",
 274       NULL },
 275 
 276     { "modify", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 277       "Find object somewhere in CIB's XML tree and update it (fails if object "
 278       "does not exist unless -c is also specified)",
 279       NULL },
 280 
 281     { "patch", 'P', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 282       "Supply an update in the form of an XML diff (see crm_diff(8))", NULL },
 283 
 284     { "replace", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 285       "Recursively replace an object in the CIB", NULL },
 286 
 287     { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 288       "Delete first object matching supplied criteria (for example, "
 289       "<" PCMK_XE_OP " " PCMK_XA_ID "=\"rsc1_op1\" "
 290           PCMK_XA_NAME "=\"monitor\"/>).\n"
 291       INDENT "The XML element name and all attributes must match in order for "
 292       "the element to be deleted.",
 293       NULL },
 294 
 295     { "delete-all", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
 296       command_cb,
 297       "When used with --xpath, remove all matching objects in the "
 298       "configuration instead of just the first one",
 299       NULL },
 300 
 301     { "empty", 'a', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
 302       command_cb,
 303       "Output an empty CIB. Accepts an optional schema name argument to use as "
 304       "the " PCMK_XA_VALIDATE_WITH " value.\n"
 305       INDENT "If no schema is given, the latest will be used.",
 306       "[schema]" },
 307 
 308     { "md5-sum", '5', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 309       "Calculate the on-disk CIB digest", NULL },
 310 
 311     { "md5-sum-versioned", '6', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
 312       command_cb, "Calculate an on-the-wire versioned CIB digest", NULL },
 313 
 314     { NULL }
 315 };
 316 
 317 static GOptionEntry data_entries[] = {
 318     /* @COMPAT: These arguments should be last-wins. We can have an enum option
 319      * that stores the input type, along with a single string option that stores
 320      * the XML string for --xml-text, filename for --xml-file, or NULL for
 321      * --xml-pipe.
 322      */
 323     { "xml-text", 'X', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
 324       &options.input_xml, "Retrieve XML from the supplied string", "value" },
 325 
 326     { "xml-file", 'x', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME,
 327       &options.input_file, "Retrieve XML from the named file", "value" },
 328 
 329     { "xml-pipe", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 330       &options.input_stdin, "Retrieve XML from stdin", NULL },
 331 
 332     { NULL }
 333 };
 334 
 335 static GOptionEntry addl_entries[] = {
 336     { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
 337       "Force the action to be performed", NULL },
 338 
 339     { "timeout", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT,
 340       &options.message_timeout_sec,
 341       "Time (in seconds) to wait before declaring the operation failed",
 342       "value" },
 343 
 344     { "user", 'U', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.cib_user,
 345       "Run the command with permissions of the named user (valid only for the "
 346       "root and " CRM_DAEMON_USER " accounts)", "value" },
 347 
 348     { "sync-call", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 349       &options.sync_call, "Wait for call to complete before returning", NULL },
 350 
 351     { "local", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.local,
 352       "Command takes effect locally (should be used only for queries)", NULL },
 353 
 354     { "scope", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
 355       "Limit scope of operation to specific section of CIB\n"
 356       INDENT "Valid values: " PCMK_XE_CONFIGURATION ", " PCMK_XE_NODES
 357       ", " PCMK_XE_RESOURCES ", " PCMK_XE_CONSTRAINTS
 358       ", " PCMK_XE_CRM_CONFIG ", " PCMK_XE_RSC_DEFAULTS ",\n"
 359       INDENT "              " PCMK_XE_OP_DEFAULTS ", " PCMK_XE_ACLS
 360       ", " PCMK_XE_FENCING_TOPOLOGY ", " PCMK_XE_TAGS ", " PCMK_XE_ALERTS
 361       ", " PCMK_XE_STATUS "\n"
 362       INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
 363       "appear takes effect",
 364       "value" },
 365 
 366     { "xpath", 'A', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
 367       "A valid XPath to use instead of --scope/-o\n"
 368       INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
 369       "appear takes effect",
 370       "value" },
 371 
 372     { "node-path", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 373       &options.get_node_path,
 374       "When performing XPath queries, return paths of any matches found\n"
 375       INDENT "(for example, "
 376       "\"/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
 377       "/" PCMK_XE_RESOURCES "/" PCMK_XE_CLONE
 378       "[@" PCMK_XA_ID "='dummy-clone']"
 379       "/" PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "='dummy']\")",
 380       NULL },
 381 
 382     { "show-access", 'S', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
 383       show_access_cb,
 384       "Whether to use syntax highlighting for ACLs (with -Q/--query and "
 385       "-U/--user)\n"
 386       INDENT "Allowed values: 'color' (default for terminal), 'text' (plain text, "
 387       "default for non-terminal),\n"
 388       INDENT "                'namespace', or 'auto' (use default value)\n"
 389       INDENT "Default value: 'auto'",
 390       "[value]" },
 391 
 392     { "score", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.score_update,
 393       "Treat new attribute values as atomic score updates where possible "
 394       "(with --modify/-M).\n"
 395 
 396       INDENT "This currently happens by default and cannot be disabled, but\n"
 397       INDENT "this default behavior is deprecated and will be removed in a\n"
 398       INDENT "future release. Set this flag if this behavior is desired.\n"
 399 
 400       INDENT "This option takes effect when updating XML attributes. For an\n"
 401       INDENT "attribute named \"name\", if the new value is \"name++\" or\n"
 402       INDENT "\"name+=X\" for some score X, the new value is set as follows:\n"
 403       INDENT "If attribute \"name\" is not already set to some value in\n"
 404       INDENT "the element being updated, the new value is set as a literal\n"
 405       INDENT "string.\n"
 406       INDENT "If the new value is \"name++\", then the attribute is set to \n"
 407       INDENT "its existing value (parsed as a score) plus 1.\n"
 408       INDENT "If the new value is \"name+=X\" for some score X, then the\n"
 409       INDENT "attribute is set to its existing value plus X, where the\n"
 410       INDENT "existing value and X are parsed and added as scores.\n"
 411 
 412       INDENT "Scores are integer values capped at INFINITY and -INFINITY.\n"
 413       INDENT "Refer to Pacemaker Explained and to the char2score() function\n"
 414       INDENT "for more details on scores, including how they're parsed and\n"
 415       INDENT "added.",
 416       NULL },
 417 
 418     { "allow-create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 419       &options.allow_create,
 420       "(Advanced) Allow target of --modify/-M to be created if it does not "
 421       "exist",
 422       NULL },
 423 
 424     { "no-children", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 425       &options.no_children,
 426       "(Advanced) When querying an object, do not include its children in the "
 427       "result",
 428       NULL },
 429 
 430     { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.dest_node,
 431       "(Advanced) Send command to the specified host", "value" },
 432 
 433     // @COMPAT: Deprecated
 434     { "no-bcast", 'b', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
 435       &options.no_bcast, "deprecated", NULL },
 436 
 437     // @COMPAT: Deprecated
 438     { "host", 'h', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
 439       &options.dest_node, "deprecated", NULL },
 440 
 441     { NULL }
 442 };
 443 
 444 static GOptionContext *
 445 build_arg_context(pcmk__common_args_t *args)
     /* [previous][next][first][last][top][bottom][index][help] */
 446 {
 447     const char *desc = NULL;
 448     GOptionContext *context = NULL;
 449 
 450     GOptionEntry extra_prog_entries[] = {
 451         // @COMPAT: Deprecated
 452         { "extended-version", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
 453           &options.extended_version, "deprecated", NULL },
 454 
 455         { NULL }
 456     };
 457 
 458     desc = "Examples:\n\n"
 459            "Query the configuration from the local node:\n\n"
 460            "\t# cibadmin --query --local\n\n"
 461            "Query just the cluster options configuration:\n\n"
 462            "\t# cibadmin --query --scope " PCMK_XE_CRM_CONFIG "\n\n"
 463            "Query all '" PCMK_META_TARGET_ROLE "' settings:\n\n"
 464            "\t# cibadmin --query --xpath "
 465                "\"//" PCMK_XE_NVPAIR
 466                "[@" PCMK_XA_NAME "='" PCMK_META_TARGET_ROLE"']\"\n\n"
 467            "Remove all '" PCMK_META_IS_MANAGED "' settings:\n\n"
 468            "\t# cibadmin --delete-all --xpath "
 469                "\"//" PCMK_XE_NVPAIR
 470                "[@" PCMK_XA_NAME "='" PCMK_META_IS_MANAGED "']\"\n\n"
 471            "Remove the resource named 'old':\n\n"
 472            "\t# cibadmin --delete --xml-text "
 473                "'<" PCMK_XE_PRIMITIVE " " PCMK_XA_ID "=\"old\"/>'\n\n"
 474            "Remove all resources from the configuration:\n\n"
 475            "\t# cibadmin --replace --scope " PCMK_XE_RESOURCES
 476                " --xml-text '<" PCMK_XE_RESOURCES "/>'\n\n"
 477            "Replace complete configuration with contents of "
 478                "$HOME/pacemaker.xml:\n\n"
 479            "\t# cibadmin --replace --xml-file $HOME/pacemaker.xml\n\n"
 480            "Replace " PCMK_XE_CONSTRAINTS " section of configuration with "
 481                "contents of $HOME/constraints.xml:\n\n"
 482            "\t# cibadmin --replace --scope " PCMK_XE_CONSTRAINTS
 483                " --xml-file $HOME/constraints.xml\n\n"
 484            "Increase configuration version to prevent old configurations from "
 485                "being loaded accidentally:\n\n"
 486            "\t# cibadmin --modify --score --xml-text "
 487                "'<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH
 488                    "=\"" PCMK_XA_ADMIN_EPOCH "++\"/>'\n\n"
 489            "Edit the configuration with your favorite $EDITOR:\n\n"
 490            "\t# cibadmin --query > $HOME/local.xml\n\n"
 491            "\t# $EDITOR $HOME/local.xml\n\n"
 492            "\t# cibadmin --replace --xml-file $HOME/local.xml\n\n"
 493            "Assuming terminal, render configuration in color (green for "
 494                "writable, blue for readable, red for\n"
 495                "denied) to visualize permissions for user tony:\n\n"
 496            "\t# cibadmin --show-access=color --query --user tony | less -r\n\n"
 497            "SEE ALSO:\n"
 498            " crm(8), pcs(8), crm_shadow(8), crm_diff(8)\n";
 499 
 500     context = pcmk__build_arg_context(args, NULL, NULL, "<command>");
 501     g_option_context_set_description(context, desc);
 502 
 503     pcmk__add_main_args(context, extra_prog_entries);
 504 
 505     pcmk__add_arg_group(context, "commands", "Commands:", "Show command help",
 506                         command_entries);
 507     pcmk__add_arg_group(context, "data", "Data:", "Show data help",
 508                         data_entries);
 509     pcmk__add_arg_group(context, "additional", "Additional Options:",
 510                         "Show additional options", addl_entries);
 511     return context;
 512 }
 513 
 514 int
 515 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 516 {
 517     int rc = pcmk_rc_ok;
 518     const char *source = NULL;
 519     xmlNode *output = NULL;
 520     xmlNode *input = NULL;
 521     gchar *acl_cred = NULL;
 522 
 523     GError *error = NULL;
 524 
 525     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 526     gchar **processed_args = pcmk__cmdline_preproc(argv, "ANSUXhotx");
 527     GOptionContext *context = build_arg_context(args);
 528 
 529     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 530         exit_code = CRM_EX_USAGE;
 531         goto done;
 532     }
 533 
 534     if (g_strv_length(processed_args) > 1) {
 535         gchar *help = g_option_context_get_help(context, TRUE, NULL);
 536         GString *extra = g_string_sized_new(128);
 537 
 538         for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
 539             if (extra->len > 0) {
 540                 g_string_append_c(extra, ' ');
 541             }
 542             g_string_append(extra, processed_args[lpc]);
 543         }
 544 
 545         exit_code = CRM_EX_USAGE;
 546         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 547                     "non-option ARGV-elements: %s\n\n%s", extra->str, help);
 548         g_free(help);
 549         g_string_free(extra, TRUE);
 550         goto done;
 551     }
 552 
 553     if (args->version || options.extended_version) {
 554         g_strfreev(processed_args);
 555         pcmk__free_arg_context(context);
 556 
 557         /* FIXME: When cibadmin is converted to use formatted output, this can
 558          * be replaced by out->version with the appropriate boolean flag.
 559          *
 560          * options.extended_version is deprecated and will be removed in a
 561          * future release.
 562          */
 563         pcmk__cli_help(options.extended_version? '!' : 'v');
 564     }
 565 
 566     /* At LOG_ERR, stderr for CIB calls is rather verbose. Several lines like
 567      *
 568      * (func@file:line)      error: CIB <op> failures   <XML>
 569      *
 570      * In cibadmin we explicitly output the XML portion without the prefixes. So
 571      * we default to LOG_CRIT.
 572      */
 573     pcmk__cli_init_logging("cibadmin", 0);
 574     set_crm_log_level(LOG_CRIT);
 575 
 576     if (args->verbosity > 0) {
 577         cib__set_call_options(options.cmd_options, crm_system_name,
 578                               cib_verbose);
 579 
 580         for (int i = 0; i < args->verbosity; i++) {
 581             crm_bump_log_level(argc, argv);
 582         }
 583     }
 584 
 585     if (options.cib_action == NULL) {
 586         // @COMPAT: Create a default command if other tools have one
 587         gchar *help = g_option_context_get_help(context, TRUE, NULL);
 588 
 589         exit_code = CRM_EX_USAGE;
 590         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 591                     "Must specify a command option\n\n%s", help);
 592         g_free(help);
 593         goto done;
 594     }
 595 
 596     if (strcmp(options.cib_action, "empty") == 0) {
 597         // Output an empty CIB
 598         GString *buf = g_string_sized_new(1024);
 599 
 600         output = createEmptyCib(1);
 601         crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
 602 
 603         pcmk__xml_string(output, pcmk__xml_fmt_pretty, buf, 0);
 604         fprintf(stdout, "%s", buf->str);
 605         g_string_free(buf, TRUE);
 606         goto done;
 607     }
 608 
 609     if (cib_action_is_dangerous() && !options.force) {
 610         exit_code = CRM_EX_UNSAFE;
 611         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 612                     "The supplied command is considered dangerous. To prevent "
 613                     "accidental destruction of the cluster, the --force flag "
 614                     "is required in order to proceed.");
 615         goto done;
 616     }
 617 
 618     if (options.message_timeout_sec < 1) {
 619         // Set default timeout
 620         options.message_timeout_sec = 30;
 621     }
 622 
 623     if (options.section_type == cibadmin_section_xpath) {
 624         // Enable getting section by XPath
 625         cib__set_call_options(options.cmd_options, crm_system_name,
 626                               cib_xpath);
 627 
 628     } else if (options.section_type == cibadmin_section_scope) {
 629         if (!scope_is_valid(options.cib_section)) {
 630             // @COMPAT: Consider requiring --force to proceed
 631             fprintf(stderr,
 632                     "Invalid value '%s' for '--scope'. Operation will apply "
 633                     "to the entire CIB.\n", options.cib_section);
 634         }
 635     }
 636 
 637     if (options.allow_create) {
 638         // Allow target of --modify/-M to be created if it does not exist
 639         cib__set_call_options(options.cmd_options, crm_system_name,
 640                               cib_can_create);
 641     }
 642 
 643     if (options.delete_all) {
 644         // With cibadmin_section_xpath, remove all matching objects
 645         cib__set_call_options(options.cmd_options, crm_system_name,
 646                               cib_multiple);
 647     }
 648 
 649     if (options.get_node_path) {
 650         /* Enable getting node path of XPath query matches.
 651          * Meaningful only if options.section_type == cibadmin_section_xpath.
 652          */
 653         cib__set_call_options(options.cmd_options, crm_system_name,
 654                               cib_xpath_address);
 655     }
 656 
 657     if (options.local) {
 658         // Configure command to take effect only locally
 659         cib__set_call_options(options.cmd_options, crm_system_name,
 660                               cib_scope_local);
 661     }
 662 
 663     // @COMPAT: Deprecated option
 664     if (options.no_bcast) {
 665         // Configure command to take effect only locally and not to broadcast
 666         cib__set_call_options(options.cmd_options, crm_system_name,
 667                               cib_inhibit_bcast|cib_scope_local);
 668     }
 669 
 670     if (options.no_children) {
 671         // When querying an object, don't include its children in the result
 672         cib__set_call_options(options.cmd_options, crm_system_name,
 673                               cib_no_children);
 674     }
 675 
 676     if (options.sync_call
 677         || (options.acl_render_mode != pcmk__acl_render_none)) {
 678         /* Wait for call to complete before returning.
 679          *
 680          * The ACL render modes work only with sync calls due to differences in
 681          * output handling between sync/async. It shouldn't matter to the user
 682          * whether the call is synchronous; for a CIB query, we have to wait for
 683          * the result in order to display it in any case.
 684          */
 685         cib__set_call_options(options.cmd_options, crm_system_name,
 686                               cib_sync_call);
 687     }
 688 
 689     if (options.input_file != NULL) {
 690         input = pcmk__xml_read(options.input_file);
 691         source = options.input_file;
 692 
 693     } else if (options.input_xml != NULL) {
 694         input = pcmk__xml_parse(options.input_xml);
 695         source = "input string";
 696 
 697     } else if (options.input_stdin) {
 698         input = pcmk__xml_read(NULL);
 699         source = "STDIN";
 700 
 701     } else if (options.acl_render_mode != pcmk__acl_render_none) {
 702         char *username = pcmk__uid2username(geteuid());
 703         bool required = pcmk_acl_required(username);
 704 
 705         free(username);
 706 
 707         if (required) {
 708             if (options.force) {
 709                 fprintf(stderr, "The supplied command can provide skewed"
 710                                  " result since it is run under user that also"
 711                                  " gets guarded per ACLs on their own right."
 712                                  " Continuing since --force flag was"
 713                                  " provided.\n");
 714 
 715             } else {
 716                 exit_code = CRM_EX_UNSAFE;
 717                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 718                             "The supplied command can provide skewed result "
 719                             "since it is run under user that also gets guarded "
 720                             "per ACLs in their own right. To accept the risk "
 721                             "of such a possible distortion (without even "
 722                             "knowing it at this time), use the --force flag.");
 723                 goto done;
 724             }
 725         }
 726 
 727         if (options.cib_user == NULL) {
 728             exit_code = CRM_EX_USAGE;
 729             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 730                         "The supplied command requires -U user specified.");
 731             goto done;
 732         }
 733 
 734         /* We already stopped/warned ACL-controlled users about consequences.
 735          *
 736          * Note: acl_cred takes ownership of options.cib_user here.
 737          * options.cib_user is set to NULL so that the CIB is obtained as the
 738          * user running the cibadmin command. The CIB must be obtained as a user
 739          * with full permissions in order to show the CIB correctly annotated
 740          * for the options.cib_user's permissions.
 741          */
 742         acl_cred = options.cib_user;
 743         options.cib_user = NULL;
 744     }
 745 
 746     if (input != NULL) {
 747         crm_log_xml_debug(input, "[admin input]");
 748 
 749     } else if (source != NULL) {
 750         exit_code = CRM_EX_CONFIG;
 751         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 752                     "Couldn't parse input from %s.", source);
 753         goto done;
 754     }
 755 
 756     if (pcmk__str_eq(options.cib_action, "md5-sum", pcmk__str_casei)) {
 757         char *digest = NULL;
 758 
 759         if (input == NULL) {
 760             exit_code = CRM_EX_USAGE;
 761             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 762                         "Please supply XML to process with -X, -x, or -p");
 763             goto done;
 764         }
 765 
 766         digest = calculate_on_disk_digest(input);
 767         fprintf(stderr, "Digest: ");
 768         fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
 769         free(digest);
 770         goto done;
 771 
 772     } else if (strcmp(options.cib_action, "md5-sum-versioned") == 0) {
 773         char *digest = NULL;
 774         const char *version = NULL;
 775 
 776         if (input == NULL) {
 777             exit_code = CRM_EX_USAGE;
 778             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 779                         "Please supply XML to process with -X, -x, or -p");
 780             goto done;
 781         }
 782 
 783         version = crm_element_value(input, PCMK_XA_CRM_FEATURE_SET);
 784         digest = calculate_xml_versioned_digest(input, FALSE, TRUE, version);
 785         fprintf(stderr, "Versioned (%s) digest: ", version);
 786         fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
 787         free(digest);
 788         goto done;
 789 
 790     } else if (pcmk__str_eq(options.cib_action, PCMK__CIB_REQUEST_MODIFY,
 791                             pcmk__str_none)) {
 792         /* @COMPAT When we drop default support for expansion in cibadmin, guard
 793          * with `if (options.score_update)`
 794          */
 795         cib__set_call_options(options.cmd_options, crm_system_name,
 796                               cib_score_update);
 797     }
 798 
 799     rc = do_init();
 800     if (rc != pcmk_ok) {
 801         rc = pcmk_legacy2rc(rc);
 802         exit_code = pcmk_rc2exitc(rc);
 803 
 804         crm_err("Init failed, could not perform requested operations: %s",
 805                 pcmk_rc_str(rc));
 806         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 807                     "Init failed, could not perform requested operations: %s",
 808                     pcmk_rc_str(rc));
 809         goto done;
 810     }
 811 
 812     rc = do_work(input, &output);
 813     if (!pcmk_is_set(options.cmd_options, cib_sync_call)
 814         && (the_cib->variant != cib_file)
 815         && (rc >= 0)) {
 816         /* For async call, positive rc is the call ID (file always synchronous).
 817          *
 818          * Wait for the reply by creating a mainloop and running it until the
 819          * callbacks are invoked.
 820          */
 821         request_id = rc;
 822 
 823         the_cib->cmds->register_callback(the_cib, request_id,
 824                                          options.message_timeout_sec, FALSE,
 825                                          NULL, "cibadmin_op_callback",
 826                                          cibadmin_op_callback);
 827 
 828         mainloop = g_main_loop_new(NULL, FALSE);
 829 
 830         crm_trace("%s waiting for reply from the local CIB", crm_system_name);
 831 
 832         crm_info("Starting mainloop");
 833         g_main_loop_run(mainloop);
 834 
 835     } else {
 836         rc = pcmk_legacy2rc(rc);
 837 
 838         if ((rc == pcmk_rc_schema_unchanged)
 839             && (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0)) {
 840 
 841             report_schema_unchanged();
 842 
 843         } else if (rc != pcmk_rc_ok) {
 844             crm_err("Call failed: %s", pcmk_rc_str(rc));
 845             fprintf(stderr, "Call failed: %s\n", pcmk_rc_str(rc));
 846             exit_code = pcmk_rc2exitc(rc);
 847 
 848             if (rc == pcmk_rc_schema_validation) {
 849                 if (strcmp(options.cib_action,
 850                            PCMK__CIB_REQUEST_UPGRADE) == 0) {
 851                     xmlNode *obj = NULL;
 852 
 853                     if (the_cib->cmds->query(the_cib, NULL, &obj,
 854                                              options.cmd_options) == pcmk_ok) {
 855                         pcmk__update_schema(&obj, NULL, true, false);
 856                     }
 857                     free_xml(obj);
 858 
 859                 } else if (output != NULL) {
 860                     // Show validation errors to stderr
 861                     pcmk__validate_xml(output, NULL, NULL, NULL);
 862                 }
 863             }
 864         }
 865     }
 866 
 867     if ((output != NULL)
 868         && (options.acl_render_mode != pcmk__acl_render_none)) {
 869 
 870         xmlDoc *acl_evaled_doc;
 871         rc = pcmk__acl_annotate_permissions(acl_cred, output->doc, &acl_evaled_doc);
 872         if (rc == pcmk_rc_ok) {
 873             xmlChar *rendered = NULL;
 874 
 875             rc = pcmk__acl_evaled_render(acl_evaled_doc,
 876                                          options.acl_render_mode, &rendered);
 877             if (rc != pcmk_rc_ok) {
 878                 exit_code = CRM_EX_CONFIG;
 879                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 880                             "Could not render evaluated access: %s",
 881                             pcmk_rc_str(rc));
 882                 goto done;
 883             }
 884             printf("%s\n", (char *) rendered);
 885             free(rendered);
 886 
 887         } else {
 888             exit_code = CRM_EX_CONFIG;
 889             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 890                         "Could not evaluate access per request (%s, error: %s)",
 891                         acl_cred, pcmk_rc_str(rc));
 892             goto done;
 893         }
 894 
 895     } else if (output != NULL) {
 896         print_xml_output(output);
 897     }
 898 
 899     crm_trace("%s exiting normally", crm_system_name);
 900 
 901 done:
 902     g_strfreev(processed_args);
 903     pcmk__free_arg_context(context);
 904 
 905     g_free(options.cib_user);
 906     g_free(options.dest_node);
 907     g_free(options.input_file);
 908     g_free(options.input_xml);
 909     free(options.cib_section);
 910     free(options.validate_with);
 911 
 912     g_free(acl_cred);
 913     free_xml(input);
 914     free_xml(output);
 915 
 916     rc = cib__clean_up_connection(&the_cib);
 917     if (exit_code == CRM_EX_OK) {
 918         exit_code = pcmk_rc2exitc(rc);
 919     }
 920 
 921     pcmk__output_and_clear_error(&error, NULL);
 922     crm_exit(exit_code);
 923 }
 924 
 925 static int
 926 do_work(xmlNode *input, xmlNode **output)
     /* [previous][next][first][last][top][bottom][index][help] */
 927 {
 928     /* construct the request */
 929     the_cib->call_timeout = options.message_timeout_sec;
 930     if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_REPLACE) == 0)
 931         && pcmk__xe_is(input, PCMK_XE_CIB)) {
 932         xmlNode *status = pcmk_find_cib_element(input, PCMK_XE_STATUS);
 933 
 934         if (status == NULL) {
 935             pcmk__xe_create(input, PCMK_XE_STATUS);
 936         }
 937     }
 938 
 939     crm_trace("Passing \"%s\" to variant_op...", options.cib_action);
 940     return cib_internal_op(the_cib, options.cib_action, options.dest_node,
 941                            options.cib_section, input, output,
 942                            options.cmd_options, options.cib_user);
 943 }
 944 
 945 int
 946 do_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 947 {
 948     int rc = pcmk_ok;
 949 
 950     the_cib = cib_new();
 951     rc = cib__signon_attempts(the_cib, crm_system_name, cib_command, 5);
 952     if (rc != pcmk_ok) {
 953         crm_err("Could not connect to the CIB: %s", pcmk_strerror(rc));
 954         fprintf(stderr, "Could not connect to the CIB: %s\n",
 955                 pcmk_strerror(rc));
 956     }
 957 
 958     return rc;
 959 }
 960 
 961 void
 962 cibadmin_op_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 963 {
 964     rc = pcmk_legacy2rc(rc);
 965     exit_code = pcmk_rc2exitc(rc);
 966 
 967     if (rc == pcmk_rc_schema_unchanged) {
 968         report_schema_unchanged();
 969 
 970     } else if (rc != pcmk_rc_ok) {
 971         crm_warn("Call %s failed: %s " CRM_XS " rc=%d",
 972                  options.cib_action, pcmk_rc_str(rc), rc);
 973         fprintf(stderr, "Call %s failed: %s\n",
 974                 options.cib_action, pcmk_rc_str(rc));
 975         print_xml_output(output);
 976 
 977     } else if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_QUERY) == 0)
 978                && (output == NULL)) {
 979         crm_err("Query returned no output");
 980         crm_log_xml_err(msg, "no output");
 981 
 982     } else if (output == NULL) {
 983         crm_info("Call passed");
 984 
 985     } else {
 986         crm_info("Call passed");
 987         print_xml_output(output);
 988     }
 989 
 990     if (call_id == request_id) {
 991         g_main_loop_quit(mainloop);
 992 
 993     } else {
 994         crm_info("Message was not the response we were looking for (%d vs. %d)",
 995                  call_id, request_id);
 996     }
 997 }

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