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 no_children;
  54     gboolean score_update;
  55     gboolean sync_call;
  56 
  57     /* @COMPAT: For "-!" version option. Not advertised nor marked as
  58      * deprecated, but accepted.
  59      */
  60     gboolean extended_version;
  61 
  62     // @COMPAT Deprecated since 3.0.0
  63     gboolean local;
  64 } options;
  65 
  66 int do_init(void);
  67 static int do_work(xmlNode *input, xmlNode **output);
  68 void cibadmin_op_callback(xmlNode *msg, int call_id, int rc, xmlNode *output,
  69                           void *user_data);
  70 
  71 static void
  72 print_xml_output(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
  73 {
  74     if (!xml) {
  75         return;
  76     } else if (xml->type != XML_ELEMENT_NODE) {
  77         return;
  78     }
  79 
  80     if (pcmk_is_set(options.cmd_options, cib_xpath_address)) {
  81         const char *id = crm_element_value(xml, PCMK_XA_ID);
  82 
  83         if (pcmk__xe_is(xml, PCMK__XE_XPATH_QUERY)) {
  84             xmlNode *child = NULL;
  85 
  86             for (child = xml->children; child; child = child->next) {
  87                 print_xml_output(child);
  88             }
  89 
  90         } else if (id) {
  91             printf("%s\n", id);
  92         }
  93 
  94     } else {
  95         GString *buf = g_string_sized_new(1024);
  96 
  97         pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buf, 0);
  98 
  99         fprintf(stdout, "%s", buf->str);
 100         g_string_free(buf, TRUE);
 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.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                             PCMK_XE_CONFIGURATION,
 144                             PCMK_XE_NODES,
 145                             PCMK_XE_RESOURCES,
 146                             PCMK_XE_CONSTRAINTS,
 147                             PCMK_XE_CRM_CONFIG,
 148                             PCMK_XE_RSC_DEFAULTS,
 149                             PCMK_XE_OP_DEFAULTS,
 150                             PCMK_XE_ACLS,
 151                             PCMK_XE_FENCING_TOPOLOGY,
 152                             PCMK_XE_TAGS,
 153                             PCMK_XE_ALERTS,
 154                             PCMK_XE_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       "<" PCMK_XE_OP " " PCMK_XA_ID "=\"rsc1_op1\" "
 289           PCMK_XA_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 " PCMK_XA_VALIDATE_WITH " 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     { "scope", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
 351       "Limit scope of operation to specific section of CIB\n"
 352       INDENT "Valid values: " PCMK_XE_CONFIGURATION ", " PCMK_XE_NODES
 353       ", " PCMK_XE_RESOURCES ", " PCMK_XE_CONSTRAINTS
 354       ", " PCMK_XE_CRM_CONFIG ", " PCMK_XE_RSC_DEFAULTS ",\n"
 355       INDENT "              " PCMK_XE_OP_DEFAULTS ", " PCMK_XE_ACLS
 356       ", " PCMK_XE_FENCING_TOPOLOGY ", " PCMK_XE_TAGS ", " PCMK_XE_ALERTS
 357       ", " PCMK_XE_STATUS "\n"
 358       INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
 359       "appear takes effect",
 360       "value" },
 361 
 362     { "xpath", 'A', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
 363       "A valid XPath to use instead of --scope/-o\n"
 364       INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
 365       "appear takes effect",
 366       "value" },
 367 
 368     { "node-path", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 369       &options.get_node_path,
 370       "When performing XPath queries, return paths of any matches found\n"
 371       INDENT "(for example, "
 372       "\"/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
 373       "/" PCMK_XE_RESOURCES "/" PCMK_XE_CLONE
 374       "[@" PCMK_XA_ID "='dummy-clone']"
 375       "/" PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "='dummy']\")",
 376       NULL },
 377 
 378     { "show-access", 'S', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
 379       show_access_cb,
 380       "Whether to use syntax highlighting for ACLs (with -Q/--query and "
 381       "-U/--user)\n"
 382       INDENT "Allowed values: 'color' (default for terminal), 'text' (plain text, "
 383       "default for non-terminal),\n"
 384       INDENT "                'namespace', or 'auto' (use default value)\n"
 385       INDENT "Default value: 'auto'",
 386       "[value]" },
 387 
 388     { "score", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.score_update,
 389       "Treat new attribute values as atomic score updates where possible "
 390       "(with --modify/-M).\n"
 391 
 392       INDENT "This currently happens by default and cannot be disabled, but\n"
 393       INDENT "this default behavior is deprecated and will be removed in a\n"
 394       INDENT "future release. Set this flag if this behavior is desired.\n"
 395 
 396       INDENT "This option takes effect when updating XML attributes. For an\n"
 397       INDENT "attribute named \"name\", if the new value is \"name++\" or\n"
 398       INDENT "\"name+=X\" for some score X, the new value is set as follows:\n"
 399       INDENT "If attribute \"name\" is not already set to some value in\n"
 400       INDENT "the element being updated, the new value is set as a literal\n"
 401       INDENT "string.\n"
 402       INDENT "If the new value is \"name++\", then the attribute is set to \n"
 403       INDENT "its existing value (parsed as a score) plus 1.\n"
 404       INDENT "If the new value is \"name+=X\" for some score X, then the\n"
 405       INDENT "attribute is set to its existing value plus X, where the\n"
 406       INDENT "existing value and X are parsed and added as scores.\n"
 407 
 408       INDENT "Scores are integer values capped at INFINITY and -INFINITY.\n"
 409       INDENT "Refer to Pacemaker Explained for more details on scores,\n"
 410       INDENT "including how they are parsed and added.",
 411       NULL },
 412 
 413     { "allow-create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 414       &options.allow_create,
 415       "(Advanced) Allow target of --modify/-M to be created if it does not "
 416       "exist",
 417       NULL },
 418 
 419     { "no-children", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 420       &options.no_children,
 421       "(Advanced) When querying an object, do not include its children in the "
 422       "result",
 423       NULL },
 424 
 425     { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.dest_node,
 426       "(Advanced) Send command to the specified host", "value" },
 427 
 428     // @COMPAT Deprecated since 3.0.0
 429     { "local", 'l', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.local,
 430       "(deprecated)", NULL },
 431 
 432     { NULL }
 433 };
 434 
 435 static GOptionContext *
 436 build_arg_context(pcmk__common_args_t *args)
     /* [previous][next][first][last][top][bottom][index][help] */
 437 {
 438     const char *desc = NULL;
 439     GOptionContext *context = NULL;
 440 
 441     GOptionEntry extra_prog_entries[] = {
 442         // @COMPAT: Deprecated
 443         { "extended-version", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
 444           &options.extended_version, "deprecated", NULL },
 445 
 446         { NULL }
 447     };
 448 
 449     desc = "Examples:\n\n"
 450            "Query the configuration:\n\n"
 451            "\t# cibadmin --query\n\n"
 452            "Query just the cluster options configuration:\n\n"
 453            "\t# cibadmin --query --scope " PCMK_XE_CRM_CONFIG "\n\n"
 454            "Query all '" PCMK_META_TARGET_ROLE "' settings:\n\n"
 455            "\t# cibadmin --query --xpath "
 456                "\"//" PCMK_XE_NVPAIR
 457                "[@" PCMK_XA_NAME "='" PCMK_META_TARGET_ROLE"']\"\n\n"
 458            "Remove all '" PCMK_META_IS_MANAGED "' settings:\n\n"
 459            "\t# cibadmin --delete-all --xpath "
 460                "\"//" PCMK_XE_NVPAIR
 461                "[@" PCMK_XA_NAME "='" PCMK_META_IS_MANAGED "']\"\n\n"
 462            "Remove the resource named 'old':\n\n"
 463            "\t# cibadmin --delete --xml-text "
 464                "'<" PCMK_XE_PRIMITIVE " " PCMK_XA_ID "=\"old\"/>'\n\n"
 465            "Remove all resources from the configuration:\n\n"
 466            "\t# cibadmin --replace --scope " PCMK_XE_RESOURCES
 467                " --xml-text '<" PCMK_XE_RESOURCES "/>'\n\n"
 468            "Replace complete configuration with contents of "
 469                "$HOME/pacemaker.xml:\n\n"
 470            "\t# cibadmin --replace --xml-file $HOME/pacemaker.xml\n\n"
 471            "Replace " PCMK_XE_CONSTRAINTS " section of configuration with "
 472                "contents of $HOME/constraints.xml:\n\n"
 473            "\t# cibadmin --replace --scope " PCMK_XE_CONSTRAINTS
 474                " --xml-file $HOME/constraints.xml\n\n"
 475            "Increase configuration version to prevent old configurations from "
 476                "being loaded accidentally:\n\n"
 477            "\t# cibadmin --modify --score --xml-text "
 478                "'<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH
 479                    "=\"" PCMK_XA_ADMIN_EPOCH "++\"/>'\n\n"
 480            "Edit the configuration with your favorite $EDITOR:\n\n"
 481            "\t# cibadmin --query > $HOME/local.xml\n\n"
 482            "\t# $EDITOR $HOME/local.xml\n\n"
 483            "\t# cibadmin --replace --xml-file $HOME/local.xml\n\n"
 484            "Assuming terminal, render configuration in color (green for "
 485                "writable, blue for readable, red for\n"
 486                "denied) to visualize permissions for user tony:\n\n"
 487            "\t# cibadmin --show-access=color --query --user tony | less -r\n\n"
 488            "SEE ALSO:\n"
 489            " crm(8), pcs(8), crm_shadow(8), crm_diff(8)\n";
 490 
 491     context = pcmk__build_arg_context(args, NULL, NULL, "<command>");
 492     g_option_context_set_description(context, desc);
 493 
 494     pcmk__add_main_args(context, extra_prog_entries);
 495 
 496     pcmk__add_arg_group(context, "commands", "Commands:", "Show command help",
 497                         command_entries);
 498     pcmk__add_arg_group(context, "data", "Data:", "Show data help",
 499                         data_entries);
 500     pcmk__add_arg_group(context, "additional", "Additional Options:",
 501                         "Show additional options", addl_entries);
 502     return context;
 503 }
 504 
 505 int
 506 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 507 {
 508     int rc = pcmk_rc_ok;
 509     const char *source = NULL;
 510     xmlNode *output = NULL;
 511     xmlNode *input = NULL;
 512     gchar *acl_cred = NULL;
 513 
 514     GError *error = NULL;
 515 
 516     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 517     gchar **processed_args = pcmk__cmdline_preproc(argv, "ANSUXhotx");
 518     GOptionContext *context = build_arg_context(args);
 519 
 520     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 521         exit_code = CRM_EX_USAGE;
 522         goto done;
 523     }
 524 
 525     if (g_strv_length(processed_args) > 1) {
 526         gchar *help = g_option_context_get_help(context, TRUE, NULL);
 527         GString *extra = g_string_sized_new(128);
 528 
 529         for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
 530             if (extra->len > 0) {
 531                 g_string_append_c(extra, ' ');
 532             }
 533             g_string_append(extra, processed_args[lpc]);
 534         }
 535 
 536         exit_code = CRM_EX_USAGE;
 537         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 538                     "non-option ARGV-elements: %s\n\n%s", extra->str, help);
 539         g_free(help);
 540         g_string_free(extra, TRUE);
 541         goto done;
 542     }
 543 
 544     if (args->version || options.extended_version) {
 545         g_strfreev(processed_args);
 546         pcmk__free_arg_context(context);
 547 
 548         /* FIXME: When cibadmin is converted to use formatted output, this can
 549          * be replaced by out->version with the appropriate boolean flag.
 550          *
 551          * options.extended_version is deprecated and will be removed in a
 552          * future release.
 553          */
 554         pcmk__cli_help(options.extended_version? '!' : 'v');
 555     }
 556 
 557     /* At LOG_ERR, stderr for CIB calls is rather verbose. Several lines like
 558      *
 559      * (func@file:line)      error: CIB <op> failures   <XML>
 560      *
 561      * In cibadmin we explicitly output the XML portion without the prefixes. So
 562      * we default to LOG_CRIT.
 563      */
 564     pcmk__cli_init_logging("cibadmin", 0);
 565     set_crm_log_level(LOG_CRIT);
 566 
 567     if (args->verbosity > 0) {
 568         cib__set_call_options(options.cmd_options, crm_system_name,
 569                               cib_verbose);
 570 
 571         for (int i = 0; i < args->verbosity; i++) {
 572             crm_bump_log_level(argc, argv);
 573         }
 574     }
 575 
 576     if (options.cib_action == NULL) {
 577         // @COMPAT: Create a default command if other tools have one
 578         gchar *help = g_option_context_get_help(context, TRUE, NULL);
 579 
 580         exit_code = CRM_EX_USAGE;
 581         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 582                     "Must specify a command option\n\n%s", help);
 583         g_free(help);
 584         goto done;
 585     }
 586 
 587     if (strcmp(options.cib_action, "empty") == 0) {
 588         // Output an empty CIB
 589         GString *buf = g_string_sized_new(1024);
 590 
 591         output = createEmptyCib(1);
 592         crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
 593 
 594         pcmk__xml_string(output, pcmk__xml_fmt_pretty, buf, 0);
 595         fprintf(stdout, "%s", buf->str);
 596         g_string_free(buf, TRUE);
 597         goto done;
 598     }
 599 
 600     if (cib_action_is_dangerous() && !options.force) {
 601         exit_code = CRM_EX_UNSAFE;
 602         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 603                     "The supplied command is considered dangerous. To prevent "
 604                     "accidental destruction of the cluster, the --force flag "
 605                     "is required in order to proceed.");
 606         goto done;
 607     }
 608 
 609     if (options.message_timeout_sec < 1) {
 610         // Set default timeout
 611         options.message_timeout_sec = 30;
 612     }
 613 
 614     if (options.section_type == cibadmin_section_xpath) {
 615         // Enable getting section by XPath
 616         cib__set_call_options(options.cmd_options, crm_system_name,
 617                               cib_xpath);
 618 
 619     } else if (options.section_type == cibadmin_section_scope) {
 620         if (!scope_is_valid(options.cib_section)) {
 621             // @COMPAT: Consider requiring --force to proceed
 622             fprintf(stderr,
 623                     "Invalid value '%s' for '--scope'. Operation will apply "
 624                     "to the entire CIB.\n", options.cib_section);
 625         }
 626     }
 627 
 628     if (options.allow_create) {
 629         // Allow target of --modify/-M to be created if it does not exist
 630         cib__set_call_options(options.cmd_options, crm_system_name,
 631                               cib_can_create);
 632     }
 633 
 634     if (options.delete_all) {
 635         // With cibadmin_section_xpath, remove all matching objects
 636         cib__set_call_options(options.cmd_options, crm_system_name,
 637                               cib_multiple);
 638     }
 639 
 640     if (options.get_node_path) {
 641         /* Enable getting node path of XPath query matches.
 642          * Meaningful only if options.section_type == cibadmin_section_xpath.
 643          */
 644         cib__set_call_options(options.cmd_options, crm_system_name,
 645                               cib_xpath_address);
 646     }
 647 
 648     if (options.no_children) {
 649         // When querying an object, don't include its children in the result
 650         cib__set_call_options(options.cmd_options, crm_system_name,
 651                               cib_no_children);
 652     }
 653 
 654     if (options.sync_call
 655         || (options.acl_render_mode != pcmk__acl_render_none)) {
 656         /* Wait for call to complete before returning.
 657          *
 658          * The ACL render modes work only with sync calls due to differences in
 659          * output handling between sync/async. It shouldn't matter to the user
 660          * whether the call is synchronous; for a CIB query, we have to wait for
 661          * the result in order to display it in any case.
 662          */
 663         cib__set_call_options(options.cmd_options, crm_system_name,
 664                               cib_sync_call);
 665     }
 666 
 667     if (options.input_file != NULL) {
 668         input = pcmk__xml_read(options.input_file);
 669         source = options.input_file;
 670 
 671     } else if (options.input_xml != NULL) {
 672         input = pcmk__xml_parse(options.input_xml);
 673         source = "input string";
 674 
 675     } else if (options.input_stdin) {
 676         input = pcmk__xml_read(NULL);
 677         source = "STDIN";
 678 
 679     } else if (options.acl_render_mode != pcmk__acl_render_none) {
 680         char *username = pcmk__uid2username(geteuid());
 681         bool required = pcmk_acl_required(username);
 682 
 683         free(username);
 684 
 685         if (required) {
 686             if (options.force) {
 687                 fprintf(stderr, "The supplied command can provide skewed"
 688                                  " result since it is run under user that also"
 689                                  " gets guarded per ACLs on their own right."
 690                                  " Continuing since --force flag was"
 691                                  " provided.\n");
 692 
 693             } else {
 694                 exit_code = CRM_EX_UNSAFE;
 695                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 696                             "The supplied command can provide skewed result "
 697                             "since it is run under user that also gets guarded "
 698                             "per ACLs in their own right. To accept the risk "
 699                             "of such a possible distortion (without even "
 700                             "knowing it at this time), use the --force flag.");
 701                 goto done;
 702             }
 703         }
 704 
 705         if (options.cib_user == NULL) {
 706             exit_code = CRM_EX_USAGE;
 707             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 708                         "The supplied command requires -U user specified.");
 709             goto done;
 710         }
 711 
 712         /* We already stopped/warned ACL-controlled users about consequences.
 713          *
 714          * Note: acl_cred takes ownership of options.cib_user here.
 715          * options.cib_user is set to NULL so that the CIB is obtained as the
 716          * user running the cibadmin command. The CIB must be obtained as a user
 717          * with full permissions in order to show the CIB correctly annotated
 718          * for the options.cib_user's permissions.
 719          */
 720         acl_cred = options.cib_user;
 721         options.cib_user = NULL;
 722     }
 723 
 724     if (input != NULL) {
 725         crm_log_xml_debug(input, "[admin input]");
 726 
 727     } else if (source != NULL) {
 728         exit_code = CRM_EX_CONFIG;
 729         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 730                     "Couldn't parse input from %s.", source);
 731         goto done;
 732     }
 733 
 734     if (pcmk__str_eq(options.cib_action, "md5-sum", pcmk__str_casei)) {
 735         char *digest = NULL;
 736 
 737         if (input == NULL) {
 738             exit_code = CRM_EX_USAGE;
 739             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 740                         "Please supply XML to process with -X, -x, or -p");
 741             goto done;
 742         }
 743 
 744         digest = pcmk__digest_on_disk_cib(input);
 745         fprintf(stderr, "Digest: ");
 746         fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
 747         free(digest);
 748         goto done;
 749 
 750     } else if (strcmp(options.cib_action, "md5-sum-versioned") == 0) {
 751         char *digest = NULL;
 752 
 753         if (input == NULL) {
 754             exit_code = CRM_EX_USAGE;
 755             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 756                         "Please supply XML to process with -X, -x, or -p");
 757             goto done;
 758         }
 759 
 760         digest = pcmk__digest_xml(input, true);
 761         fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
 762         free(digest);
 763         goto done;
 764 
 765     } else if (pcmk__str_eq(options.cib_action, PCMK__CIB_REQUEST_MODIFY,
 766                             pcmk__str_none)) {
 767         /* @COMPAT When we drop default support for expansion in cibadmin, guard
 768          * with `if (options.score_update)`
 769          */
 770         cib__set_call_options(options.cmd_options, crm_system_name,
 771                               cib_score_update);
 772     }
 773 
 774     rc = do_init();
 775     if (rc != pcmk_ok) {
 776         rc = pcmk_legacy2rc(rc);
 777         exit_code = pcmk_rc2exitc(rc);
 778 
 779         crm_err("Init failed, could not perform requested operations: %s",
 780                 pcmk_rc_str(rc));
 781         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 782                     "Init failed, could not perform requested operations: %s",
 783                     pcmk_rc_str(rc));
 784         goto done;
 785     }
 786 
 787     rc = do_work(input, &output);
 788     if (!pcmk_is_set(options.cmd_options, cib_sync_call)
 789         && (the_cib->variant != cib_file)
 790         && (rc >= 0)) {
 791         /* For async call, positive rc is the call ID (file always synchronous).
 792          *
 793          * Wait for the reply by creating a mainloop and running it until the
 794          * callbacks are invoked.
 795          */
 796         request_id = rc;
 797 
 798         the_cib->cmds->register_callback(the_cib, request_id,
 799                                          options.message_timeout_sec, FALSE,
 800                                          NULL, "cibadmin_op_callback",
 801                                          cibadmin_op_callback);
 802 
 803         mainloop = g_main_loop_new(NULL, FALSE);
 804 
 805         crm_trace("%s waiting for reply from the local CIB", crm_system_name);
 806 
 807         crm_info("Starting mainloop");
 808         g_main_loop_run(mainloop);
 809 
 810     } else {
 811         rc = pcmk_legacy2rc(rc);
 812 
 813         if ((rc == pcmk_rc_schema_unchanged)
 814             && (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0)) {
 815 
 816             report_schema_unchanged();
 817 
 818         } else if (rc != pcmk_rc_ok) {
 819             crm_err("Call failed: %s", pcmk_rc_str(rc));
 820             fprintf(stderr, "Call failed: %s\n", pcmk_rc_str(rc));
 821             exit_code = pcmk_rc2exitc(rc);
 822 
 823             if (rc == pcmk_rc_schema_validation) {
 824                 if (strcmp(options.cib_action,
 825                            PCMK__CIB_REQUEST_UPGRADE) == 0) {
 826                     xmlNode *obj = NULL;
 827 
 828                     if (the_cib->cmds->query(the_cib, NULL, &obj,
 829                                              options.cmd_options) == pcmk_ok) {
 830                         pcmk__update_schema(&obj, NULL, true, false);
 831                     }
 832                     pcmk__xml_free(obj);
 833 
 834                 } else if (output != NULL) {
 835                     // Show validation errors to stderr
 836                     pcmk__validate_xml(output, NULL, NULL, NULL);
 837                 }
 838             }
 839         }
 840     }
 841 
 842     if ((output != NULL)
 843         && (options.acl_render_mode != pcmk__acl_render_none)) {
 844 
 845         xmlDoc *acl_evaled_doc;
 846         rc = pcmk__acl_annotate_permissions(acl_cred, output->doc, &acl_evaled_doc);
 847         if (rc == pcmk_rc_ok) {
 848             xmlChar *rendered = NULL;
 849 
 850             rc = pcmk__acl_evaled_render(acl_evaled_doc,
 851                                          options.acl_render_mode, &rendered);
 852             if (rc != pcmk_rc_ok) {
 853                 exit_code = CRM_EX_CONFIG;
 854                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 855                             "Could not render evaluated access: %s",
 856                             pcmk_rc_str(rc));
 857                 goto done;
 858             }
 859             printf("%s\n", (char *) rendered);
 860             free(rendered);
 861 
 862         } else {
 863             exit_code = CRM_EX_CONFIG;
 864             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 865                         "Could not evaluate access per request (%s, error: %s)",
 866                         acl_cred, pcmk_rc_str(rc));
 867             goto done;
 868         }
 869 
 870     } else if (output != NULL) {
 871         print_xml_output(output);
 872     }
 873 
 874     crm_trace("%s exiting normally", crm_system_name);
 875 
 876 done:
 877     g_strfreev(processed_args);
 878     pcmk__free_arg_context(context);
 879 
 880     g_free(options.cib_user);
 881     g_free(options.dest_node);
 882     g_free(options.input_file);
 883     g_free(options.input_xml);
 884     free(options.cib_section);
 885     free(options.validate_with);
 886 
 887     g_free(acl_cred);
 888     pcmk__xml_free(input);
 889     pcmk__xml_free(output);
 890 
 891     rc = cib__clean_up_connection(&the_cib);
 892     if (exit_code == CRM_EX_OK) {
 893         exit_code = pcmk_rc2exitc(rc);
 894     }
 895 
 896     pcmk__output_and_clear_error(&error, NULL);
 897     crm_exit(exit_code);
 898 }
 899 
 900 static int
 901 do_work(xmlNode *input, xmlNode **output)
     /* [previous][next][first][last][top][bottom][index][help] */
 902 {
 903     /* construct the request */
 904     the_cib->call_timeout = options.message_timeout_sec;
 905     if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_REPLACE) == 0)
 906         && pcmk__xe_is(input, PCMK_XE_CIB)) {
 907         xmlNode *status = pcmk_find_cib_element(input, PCMK_XE_STATUS);
 908 
 909         if (status == NULL) {
 910             pcmk__xe_create(input, PCMK_XE_STATUS);
 911         }
 912     }
 913 
 914     crm_trace("Passing \"%s\" to variant_op...", options.cib_action);
 915     return cib_internal_op(the_cib, options.cib_action, options.dest_node,
 916                            options.cib_section, input, output,
 917                            options.cmd_options, options.cib_user);
 918 }
 919 
 920 int
 921 do_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 922 {
 923     int rc = pcmk_ok;
 924 
 925     the_cib = cib_new();
 926     rc = cib__signon_attempts(the_cib, cib_command, 5);
 927     if (rc != pcmk_ok) {
 928         crm_err("Could not connect to the CIB: %s", pcmk_strerror(rc));
 929         fprintf(stderr, "Could not connect to the CIB: %s\n",
 930                 pcmk_strerror(rc));
 931     }
 932 
 933     return rc;
 934 }
 935 
 936 void
 937 cibadmin_op_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 938 {
 939     rc = pcmk_legacy2rc(rc);
 940     exit_code = pcmk_rc2exitc(rc);
 941 
 942     if (rc == pcmk_rc_schema_unchanged) {
 943         report_schema_unchanged();
 944 
 945     } else if (rc != pcmk_rc_ok) {
 946         crm_warn("Call %s failed: %s " QB_XS " rc=%d",
 947                  options.cib_action, pcmk_rc_str(rc), rc);
 948         fprintf(stderr, "Call %s failed: %s\n",
 949                 options.cib_action, pcmk_rc_str(rc));
 950         print_xml_output(output);
 951 
 952     } else if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_QUERY) == 0)
 953                && (output == NULL)) {
 954         crm_err("Query returned no output");
 955         crm_log_xml_err(msg, "no output");
 956 
 957     } else if (output == NULL) {
 958         crm_info("Call passed");
 959 
 960     } else {
 961         crm_info("Call passed");
 962         print_xml_output(output);
 963     }
 964 
 965     if (call_id == request_id) {
 966         g_main_loop_quit(mainloop);
 967 
 968     } else {
 969         crm_info("Message was not the response we were looking for (%d vs. %d)",
 970                  call_id, request_id);
 971     }
 972 }

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