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

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