root/tools/crm_shadow.c

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

DEFINITIONS

This source file includes following definitions.
  1. PCMK__OUTPUT_ARGS
  2. PCMK__OUTPUT_ARGS
  3. PCMK__OUTPUT_ARGS
  4. PCMK__OUTPUT_ARGS
  5. PCMK__OUTPUT_ARGS
  6. set_danger_error
  7. get_instance_from_env
  8. check_file_exists
  9. connect_real_cib
  10. query_real_cib
  11. read_xml
  12. write_shadow_file
  13. get_shadow_prompt
  14. shadow_setup
  15. shadow_teardown
  16. commit_shadow_file
  17. create_shadow_empty
  18. create_shadow_from_cib
  19. delete_shadow_file
  20. edit_shadow_file
  21. show_shadow_contents
  22. show_shadow_diff
  23. show_shadow_filename
  24. show_shadow_instance
  25. switch_shadow_instance
  26. command_cb
  27. build_arg_context
  28. main

   1 /*
   2  * Copyright 2004-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdio.h>
  13 #include <unistd.h>
  14 
  15 #include <sys/param.h>
  16 #include <crm/crm.h>
  17 #include <sys/stat.h>
  18 #include <sys/types.h>
  19 
  20 #include <stdlib.h>
  21 #include <errno.h>
  22 #include <fcntl.h>
  23 
  24 #include <crm/common/cmdline_internal.h>
  25 #include <crm/common/ipc.h>
  26 #include <crm/common/output_internal.h>
  27 #include <crm/common/xml.h>
  28 
  29 #include <crm/cib.h>
  30 #include <crm/cib/internal.h>
  31 
  32 #define SUMMARY "perform Pacemaker configuration changes in a sandbox\n\n"  \
  33                 "This command sets up an environment in which "             \
  34                 "configuration tools (cibadmin,\n"                          \
  35                 "crm_resource, etc.) work offline instead of against a "    \
  36                 "live cluster, allowing\n"                                  \
  37                 "changes to be previewed and tested for side effects."
  38 
  39 #define INDENT "                              "
  40 
  41 enum shadow_command {
  42     shadow_cmd_none = 0,
  43     shadow_cmd_which,
  44     shadow_cmd_display,
  45     shadow_cmd_diff,
  46     shadow_cmd_file,
  47     shadow_cmd_create,
  48     shadow_cmd_create_empty,
  49     shadow_cmd_commit,
  50     shadow_cmd_delete,
  51     shadow_cmd_edit,
  52     shadow_cmd_reset,
  53     shadow_cmd_switch,
  54 };
  55 
  56 /*!
  57  * \internal
  58  * \enum shadow_disp_flags
  59  * \brief Bit flags to control which fields of shadow CIB info are displayed
  60  *
  61  * \note Ignored for XML output.
  62  */
  63 enum shadow_disp_flags {
  64     shadow_disp_instance = (1 << 0),
  65     shadow_disp_file     = (1 << 1),
  66     shadow_disp_content  = (1 << 2),
  67     shadow_disp_diff     = (1 << 3),
  68 };
  69 
  70 static crm_exit_t exit_code = CRM_EX_OK;
  71 
  72 static struct {
  73     enum shadow_command cmd;
  74     int cmd_options;
  75     char *instance;
  76     gboolean force;
  77     gboolean batch;
  78     gboolean full_upload;
  79     gchar *validate_with;
  80 } options = {
  81     .cmd_options = cib_sync_call,
  82 };
  83 
  84 /*!
  85  * \internal
  86  * \brief Display an instruction to the user
  87  *
  88  * \param[in,out] out   Output object
  89  * \param[in]     args  Message-specific arguments
  90  *
  91  * \return Standard Pacemaker return code
  92  *
  93  * \note \p args should contain the following:
  94  *       -# Instructional message
  95  */
  96 PCMK__OUTPUT_ARGS("instruction", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
  97 static int
  98 instruction_default(pcmk__output_t *out, va_list args)
  99 {
 100     const char *msg = va_arg(args, const char *);
 101 
 102     if (msg == NULL) {
 103         return pcmk_rc_no_output;
 104     }
 105     return out->info(out, "%s", msg);
 106 }
 107 
 108 /*!
 109  * \internal
 110  * \brief Display an instruction to the user
 111  *
 112  * \param[in,out] out   Output object
 113  * \param[in]     args  Message-specific arguments
 114  *
 115  * \return Standard Pacemaker return code
 116  *
 117  * \note \p args should contain the following:
 118  *       -# Instructional message
 119  */
 120 PCMK__OUTPUT_ARGS("instruction", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
 121 static int
 122 instruction_xml(pcmk__output_t *out, va_list args)
 123 {
 124     const char *msg = va_arg(args, const char *);
 125 
 126     if (msg == NULL) {
 127         return pcmk_rc_no_output;
 128     }
 129     pcmk__output_create_xml_text_node(out, "instruction", msg);
 130     return pcmk_rc_ok;
 131 }
 132 
 133 /*!
 134  * \internal
 135  * \brief Display information about a shadow CIB instance
 136  *
 137  * \param[in,out] out   Output object
 138  * \param[in]     args  Message-specific arguments
 139  *
 140  * \return Standard Pacemaker return code
 141  *
 142  * \note \p args should contain the following:
 143  *       -# Instance name (can be \p NULL)
 144  *       -# Shadow file name (can be \p NULL)
 145  *       -# Shadow file content (can be \p NULL)
 146  *       -# Patchset containing the changes in the shadow CIB (can be \p NULL)
 147  *       -# Group of \p shadow_disp_flags indicating which fields to display
 148  */
 149 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
     /* [previous][next][first][last][top][bottom][index][help] */
 150                   "const xmlNode *", "enum shadow_disp_flags")
 151 static int
 152 shadow_default(pcmk__output_t *out, va_list args)
 153 {
 154     const char *instance = va_arg(args, const char *);
 155     const char *filename = va_arg(args, const char *);
 156     const xmlNode *content = va_arg(args, const xmlNode *);
 157     const xmlNode *diff = va_arg(args, const xmlNode *);
 158     enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
 159 
 160     int rc = pcmk_rc_no_output;
 161 
 162     if (pcmk_is_set(flags, shadow_disp_instance)) {
 163         rc = out->info(out, "Instance: %s", pcmk__s(instance, "<unknown>"));
 164     }
 165     if (pcmk_is_set(flags, shadow_disp_file)) {
 166         rc = out->info(out, "File name: %s", pcmk__s(filename, "<unknown>"));
 167     }
 168     if (pcmk_is_set(flags, shadow_disp_content)) {
 169         rc = out->info(out, "Content:");
 170 
 171         if (content != NULL) {
 172             GString *buf = g_string_sized_new(1024);
 173             gchar *str = NULL;
 174 
 175             pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
 176                              buf, 0);
 177 
 178             str = g_string_free(buf, FALSE);
 179             str = pcmk__trim(str);
 180             if (!pcmk__str_empty(str)) {
 181                 out->info(out, "%s", str);
 182             }
 183             g_free(str);
 184 
 185         } else {
 186             out->info(out, "<unknown>");
 187         }
 188     }
 189     if (pcmk_is_set(flags, shadow_disp_diff)) {
 190         rc = out->info(out, "Diff:");
 191 
 192         if (diff != NULL) {
 193             out->message(out, "xml-patchset", diff);
 194         } else {
 195             out->info(out, "<empty>");
 196         }
 197     }
 198 
 199     return rc;
 200 }
 201 
 202 /*!
 203  * \internal
 204  * \brief Display information about a shadow CIB instance
 205  *
 206  * \param[in,out] out   Output object
 207  * \param[in]     args  Message-specific arguments
 208  *
 209  * \return Standard Pacemaker return code
 210  *
 211  * \note \p args should contain the following:
 212  *       -# Instance name (can be \p NULL)
 213  *       -# Shadow file name (can be \p NULL)
 214  *       -# Shadow file content (can be \p NULL)
 215  *       -# Patchset containing the changes in the shadow CIB (can be \p NULL)
 216  *       -# Group of \p shadow_disp_flags indicating which fields to display
 217  */
 218 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
     /* [previous][next][first][last][top][bottom][index][help] */
 219                   "const xmlNode *", "enum shadow_disp_flags")
 220 static int
 221 shadow_text(pcmk__output_t *out, va_list args)
 222 {
 223     if (!out->is_quiet(out)) {
 224         return shadow_default(out, args);
 225 
 226     } else {
 227         const char *instance = va_arg(args, const char *);
 228         const char *filename = va_arg(args, const char *);
 229         const xmlNode *content = va_arg(args, const xmlNode *);
 230         const xmlNode *diff = va_arg(args, const xmlNode *);
 231         enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
 232 
 233         int rc = pcmk_rc_no_output;
 234         bool quiet_orig = out->quiet;
 235 
 236         /* We have to disable quiet mode for the "xml-patchset" message if we
 237          * call it, so we might as well do so for this whole section.
 238          */
 239         out->quiet = false;
 240 
 241         if (pcmk_is_set(flags, shadow_disp_instance) && (instance != NULL)) {
 242             rc = out->info(out, "%s", instance);
 243         }
 244         if (pcmk_is_set(flags, shadow_disp_file) && (filename != NULL)) {
 245             rc = out->info(out, "%s", filename);
 246         }
 247         if (pcmk_is_set(flags, shadow_disp_content) && (content != NULL)) {
 248             GString *buf = g_string_sized_new(1024);
 249             gchar *str = NULL;
 250 
 251             pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
 252                              buf, 0);
 253 
 254             str = g_string_free(buf, FALSE);
 255             str = pcmk__trim(str);
 256             rc = out->info(out, "%s", str);
 257             g_free(str);
 258         }
 259         if (pcmk_is_set(flags, shadow_disp_diff) && (diff != NULL)) {
 260             rc = out->message(out, "xml-patchset", diff);
 261         }
 262 
 263         out->quiet = quiet_orig;
 264         return rc;
 265     }
 266 }
 267 
 268 /*!
 269  * \internal
 270  * \brief Display information about a shadow CIB instance
 271  *
 272  * \param[in,out] out   Output object
 273  * \param[in]     args  Message-specific arguments
 274  *
 275  * \return Standard Pacemaker return code
 276  *
 277  * \note \p args should contain the following:
 278  *       -# Instance name (can be \p NULL)
 279  *       -# Shadow file name (can be \p NULL)
 280  *       -# Shadow file content (can be \p NULL)
 281  *       -# Patchset containing the changes in the shadow CIB (can be \p NULL)
 282  *       -# Group of \p shadow_disp_flags indicating which fields to display
 283  *          (ignored)
 284  */
 285 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
     /* [previous][next][first][last][top][bottom][index][help] */
 286                   "const xmlNode *", "enum shadow_disp_flags")
 287 static int
 288 shadow_xml(pcmk__output_t *out, va_list args)
 289 {
 290     const char *instance = va_arg(args, const char *);
 291     const char *filename = va_arg(args, const char *);
 292     const xmlNode *content = va_arg(args, const xmlNode *);
 293     const xmlNode *diff = va_arg(args, const xmlNode *);
 294     enum shadow_disp_flags flags G_GNUC_UNUSED =
 295         (enum shadow_disp_flags) va_arg(args, int);
 296 
 297     pcmk__output_xml_create_parent(out, PCMK_XE_SHADOW,
 298                                    PCMK_XA_INSTANCE, instance,
 299                                    PCMK_XA_FILE, filename,
 300                                    NULL);
 301 
 302     if (content != NULL) {
 303         GString *buf = g_string_sized_new(1024);
 304 
 305         pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf,
 306                          0);
 307 
 308         out->output_xml(out, PCMK_XE_CONTENT, buf->str);
 309         g_string_free(buf, TRUE);
 310     }
 311 
 312     if (diff != NULL) {
 313         out->message(out, "xml-patchset", diff);
 314     }
 315 
 316     pcmk__output_xml_pop_parent(out);
 317     return pcmk_rc_ok;
 318 }
 319 
 320 static const pcmk__supported_format_t formats[] = {
 321     PCMK__SUPPORTED_FORMAT_NONE,
 322     PCMK__SUPPORTED_FORMAT_TEXT,
 323     PCMK__SUPPORTED_FORMAT_XML,
 324     { NULL, NULL, NULL }
 325 };
 326 
 327 static const pcmk__message_entry_t fmt_functions[] = {
 328     { "instruction", "default", instruction_default },
 329     { "instruction", "xml", instruction_xml },
 330     { "shadow", "default", shadow_default },
 331     { "shadow", "text", shadow_text },
 332     { "shadow", "xml", shadow_xml },
 333 
 334     { NULL, NULL, NULL }
 335 };
 336 
 337 /*!
 338  * \internal
 339  * \brief Set the error when \p --force is not passed with a dangerous command
 340  *
 341  * \param[in]  reason         Why command is dangerous
 342  * \param[in]  for_shadow     If true, command is dangerous to the shadow file.
 343  *                            Otherwise, command is dangerous to the active
 344  *                            cluster.
 345  * \param[in]  show_mismatch  If true and the supplied shadow instance is not
 346  *                            the same as the active shadow instance, report
 347  *                            this
 348  * \param[out] error          Where to store error
 349  */
 350 static void
 351 set_danger_error(const char *reason, bool for_shadow, bool show_mismatch,
     /* [previous][next][first][last][top][bottom][index][help] */
 352                  GError **error)
 353 {
 354     const char *active = getenv("CIB_shadow");
 355     char *full = NULL;
 356 
 357     if (show_mismatch
 358         && !pcmk__str_eq(active, options.instance, pcmk__str_null_matches)) {
 359 
 360         full = crm_strdup_printf("%s.\nAdditionally, the supplied shadow "
 361                                  "instance (%s) is not the same as the active "
 362                                  "one (%s)",
 363                                 reason, options.instance, active);
 364         reason = full;
 365     }
 366 
 367     g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 368                 "%s%sTo prevent accidental destruction of the %s, the --force "
 369                 "flag is required in order to proceed.",
 370                 pcmk__s(reason, ""), ((reason != NULL)? ".\n" : ""),
 371                 (for_shadow? "shadow file" : "cluster"));
 372     free(full);
 373 }
 374 
 375 /*!
 376  * \internal
 377  * \brief Get the active shadow instance from the environment
 378  *
 379  * This sets \p options.instance to the value of the \p CIB_shadow env variable.
 380  *
 381  * \param[out] error  Where to store error
 382  */
 383 static int
 384 get_instance_from_env(GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 385 {
 386     int rc = pcmk_rc_ok;
 387 
 388     pcmk__str_update(&options.instance, getenv("CIB_shadow"));
 389     if (options.instance == NULL) {
 390         rc = ENXIO;
 391         exit_code = pcmk_rc2exitc(rc);
 392         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 393                     "No active shadow configuration defined");
 394     }
 395     return rc;
 396 }
 397 
 398 /*!
 399  * \internal
 400  * \brief Validate that the shadow file does or does not exist, as appropriate
 401  *
 402  * \param[in]  filename      Absolute path of shadow file
 403  * \param[in]  should_exist  Whether the shadow file is expected to exist
 404  * \param[out] error         Where to store error
 405  *
 406  * \return Standard Pacemaker return code
 407  */
 408 static int
 409 check_file_exists(const char *filename, bool should_exist, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 410 {
 411     struct stat buf;
 412 
 413     if (!should_exist && (stat(filename, &buf) == 0)) {
 414         char *reason = crm_strdup_printf("A shadow instance '%s' already "
 415                                          "exists", options.instance);
 416 
 417         exit_code = CRM_EX_CANTCREAT;
 418         set_danger_error(reason, true, false, error);
 419         free(reason);
 420         return EEXIST;
 421     }
 422 
 423     if (should_exist && (stat(filename, &buf) < 0)) {
 424         // @COMPAT: Use pcmk_rc2exitc(errno)?
 425         exit_code = CRM_EX_NOSUCH;
 426         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 427                     "Could not access shadow instance '%s': %s",
 428                     options.instance, strerror(errno));
 429         return errno;
 430     }
 431 
 432     return pcmk_rc_ok;
 433 }
 434 
 435 /*!
 436  * \internal
 437  * \brief Connect to the "real" (non-shadow) CIB
 438  *
 439  * \param[out] real_cib  Where to store CIB connection
 440  * \param[out] error     Where to store error
 441  *
 442  * \return Standard Pacemaker return code
 443  */
 444 static int
 445 connect_real_cib(cib_t **real_cib, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 446 {
 447     int rc = pcmk_rc_ok;
 448 
 449     *real_cib = cib_new_no_shadow();
 450     if (*real_cib == NULL) {
 451         rc = ENOMEM;
 452         exit_code = pcmk_rc2exitc(rc);
 453         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 454                     "Could not create a CIB connection object");
 455         return rc;
 456     }
 457 
 458     rc = cib__signon_attempts(*real_cib, crm_system_name, cib_command, 5);
 459     rc = pcmk_legacy2rc(rc);
 460     if (rc != pcmk_rc_ok) {
 461         exit_code = pcmk_rc2exitc(rc);
 462         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 463                     "Could not connect to CIB: %s", pcmk_rc_str(rc));
 464     }
 465     return rc;
 466 }
 467 
 468 /*!
 469  * \internal
 470  * \brief Query the "real" (non-shadow) CIB and store the result
 471  *
 472  * \param[out]    output    Where to store query output
 473  * \param[out]    error     Where to store error
 474  *
 475  * \return Standard Pacemaker return code
 476  */
 477 static int
 478 query_real_cib(xmlNode **output, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 479 {
 480     cib_t *real_cib = NULL;
 481     int rc = connect_real_cib(&real_cib, error);
 482 
 483     if (rc != pcmk_rc_ok) {
 484         goto done;
 485     }
 486 
 487     rc = real_cib->cmds->query(real_cib, NULL, output, options.cmd_options);
 488     rc = pcmk_legacy2rc(rc);
 489     if (rc != pcmk_rc_ok) {
 490         exit_code = pcmk_rc2exitc(rc);
 491         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 492                     "Could not query the non-shadow CIB: %s", pcmk_rc_str(rc));
 493     }
 494 
 495 done:
 496     cib_delete(real_cib);
 497     return rc;
 498 }
 499 
 500 /*!
 501  * \internal
 502  * \brief Read XML from the given file
 503  *
 504  * \param[in]  filename  Path of input file
 505  * \param[out] output    Where to store XML read from \p filename
 506  * \param[out] error     Where to store error
 507  *
 508  * \return Standard Pacemaker return code
 509  */
 510 static int
 511 read_xml(const char *filename, xmlNode **output, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 512 {
 513     int rc = pcmk_rc_ok;
 514 
 515     *output = pcmk__xml_read(filename);
 516     if (*output == NULL) {
 517         rc = pcmk_rc_no_input;
 518         exit_code = pcmk_rc2exitc(rc);
 519         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 520                     "Could not parse XML from input file '%s'", filename);
 521     }
 522     return rc;
 523 }
 524 
 525 /*!
 526  * \internal
 527  * \brief Write the shadow XML to a file
 528  *
 529  * \param[in]  xml       Shadow XML
 530  * \param[in]  filename  Name of destination file
 531  * \param[in]  reset     Whether the write is a reset (for logging only)
 532  * \param[out] error     Where to store error
 533  */
 534 static int
 535 write_shadow_file(const xmlNode *xml, const char *filename, bool reset,
     /* [previous][next][first][last][top][bottom][index][help] */
 536                   GError **error)
 537 {
 538     int rc = pcmk__xml_write_file(xml, filename, false, NULL);
 539 
 540     if (rc != pcmk_rc_ok) {
 541         exit_code = pcmk_rc2exitc(rc);
 542         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 543                     "Could not %s the shadow instance '%s': %s",
 544                     reset? "reset" : "create", options.instance,
 545                     pcmk_rc_str(rc));
 546     }
 547     return rc;
 548 }
 549 
 550 /*!
 551  * \internal
 552  * \brief Create a shell prompt based on the given shadow instance name
 553  *
 554  * \return Newly created prompt
 555  *
 556  * \note The caller is responsible for freeing the return value using \p free().
 557  */
 558 static inline char *
 559 get_shadow_prompt(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 560 {
 561     return crm_strdup_printf("shadow[%.40s] # ", options.instance);
 562 }
 563 
 564 /*!
 565  * \internal
 566  * \brief Set up environment variables for a shadow instance
 567  *
 568  * \param[in,out] out      Output object
 569  * \param[in]     do_switch  If true, switch to an existing instance (logging
 570  *                           only)
 571  * \param[out]    error      Where to store error
 572  */
 573 static void
 574 shadow_setup(pcmk__output_t *out, bool do_switch, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 575 {
 576     const char *active = getenv("CIB_shadow");
 577     const char *prompt = getenv("PS1");
 578     const char *shell = getenv("SHELL");
 579     char *new_prompt = get_shadow_prompt();
 580 
 581     if (pcmk__str_eq(active, options.instance, pcmk__str_none)
 582         && pcmk__str_eq(new_prompt, prompt, pcmk__str_none)) {
 583         // CIB_shadow and prompt environment variables are already set up
 584         goto done;
 585     }
 586 
 587     if (!options.batch && (shell != NULL)) {
 588         out->info(out, "Setting up shadow instance");
 589         setenv("PS1", new_prompt, 1);
 590         setenv("CIB_shadow", options.instance, 1);
 591 
 592         out->message(out, PCMK_XE_INSTRUCTION,
 593                      "Press Ctrl+D to exit the crm_shadow shell");
 594 
 595         if (pcmk__str_eq(shell, "(^|/)bash$", pcmk__str_regex)) {
 596             execl(shell, shell, "--norc", "--noprofile", NULL);
 597         } else {
 598             execl(shell, shell, NULL);
 599         }
 600 
 601         exit_code = pcmk_rc2exitc(errno);
 602         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 603                     "Failed to launch shell '%s': %s",
 604                     shell, pcmk_rc_str(errno));
 605 
 606     } else {
 607         char *msg = NULL;
 608         const char *prefix = "A new shadow instance was created. To begin "
 609                              "using it";
 610 
 611         if (do_switch) {
 612             prefix = "To switch to the named shadow instance";
 613         }
 614 
 615         msg = crm_strdup_printf("%s, enter the following into your shell:\n"
 616                                 "\texport CIB_shadow=%s",
 617                                 prefix, options.instance);
 618         out->message(out, "instruction", msg);
 619         free(msg);
 620     }
 621 
 622 done:
 623     free(new_prompt);
 624 }
 625 
 626 /*!
 627  * \internal
 628  * \brief Remind the user to clean up the shadow environment
 629  *
 630  * \param[in,out] out  Output object
 631  */
 632 static void
 633 shadow_teardown(pcmk__output_t *out)
     /* [previous][next][first][last][top][bottom][index][help] */
 634 {
 635     const char *active = getenv("CIB_shadow");
 636     const char *prompt = getenv("PS1");
 637 
 638     if (pcmk__str_eq(active, options.instance, pcmk__str_none)) {
 639         char *our_prompt = get_shadow_prompt();
 640 
 641         if (pcmk__str_eq(prompt, our_prompt, pcmk__str_none)) {
 642             out->message(out, "instruction",
 643                          "Press Ctrl+D to exit the crm_shadow shell");
 644 
 645         } else {
 646             out->message(out, "instruction",
 647                          "Remember to unset the CIB_shadow variable by "
 648                          "entering the following into your shell:\n"
 649                          "\tunset CIB_shadow");
 650         }
 651         free(our_prompt);
 652     }
 653 }
 654 
 655 /*!
 656  * \internal
 657  * \brief Commit the shadow file contents to the active cluster
 658  *
 659  * \param[out] error  Where to store error
 660  */
 661 static void
 662 commit_shadow_file(GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 663 {
 664     char *filename = NULL;
 665     cib_t *real_cib = NULL;
 666 
 667     xmlNodePtr input = NULL;
 668     xmlNodePtr section_xml = NULL;
 669     const char *section = NULL;
 670 
 671     int rc = pcmk_rc_ok;
 672 
 673     if (!options.force) {
 674         const char *reason = "The commit command overwrites the active cluster "
 675                              "configuration";
 676 
 677         exit_code = CRM_EX_USAGE;
 678         set_danger_error(reason, false, true, error);
 679         return;
 680     }
 681 
 682     filename = get_shadow_file(options.instance);
 683     if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
 684         goto done;
 685     }
 686 
 687     if (connect_real_cib(&real_cib, error) != pcmk_rc_ok) {
 688         goto done;
 689     }
 690 
 691     if (read_xml(filename, &input, error) != pcmk_rc_ok) {
 692         goto done;
 693     }
 694 
 695     section_xml = input;
 696 
 697     if (!options.full_upload) {
 698         section = PCMK_XE_CONFIGURATION;
 699         section_xml = pcmk__xe_first_child(input, section, NULL, NULL);
 700     }
 701 
 702     rc = real_cib->cmds->replace(real_cib, section, section_xml,
 703                                  options.cmd_options);
 704     rc = pcmk_legacy2rc(rc);
 705 
 706     if (rc != pcmk_rc_ok) {
 707         exit_code = pcmk_rc2exitc(rc);
 708         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 709                     "Could not commit shadow instance '%s' to the CIB: %s",
 710                     options.instance, pcmk_rc_str(rc));
 711     }
 712 
 713 done:
 714     free(filename);
 715     cib_delete(real_cib);
 716     free_xml(input);
 717 }
 718 
 719 /*!
 720  * \internal
 721  * \brief Create a new empty shadow instance
 722  *
 723  * \param[in,out] out    Output object
 724  * \param[out]    error  Where to store error
 725  *
 726  * \note If \p --force is given, we try to write the file regardless of whether
 727  *       it already exists.
 728  */
 729 static void
 730 create_shadow_empty(pcmk__output_t *out, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 731 {
 732     char *filename = get_shadow_file(options.instance);
 733     xmlNode *output = NULL;
 734 
 735     if (!options.force
 736         && (check_file_exists(filename, false, error) != pcmk_rc_ok)) {
 737         goto done;
 738     }
 739 
 740     output = createEmptyCib(0);
 741     crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
 742     out->info(out, "Created new %s configuration",
 743               crm_element_value(output, PCMK_XA_VALIDATE_WITH));
 744 
 745     if (write_shadow_file(output, filename, false, error) != pcmk_rc_ok) {
 746         goto done;
 747     }
 748     shadow_setup(out, false, error);
 749 
 750 done:
 751     free(filename);
 752     free_xml(output);
 753 }
 754 
 755 /*!
 756  * \internal
 757  * \brief Create a shadow instance based on the active CIB
 758  *
 759  * \param[in,out] out    Output object
 760  * \param[in]     reset  If true, overwrite the given existing shadow instance.
 761  *                       Otherwise, create a new shadow instance with the given
 762  *                       name.
 763  * \param[out]    error  Where to store error
 764  *
 765  * \note If \p --force is given, we try to write the file regardless of whether
 766  *       it already exists.
 767  */
 768 static void
 769 create_shadow_from_cib(pcmk__output_t *out, bool reset, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 770 {
 771     char *filename = get_shadow_file(options.instance);
 772     xmlNode *output = NULL;
 773 
 774     if (!options.force) {
 775         if (reset) {
 776             /* @COMPAT: Reset is dangerous to the shadow file, but to preserve
 777              * compatibility we can't require --force unless there's a mismatch.
 778              * At a compatibility break, call set_danger_error() with for_shadow
 779              * and show_mismatch set to true.
 780              */
 781             const char *local = getenv("CIB_shadow");
 782 
 783             if (!pcmk__str_eq(local, options.instance, pcmk__str_null_matches)) {
 784                 exit_code = CRM_EX_USAGE;
 785                 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 786                             "The supplied shadow instance (%s) is not the same "
 787                             "as the active one (%s).\n"
 788                             "To prevent accidental destruction of the shadow "
 789                             "file, the --force flag is required in order to "
 790                             "proceed.",
 791                             options.instance, local);
 792                 goto done;
 793             }
 794         }
 795 
 796         if (check_file_exists(filename, reset, error) != pcmk_rc_ok) {
 797             goto done;
 798         }
 799     }
 800 
 801     if (query_real_cib(&output, error) != pcmk_rc_ok) {
 802         goto done;
 803     }
 804 
 805     if (write_shadow_file(output, filename, reset, error) != pcmk_rc_ok) {
 806         goto done;
 807     }
 808     shadow_setup(out, false, error);
 809 
 810 done:
 811     free(filename);
 812     free_xml(output);
 813 }
 814 
 815 /*!
 816  * \internal
 817  * \brief Delete the shadow file
 818  *
 819  * \param[in,out] out  Output object
 820  * \param[out]    error  Where to store error
 821  */
 822 static void
 823 delete_shadow_file(pcmk__output_t *out, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 824 {
 825     char *filename = NULL;
 826 
 827     if (!options.force) {
 828         const char *reason = "The delete command removes the specified shadow "
 829                              "file";
 830 
 831         exit_code = CRM_EX_USAGE;
 832         set_danger_error(reason, true, true, error);
 833         return;
 834     }
 835 
 836     filename = get_shadow_file(options.instance);
 837 
 838     if ((unlink(filename) < 0) && (errno != ENOENT)) {
 839         exit_code = pcmk_rc2exitc(errno);
 840         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 841                     "Could not remove shadow instance '%s': %s",
 842                     options.instance, strerror(errno));
 843     } else {
 844         shadow_teardown(out);
 845     }
 846     free(filename);
 847 }
 848 
 849 /*!
 850  * \internal
 851  * \brief Open the shadow file in a text editor
 852  *
 853  * \param[out] error  Where to store error
 854  *
 855  * \note The \p EDITOR environment variable must be set.
 856  */
 857 static void
 858 edit_shadow_file(GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 859 {
 860     char *filename = NULL;
 861     const char *editor = NULL;
 862 
 863     if (get_instance_from_env(error) != pcmk_rc_ok) {
 864         return;
 865     }
 866 
 867     filename = get_shadow_file(options.instance);
 868     if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
 869         goto done;
 870     }
 871 
 872     editor = getenv("EDITOR");
 873     if (editor == NULL) {
 874         exit_code = CRM_EX_NOT_CONFIGURED;
 875         g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 876                     "No value for EDITOR defined");
 877         goto done;
 878     }
 879 
 880     execlp(editor, "--", filename, NULL);
 881     exit_code = CRM_EX_OSFILE;
 882     g_set_error(error, PCMK__EXITC_ERROR, exit_code,
 883                 "Could not invoke EDITOR (%s %s): %s",
 884                 editor, filename, strerror(errno));
 885 
 886 done:
 887     free(filename);
 888 }
 889 
 890 /*!
 891  * \internal
 892  * \brief Show the contents of the active shadow instance
 893  *
 894  * \param[in,out] out    Output object
 895  * \param[out]    error  Where to store error
 896  */
 897 static void
 898 show_shadow_contents(pcmk__output_t *out, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 899 {
 900     char *filename = NULL;
 901 
 902     if (get_instance_from_env(error) != pcmk_rc_ok) {
 903         return;
 904     }
 905 
 906     filename = get_shadow_file(options.instance);
 907 
 908     if (check_file_exists(filename, true, error) == pcmk_rc_ok) {
 909         xmlNode *output = NULL;
 910         bool quiet_orig = out->quiet;
 911 
 912         if (read_xml(filename, &output, error) != pcmk_rc_ok) {
 913             goto done;
 914         }
 915 
 916         out->quiet = true;
 917         out->message(out, "shadow",
 918                      options.instance, NULL, output, NULL, shadow_disp_content);
 919         out->quiet = quiet_orig;
 920 
 921         free_xml(output);
 922     }
 923 
 924 done:
 925     free(filename);
 926 }
 927 
 928 /*!
 929  * \internal
 930  * \brief Show the changes in the active shadow instance
 931  *
 932  * \param[in,out] out    Output object
 933  * \param[out]    error  Where to store error
 934  */
 935 static void
 936 show_shadow_diff(pcmk__output_t *out, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 937 {
 938     char *filename = NULL;
 939     xmlNodePtr old_config = NULL;
 940     xmlNodePtr new_config = NULL;
 941     xmlNodePtr diff = NULL;
 942     bool quiet_orig = out->quiet;
 943 
 944     if (get_instance_from_env(error) != pcmk_rc_ok) {
 945         return;
 946     }
 947 
 948     filename = get_shadow_file(options.instance);
 949     if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
 950         goto done;
 951     }
 952 
 953     if (query_real_cib(&old_config, error) != pcmk_rc_ok) {
 954         goto done;
 955     }
 956 
 957     if (read_xml(filename, &new_config, error) != pcmk_rc_ok) {
 958         goto done;
 959     }
 960     xml_track_changes(new_config, NULL, new_config, false);
 961     xml_calculate_changes(old_config, new_config);
 962     diff = xml_create_patchset(0, old_config, new_config, NULL, false);
 963 
 964     pcmk__log_xml_changes(LOG_INFO, new_config);
 965     xml_accept_changes(new_config);
 966 
 967     out->quiet = true;
 968     out->message(out, "shadow",
 969                  options.instance, NULL, NULL, diff, shadow_disp_diff);
 970     out->quiet = quiet_orig;
 971 
 972     if (diff != NULL) {
 973         /* @COMPAT: Exit with CRM_EX_DIGEST? This is not really an error; we
 974          * just want to indicate that there are differences (as the diff command
 975          * does).
 976          */
 977         exit_code = CRM_EX_ERROR;
 978     }
 979 
 980 done:
 981     free(filename);
 982     free_xml(old_config);
 983     free_xml(new_config);
 984     free_xml(diff);
 985 }
 986 
 987 /*!
 988  * \internal
 989  * \brief Show the absolute path of the active shadow instance
 990  *
 991  * \param[in,out] out    Output object
 992  * \param[out]    error  Where to store error
 993  */
 994 static void
 995 show_shadow_filename(pcmk__output_t *out, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
 996 {
 997     if (get_instance_from_env(error) == pcmk_rc_ok) {
 998         char *filename = get_shadow_file(options.instance);
 999         bool quiet_orig = out->quiet;
1000 
1001         out->quiet = true;
1002         out->message(out, "shadow",
1003                      options.instance, filename, NULL, NULL, shadow_disp_file);
1004         out->quiet = quiet_orig;
1005 
1006         free(filename);
1007     }
1008 }
1009 
1010 /*!
1011  * \internal
1012  * \brief Show the active shadow instance
1013  *
1014  * \param[in,out] out    Output object
1015  * \param[out]    error  Where to store error
1016  */
1017 static void
1018 show_shadow_instance(pcmk__output_t *out, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
1019 {
1020     if (get_instance_from_env(error) == pcmk_rc_ok) {
1021         bool quiet_orig = out->quiet;
1022 
1023         out->quiet = true;
1024         out->message(out, "shadow",
1025                      options.instance, NULL, NULL, NULL, shadow_disp_instance);
1026         out->quiet = quiet_orig;
1027     }
1028 }
1029 
1030 /*!
1031  * \internal
1032  * \brief Switch to the given shadow instance
1033  *
1034  * \param[in,out] out    Output object
1035  * \param[out]    error  Where to store error
1036  */
1037 static void
1038 switch_shadow_instance(pcmk__output_t *out, GError **error)
     /* [previous][next][first][last][top][bottom][index][help] */
1039 {
1040     char *filename = NULL;
1041 
1042     filename = get_shadow_file(options.instance);
1043     if (check_file_exists(filename, true, error) == pcmk_rc_ok) {
1044         shadow_setup(out, true, error);
1045     }
1046     free(filename);
1047 }
1048 
1049 static gboolean
1050 command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
1051            GError **error)
1052 {
1053     if (pcmk__str_any_of(option_name, "-w", "--which", NULL)) {
1054         options.cmd = shadow_cmd_which;
1055 
1056     } else if (pcmk__str_any_of(option_name, "-p", "--display", NULL)) {
1057         options.cmd = shadow_cmd_display;
1058 
1059     } else if (pcmk__str_any_of(option_name, "-d", "--diff", NULL)) {
1060         options.cmd = shadow_cmd_diff;
1061 
1062     } else if (pcmk__str_any_of(option_name, "-F", "--file", NULL)) {
1063         options.cmd = shadow_cmd_file;
1064 
1065     } else if (pcmk__str_any_of(option_name, "-c", "--create", NULL)) {
1066         options.cmd = shadow_cmd_create;
1067 
1068     } else if (pcmk__str_any_of(option_name, "-e", "--create-empty", NULL)) {
1069         options.cmd = shadow_cmd_create_empty;
1070 
1071     } else if (pcmk__str_any_of(option_name, "-C", "--commit", NULL)) {
1072         options.cmd = shadow_cmd_commit;
1073 
1074     } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
1075         options.cmd = shadow_cmd_delete;
1076 
1077     } else if (pcmk__str_any_of(option_name, "-E", "--edit", NULL)) {
1078         options.cmd = shadow_cmd_edit;
1079 
1080     } else if (pcmk__str_any_of(option_name, "-r", "--reset", NULL)) {
1081         options.cmd = shadow_cmd_reset;
1082 
1083     } else if (pcmk__str_any_of(option_name, "-s", "--switch", NULL)) {
1084         options.cmd = shadow_cmd_switch;
1085 
1086     } else {
1087         // Should be impossible
1088         return FALSE;
1089     }
1090 
1091     // optarg may be NULL and that's okay
1092     pcmk__str_update(&options.instance, optarg);
1093     return TRUE;
1094 }
1095 
1096 static GOptionEntry query_entries[] = {
1097     { "which", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1098       "Indicate the active shadow copy", NULL },
1099 
1100     { "display", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1101       "Display the contents of the active shadow copy", NULL },
1102 
1103     { "diff", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1104       "Display the changes in the active shadow copy", NULL },
1105 
1106     { "file", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1107       "Display the location of the active shadow copy file", NULL },
1108 
1109     { NULL }
1110 };
1111 
1112 static GOptionEntry command_entries[] = {
1113     { "create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1114       "Create the named shadow copy of the active cluster configuration",
1115       "name" },
1116 
1117     { "create-empty", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
1118       command_cb,
1119       "Create the named shadow copy with an empty cluster configuration.\n"
1120       INDENT "Optional: --validate-with", "name" },
1121 
1122     { "commit", 'C', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1123       "Upload the contents of the named shadow copy to the cluster", "name" },
1124 
1125     { "delete", 'D', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1126       "Delete the contents of the named shadow copy", "name" },
1127 
1128     { "edit", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1129       "Edit the contents of the active shadow copy with your favorite $EDITOR",
1130       NULL },
1131 
1132     { "reset", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1133       "Recreate named shadow copy from the active cluster configuration",
1134       "name" },
1135 
1136     { "switch", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1137       "(Advanced) Switch to the named shadow copy", "name" },
1138 
1139     { NULL }
1140 };
1141 
1142 static GOptionEntry addl_entries[] = {
1143     { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
1144       "(Advanced) Force the action to be performed", NULL },
1145 
1146     { "batch", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.batch,
1147       "(Advanced) Don't spawn a new shell", NULL },
1148 
1149     { "all", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.full_upload,
1150       "(Advanced) Upload entire CIB, including status, with --commit", NULL },
1151 
1152     { "validate-with", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
1153       &options.validate_with,
1154       "(Advanced) Create an older configuration version", NULL },
1155 
1156     { NULL }
1157 };
1158 
1159 static GOptionContext *
1160 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
     /* [previous][next][first][last][top][bottom][index][help] */
1161 {
1162     const char *desc = NULL;
1163     GOptionContext *context = NULL;
1164 
1165     desc = "Examples:\n\n"
1166            "Create a blank shadow configuration:\n\n"
1167            "\t# crm_shadow --create-empty myShadow\n\n"
1168            "Create a shadow configuration from the running cluster\n\n"
1169            "\t# crm_shadow --create myShadow\n\n"
1170            "Display the current shadow configuration:\n\n"
1171            "\t# crm_shadow --display\n\n"
1172            "Discard the current shadow configuration (named myShadow):\n\n"
1173            "\t# crm_shadow --delete myShadow --force\n\n"
1174            "Upload current shadow configuration (named myShadow) to running "
1175            "cluster:\n\n"
1176            "\t# crm_shadow --commit myShadow\n\n";
1177 
1178     context = pcmk__build_arg_context(args, "text (default), xml", group,
1179                                       "<query>|<command>");
1180     g_option_context_set_description(context, desc);
1181 
1182     pcmk__add_arg_group(context, "queries", "Queries:",
1183                         "Show query help", query_entries);
1184     pcmk__add_arg_group(context, "commands", "Commands:",
1185                         "Show command help", command_entries);
1186     pcmk__add_arg_group(context, "additional", "Additional Options:",
1187                         "Show additional options", addl_entries);
1188     return context;
1189 }
1190 
1191 int
1192 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
1193 {
1194     int rc = pcmk_rc_ok;
1195     pcmk__output_t *out = NULL;
1196 
1197     GError *error = NULL;
1198 
1199     GOptionGroup *output_group = NULL;
1200     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
1201     gchar **processed_args = pcmk__cmdline_preproc(argv, "CDcersv");
1202     GOptionContext *context = build_arg_context(args, &output_group);
1203 
1204     crm_log_preinit(NULL, argc, argv);
1205 
1206     pcmk__register_formats(output_group, formats);
1207 
1208     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
1209         exit_code = CRM_EX_USAGE;
1210         goto done;
1211     }
1212 
1213     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
1214     if (rc != pcmk_rc_ok) {
1215         exit_code = CRM_EX_ERROR;
1216         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1217                     "Error creating output format %s: %s", args->output_ty,
1218                     pcmk_rc_str(rc));
1219         goto done;
1220     }
1221 
1222     if (g_strv_length(processed_args) > 1) {
1223         gchar *help = g_option_context_get_help(context, TRUE, NULL);
1224         GString *extra = g_string_sized_new(128);
1225 
1226         for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
1227             if (extra->len > 0) {
1228                 g_string_append_c(extra, ' ');
1229             }
1230             g_string_append(extra, processed_args[lpc]);
1231         }
1232 
1233         exit_code = CRM_EX_USAGE;
1234         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1235                     "non-option ARGV-elements: %s\n\n%s", extra->str, help);
1236         g_free(help);
1237         g_string_free(extra, TRUE);
1238         goto done;
1239     }
1240 
1241     if (args->version) {
1242         out->version(out, false);
1243         goto done;
1244     }
1245 
1246     pcmk__register_messages(out, fmt_functions);
1247 
1248     if (options.cmd == shadow_cmd_none) {
1249         // @COMPAT: Create a default command if other tools have one
1250         gchar *help = g_option_context_get_help(context, TRUE, NULL);
1251 
1252         exit_code = CRM_EX_USAGE;
1253         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1254                     "Must specify a query or command option\n\n%s", help);
1255         g_free(help);
1256         goto done;
1257     }
1258 
1259     pcmk__cli_init_logging("crm_shadow", args->verbosity);
1260 
1261     if (args->verbosity > 0) {
1262         cib__set_call_options(options.cmd_options, crm_system_name,
1263                               cib_verbose);
1264     }
1265 
1266     // Run the command
1267     switch (options.cmd) {
1268         case shadow_cmd_commit:
1269             commit_shadow_file(&error);
1270             break;
1271         case shadow_cmd_create:
1272             create_shadow_from_cib(out, false, &error);
1273             break;
1274         case shadow_cmd_create_empty:
1275             create_shadow_empty(out, &error);
1276             break;
1277         case shadow_cmd_reset:
1278             create_shadow_from_cib(out, true, &error);
1279             break;
1280         case shadow_cmd_delete:
1281             delete_shadow_file(out, &error);
1282             break;
1283         case shadow_cmd_diff:
1284             show_shadow_diff(out, &error);
1285             break;
1286         case shadow_cmd_display:
1287             show_shadow_contents(out, &error);
1288             break;
1289         case shadow_cmd_edit:
1290             edit_shadow_file(&error);
1291             break;
1292         case shadow_cmd_file:
1293             show_shadow_filename(out, &error);
1294             break;
1295         case shadow_cmd_switch:
1296             switch_shadow_instance(out, &error);
1297             break;
1298         case shadow_cmd_which:
1299             show_shadow_instance(out, &error);
1300             break;
1301         default:
1302             // Should never reach this point
1303             break;
1304     }
1305 
1306 done:
1307     g_strfreev(processed_args);
1308     pcmk__free_arg_context(context);
1309 
1310     pcmk__output_and_clear_error(&error, out);
1311 
1312     free(options.instance);
1313     g_free(options.validate_with);
1314 
1315     if (out != NULL) {
1316         out->finish(out, exit_code, true, NULL);
1317         pcmk__output_free(out);
1318     }
1319 
1320     crm_exit(exit_code);
1321 }

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