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

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