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