This source file includes following definitions.
- attr_value_cb
- command_cb
- delete_attr_cb
- get_attr_cb
- grant_standby_cb
- set_attr_cb
- ticket_grant_warning
- ticket_revoke_warning
- build_arg_context
- main
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <sys/param.h>
13
14 #include <crm/crm.h>
15
16 #include <stdio.h>
17 #include <sys/types.h>
18 #include <unistd.h>
19
20 #include <stdlib.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <libgen.h>
24
25 #include <crm/common/xml.h>
26 #include <crm/common/ipc.h>
27 #include <crm/common/cmdline_internal.h>
28
29 #include <crm/cib.h>
30 #include <crm/cib/internal.h>
31 #include <crm/pengine/rules.h>
32 #include <crm/pengine/status.h>
33 #include <crm/pengine/internal.h>
34
35 #include <pacemaker-internal.h>
36
37 GError *error = NULL;
38
39 #define SUMMARY "Perform tasks related to cluster tickets\n\n" \
40 "Allows ticket attributes to be queried, modified and deleted."
41
42 struct {
43 gchar *attr_default;
44 gchar *attr_id;
45 char *attr_name;
46 char *attr_value;
47 gboolean force;
48 char *get_attr_name;
49 gboolean quiet;
50 gchar *set_name;
51 char ticket_cmd;
52 gchar *ticket_id;
53 gchar *xml_file;
54 } options = {
55 .ticket_cmd = 'S'
56 };
57
58 GList *attr_delete;
59 GHashTable *attr_set;
60 bool modified = false;
61 int cib_options = cib_sync_call;
62 static pcmk__output_t *out = NULL;
63
64 #define INDENT " "
65
66 static pcmk__supported_format_t formats[] = {
67 PCMK__SUPPORTED_FORMAT_NONE,
68 PCMK__SUPPORTED_FORMAT_TEXT,
69 PCMK__SUPPORTED_FORMAT_XML,
70 { NULL, NULL, NULL }
71 };
72
73 static gboolean
74 attr_value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
75 pcmk__str_update(&options.attr_value, optarg);
76
77 if (!options.attr_name || !options.attr_value) {
78 return TRUE;
79 }
80
81 pcmk__insert_dup(attr_set, options.attr_name, options.attr_value);
82 pcmk__str_update(&options.attr_name, NULL);
83 pcmk__str_update(&options.attr_value, NULL);
84
85 modified = true;
86
87 return TRUE;
88 }
89
90 static gboolean
91 command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
92 if (pcmk__str_any_of(option_name, "--info", "-l", NULL)) {
93 options.ticket_cmd = 'l';
94 } else if (pcmk__str_any_of(option_name, "--details", "-L", NULL)) {
95 options.ticket_cmd = 'L';
96 } else if (pcmk__str_any_of(option_name, "--raw", "-w", NULL)) {
97 options.ticket_cmd = 'w';
98 } else if (pcmk__str_any_of(option_name, "--query-xml", "-q", NULL)) {
99 options.ticket_cmd = 'q';
100 } else if (pcmk__str_any_of(option_name, "--constraints", "-c", NULL)) {
101 options.ticket_cmd = 'c';
102 } else if (pcmk__str_any_of(option_name, "--cleanup", "-C", NULL)) {
103 options.ticket_cmd = 'C';
104 }
105
106 return TRUE;
107 }
108
109 static gboolean
110 delete_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
111 attr_delete = g_list_append(attr_delete, strdup(optarg));
112 modified = true;
113 return TRUE;
114 }
115
116 static gboolean
117 get_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
118 pcmk__str_update(&options.get_attr_name, optarg);
119 options.ticket_cmd = 'G';
120 return TRUE;
121 }
122
123 static gboolean
124 grant_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
125 if (pcmk__str_any_of(option_name, "--grant", "-g", NULL)) {
126 pcmk__insert_dup(attr_set, PCMK__XA_GRANTED, PCMK_VALUE_TRUE);
127 modified = true;
128 } else if (pcmk__str_any_of(option_name, "--revoke", "-r", NULL)) {
129 pcmk__insert_dup(attr_set, PCMK__XA_GRANTED, PCMK_VALUE_FALSE);
130 modified = true;
131 } else if (pcmk__str_any_of(option_name, "--standby", "-s", NULL)) {
132 pcmk__insert_dup(attr_set, PCMK_XA_STANDBY, PCMK_VALUE_TRUE);
133 modified = true;
134 } else if (pcmk__str_any_of(option_name, "--activate", "-a", NULL)) {
135 pcmk__insert_dup(attr_set, PCMK_XA_STANDBY, PCMK_VALUE_FALSE);
136 modified = true;
137 }
138
139 return TRUE;
140 }
141
142 static gboolean
143 set_attr_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
144 pcmk__str_update(&options.attr_name, optarg);
145
146 if (!options.attr_name || !options.attr_value) {
147 return TRUE;
148 }
149
150 pcmk__insert_dup(attr_set, options.attr_name, options.attr_value);
151 pcmk__str_update(&options.attr_name, NULL);
152 pcmk__str_update(&options.attr_value, NULL);
153
154 modified = true;
155
156 return TRUE;
157 }
158
159 static GOptionEntry query_entries[] = {
160 { "info", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
161 "Display the information of ticket(s)",
162 NULL },
163
164 { "details", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
165 "Display the details of ticket(s)",
166 NULL },
167
168 { "raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
169 "Display the IDs of ticket(s)",
170 NULL },
171
172 { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
173 "Query the XML of ticket(s)",
174 NULL },
175
176 { "constraints", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
177 "Display the " PCMK_XE_RSC_TICKET " constraints that apply to ticket(s)",
178 NULL },
179
180 { NULL }
181 };
182
183 static GOptionEntry command_entries[] = {
184 { "grant", 'g', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb,
185 "Grant a ticket to this cluster site",
186 NULL },
187
188 { "revoke", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb,
189 "Revoke a ticket from this cluster site",
190 NULL },
191
192 { "standby", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb,
193 "Tell this cluster site this ticket is standby",
194 NULL },
195
196 { "activate", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, grant_standby_cb,
197 "Tell this cluster site this ticket is active",
198 NULL },
199
200 { NULL }
201 };
202
203 static GOptionEntry advanced_entries[] = {
204 { "get-attr", 'G', 0, G_OPTION_ARG_CALLBACK, get_attr_cb,
205 "Display the named attribute for a ticket",
206 "ATTRIBUTE" },
207
208 { "set-attr", 'S', 0, G_OPTION_ARG_CALLBACK, set_attr_cb,
209 "Set the named attribute for a ticket",
210 "ATTRIBUTE" },
211
212 { "delete-attr", 'D', 0, G_OPTION_ARG_CALLBACK, delete_attr_cb,
213 "Delete the named attribute for a ticket",
214 "ATTRIBUTE" },
215
216 { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
217 "Delete all state of a ticket at this cluster site",
218 NULL },
219
220 { NULL}
221 };
222
223 static GOptionEntry addl_entries[] = {
224 { "attr-value", 'v', 0, G_OPTION_ARG_CALLBACK, attr_value_cb,
225 "Attribute value to use with -S",
226 "VALUE" },
227
228 { "default", 'd', 0, G_OPTION_ARG_STRING, &options.attr_default,
229 "(Advanced) Default attribute value to display if none is found\n"
230 INDENT "(for use with -G)",
231 "VALUE" },
232
233 { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force,
234 "(Advanced) Force the action to be performed",
235 NULL },
236
237 { "ticket", 't', 0, G_OPTION_ARG_STRING, &options.ticket_id,
238 "Ticket ID",
239 "ID" },
240
241 { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.xml_file,
242 NULL,
243 NULL },
244
245 { NULL }
246 };
247
248 static GOptionEntry deprecated_entries[] = {
249 { "set-name", 'n', 0, G_OPTION_ARG_STRING, &options.set_name,
250 "(Advanced) ID of the " PCMK_XE_INSTANCE_ATTRIBUTES " object to change",
251 "ID" },
252
253 { "nvpair", 'i', 0, G_OPTION_ARG_STRING, &options.attr_id,
254 "(Advanced) ID of the nvpair object to change/delete",
255 "ID" },
256
257 { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &options.quiet,
258 "Print only the value on stdout",
259 NULL },
260
261 { NULL }
262 };
263
264 static void
265 ticket_grant_warning(gchar *ticket_id)
266 {
267 out->err(out, "This command cannot help you verify whether '%s' has "
268 "been already granted elsewhere.\n"
269 "If you really want to grant '%s' to this site now, and "
270 "you know what you are doing,\n"
271 "please specify --force.",
272 ticket_id, ticket_id);
273 }
274
275 static void
276 ticket_revoke_warning(gchar *ticket_id)
277 {
278 out->err(out, "Revoking '%s' can trigger the specified '" PCMK_XA_LOSS_POLICY
279 "'(s) relating to '%s'.\n\n"
280 "You can check that with:\n"
281 "crm_ticket --ticket %s --constraints\n\n"
282 "Otherwise before revoking '%s', you may want to make '%s'"
283 "standby with:\n"
284 "crm_ticket --ticket %s --standby\n\n"
285 "If you really want to revoke '%s' from this site now, and "
286 "you know what you are doing,\n"
287 "please specify --force.",
288 ticket_id, ticket_id, ticket_id, ticket_id, ticket_id,
289 ticket_id, ticket_id);
290 }
291
292 static GOptionContext *
293 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
294 {
295 GOptionContext *context = NULL;
296
297 const char *description = "Examples:\n\n"
298 "Display the info of tickets:\n\n"
299 "\tcrm_ticket --info\n\n"
300 "Display the detailed info of tickets:\n\n"
301 "\tcrm_ticket --details\n\n"
302 "Display the XML of 'ticketA':\n\n"
303 "\tcrm_ticket --ticket ticketA --query-xml\n\n"
304 "Display the " PCMK_XE_RSC_TICKET " constraints that apply to 'ticketA':\n\n"
305 "\tcrm_ticket --ticket ticketA --constraints\n\n"
306 "Grant 'ticketA' to this cluster site:\n\n"
307 "\tcrm_ticket --ticket ticketA --grant\n\n"
308 "Revoke 'ticketA' from this cluster site:\n\n"
309 "\tcrm_ticket --ticket ticketA --revoke\n\n"
310 "Make 'ticketA' standby (the cluster site will treat a granted\n"
311 "'ticketA' as 'standby', and the dependent resources will be\n"
312 "stopped or demoted gracefully without triggering loss-policies):\n\n"
313 "\tcrm_ticket --ticket ticketA --standby\n\n"
314 "Activate 'ticketA' from being standby:\n\n"
315 "\tcrm_ticket --ticket ticketA --activate\n\n"
316 "Get the value of the 'granted' attribute for 'ticketA':\n\n"
317 "\tcrm_ticket --ticket ticketA --get-attr granted\n\n"
318 "Set the value of the 'standby' attribute for 'ticketA':\n\n"
319 "\tcrm_ticket --ticket ticketA --set-attr standby --attr-value true\n\n"
320 "Delete the 'granted' attribute for 'ticketA':\n\n"
321 "\tcrm_ticket --ticket ticketA --delete-attr granted\n\n"
322 "Erase the operation history of 'ticketA' at this cluster site,\n"
323 "causing the cluster site to 'forget' the existing ticket state:\n\n"
324 "\tcrm_ticket --ticket ticketA --cleanup\n\n";
325
326 context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
327 g_option_context_set_description(context, description);
328
329 pcmk__add_arg_group(context, "queries", "Queries:",
330 "Show queries", query_entries);
331 pcmk__add_arg_group(context, "commands", "Commands:",
332 "Show command options", command_entries);
333 pcmk__add_arg_group(context, "advanced", "Advanced Options:",
334 "Show advanced options", advanced_entries);
335 pcmk__add_arg_group(context, "additional", "Additional Options:",
336 "Show additional options", addl_entries);
337 pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
338 "Show deprecated options", deprecated_entries);
339
340 return context;
341 }
342
343 int
344 main(int argc, char **argv)
345 {
346 pcmk_scheduler_t *scheduler = NULL;
347 xmlNode *cib_xml_copy = NULL;
348
349 cib_t *cib_conn = NULL;
350 crm_exit_t exit_code = CRM_EX_OK;
351 int rc = pcmk_rc_ok;
352
353 GOptionGroup *output_group = NULL;
354 pcmk__common_args_t *args = NULL;
355 GOptionContext *context = NULL;
356 gchar **processed_args = NULL;
357
358 attr_set = pcmk__strkey_table(free, free);
359 attr_delete = NULL;
360
361 args = pcmk__new_common_args(SUMMARY);
362 context = build_arg_context(args, &output_group);
363 processed_args = pcmk__cmdline_preproc(argv, "dintvxCDGS");
364
365 pcmk__register_formats(output_group, formats);
366 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
367 exit_code = CRM_EX_USAGE;
368 goto done;
369 }
370
371 pcmk__cli_init_logging("crm_ticket", args->verbosity);
372
373 rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
374 if (rc != pcmk_rc_ok) {
375 exit_code = pcmk_rc2exitc(rc);
376 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
377 "Error creating output format %s: %s", args->output_ty,
378 pcmk_rc_str(rc));
379 goto done;
380 }
381
382 pe__register_messages(out);
383 pcmk__register_lib_messages(out);
384
385 if (args->version) {
386 out->version(out, false);
387 goto done;
388 }
389
390 scheduler = pe_new_working_set();
391 if (scheduler == NULL) {
392 rc = errno;
393 exit_code = pcmk_rc2exitc(rc);
394 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
395 "Could not allocate scheduler data: %s", pcmk_rc_str(rc));
396 goto done;
397 }
398 pcmk__set_scheduler_flags(scheduler,
399 pcmk_sched_no_counts|pcmk_sched_no_compat);
400
401 cib_conn = cib_new();
402 if (cib_conn == NULL) {
403 exit_code = CRM_EX_DISCONNECT;
404 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to the CIB manager");
405 goto done;
406 }
407
408 rc = cib__signon_attempts(cib_conn, crm_system_name, cib_command, 5);
409 rc = pcmk_legacy2rc(rc);
410
411 if (rc != pcmk_rc_ok) {
412 exit_code = pcmk_rc2exitc(rc);
413 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to the CIB: %s",
414 pcmk_rc_str(rc));
415 goto done;
416 }
417
418 if (options.xml_file != NULL) {
419 cib_xml_copy = pcmk__xml_read(options.xml_file);
420
421 } else {
422 rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
423 rc = pcmk_legacy2rc(rc);
424
425 if (rc != pcmk_rc_ok) {
426 exit_code = pcmk_rc2exitc(rc);
427 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not get local CIB: %s",
428 pcmk_rc_str(rc));
429 goto done;
430 }
431 }
432
433 rc = pcmk__update_configured_schema(&cib_xml_copy, false);
434 if (rc != pcmk_rc_ok) {
435 exit_code = pcmk_rc2exitc(rc);
436 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
437 "Could not update local CIB to latest schema version");
438 goto done;
439 }
440
441 scheduler->input = cib_xml_copy;
442 scheduler->now = crm_time_new(NULL);
443
444 cluster_status(scheduler);
445
446
447
448
449 pcmk__unpack_constraints(scheduler);
450
451 if (options.ticket_cmd == 'l' || options.ticket_cmd == 'L' || options.ticket_cmd == 'w') {
452 bool raw = false;
453 bool details = false;
454
455 if (options.ticket_cmd == 'L') {
456 details = true;
457 } else if (options.ticket_cmd == 'w') {
458 raw = true;
459 }
460
461 rc = pcmk__ticket_info(out, scheduler, options.ticket_id, details, raw);
462 exit_code = pcmk_rc2exitc(rc);
463
464 if (rc == ENXIO) {
465 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
466 "No such ticket '%s'", options.ticket_id);
467 } else if (rc != pcmk_rc_ok) {
468 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
469 "Could not get ticket info: %s", pcmk_rc_str(rc));
470 }
471
472 } else if (options.ticket_cmd == 'q') {
473 rc = pcmk__ticket_state(out, cib_conn, options.ticket_id);
474
475 if (rc != pcmk_rc_ok && rc != pcmk_rc_duplicate_id) {
476 exit_code = pcmk_rc2exitc(rc);
477 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
478 "Could not query ticket XML: %s", pcmk_rc_str(rc));
479 } else {
480 exit_code = CRM_EX_OK;
481 }
482
483 } else if (options.ticket_cmd == 'c') {
484 rc = pcmk__ticket_constraints(out, cib_conn, options.ticket_id);
485 exit_code = pcmk_rc2exitc(rc);
486
487 if (rc != pcmk_rc_ok) {
488 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
489 "Could not show ticket constraints: %s", pcmk_rc_str(rc));
490 }
491
492 } else if (options.ticket_cmd == 'G') {
493 if (options.ticket_id == NULL) {
494 exit_code = CRM_EX_NOSUCH;
495 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
496 "Must supply ticket ID with -t");
497 goto done;
498 }
499
500 rc = pcmk__ticket_get_attr(out, scheduler, options.ticket_id,
501 options.get_attr_name, options.attr_default);
502 exit_code = pcmk_rc2exitc(rc);
503
504 } else if (options.ticket_cmd == 'C') {
505 if (options.ticket_id == NULL) {
506 exit_code = CRM_EX_USAGE;
507 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
508 "Must supply ticket ID with -t");
509 goto done;
510 }
511
512 rc = pcmk__ticket_delete(out, cib_conn, scheduler, options.ticket_id,
513 options.force);
514 exit_code = pcmk_rc2exitc(rc);
515
516 switch (rc) {
517 case ENXIO:
518 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
519 "No such ticket '%s'", options.ticket_id);
520 break;
521
522 case EACCES:
523 ticket_revoke_warning(options.ticket_id);
524 break;
525
526 case pcmk_rc_ok:
527 case pcmk_rc_duplicate_id:
528 break;
529
530 default:
531 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
532 "Could not clean up ticket: %s", pcmk_rc_str(rc));
533 break;
534 }
535
536 } else if (modified) {
537 if (options.ticket_id == NULL) {
538 exit_code = CRM_EX_USAGE;
539 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
540 "Must supply ticket ID with -t");
541 goto done;
542 }
543
544 if (options.attr_value
545 && (pcmk__str_empty(options.attr_name))) {
546 exit_code = CRM_EX_USAGE;
547 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
548 "Must supply attribute name with -S for -v %s", options.attr_value);
549 goto done;
550 }
551
552 if (options.attr_name
553 && (pcmk__str_empty(options.attr_value))) {
554 exit_code = CRM_EX_USAGE;
555 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
556 "Must supply attribute value with -v for -S %s", options.attr_value);
557 goto done;
558 }
559
560 if (attr_delete != NULL) {
561 rc = pcmk__ticket_remove_attr(out, cib_conn, scheduler, options.ticket_id,
562 attr_delete, options.force);
563
564 if (rc == EACCES) {
565 ticket_revoke_warning(options.ticket_id);
566 exit_code = pcmk_rc2exitc(rc);
567 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
568 "Ticket modification not allowed without --force");
569 }
570 } else {
571 rc = pcmk__ticket_set_attr(out, cib_conn, scheduler, options.ticket_id,
572 attr_set, options.force);
573
574 if (rc == EACCES) {
575 const char *value = NULL;
576
577 value = g_hash_table_lookup(attr_set, PCMK__XA_GRANTED);
578 if (crm_is_true(value)) {
579 ticket_grant_warning(options.ticket_id);
580 } else {
581 ticket_revoke_warning(options.ticket_id);
582 }
583
584 exit_code = pcmk_rc2exitc(rc);
585 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
586 "Ticket modification not allowed without --force");
587 }
588 }
589
590 exit_code = pcmk_rc2exitc(rc);
591
592 if (rc != pcmk_rc_ok && error == NULL) {
593 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
594 "Could not modify ticket: %s", pcmk_rc_str(rc));
595 }
596
597 } else if (options.ticket_cmd == 'S') {
598
599
600
601
602 if (pcmk__str_empty(options.attr_name)) {
603
604 exit_code = CRM_EX_USAGE;
605 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must supply a command");
606 goto done;
607 }
608
609 if (options.ticket_id == NULL) {
610 exit_code = CRM_EX_USAGE;
611 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
612 "Must supply ticket ID with -t");
613 goto done;
614 }
615
616 if (pcmk__str_empty(options.attr_value)) {
617 exit_code = CRM_EX_USAGE;
618 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
619 "Must supply value with -v for -S %s", options.attr_name);
620 goto done;
621 }
622
623 } else {
624 exit_code = CRM_EX_USAGE;
625 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
626 "Unknown command: %c", options.ticket_cmd);
627 }
628
629 done:
630 if (attr_set) {
631 g_hash_table_destroy(attr_set);
632 }
633 attr_set = NULL;
634
635 if (attr_delete) {
636 g_list_free_full(attr_delete, free);
637 }
638 attr_delete = NULL;
639
640 pe_free_working_set(scheduler);
641 scheduler = NULL;
642
643 cib__clean_up_connection(&cib_conn);
644
645 g_strfreev(processed_args);
646 pcmk__free_arg_context(context);
647 g_free(options.attr_default);
648 g_free(options.attr_id);
649 free(options.attr_name);
650 free(options.attr_value);
651 free(options.get_attr_name);
652 g_free(options.set_name);
653 g_free(options.ticket_id);
654 g_free(options.xml_file);
655
656 pcmk__output_and_clear_error(&error, out);
657
658 if (out != NULL) {
659 out->finish(out, exit_code, true, NULL);
660 pcmk__output_free(out);
661 }
662
663 pcmk__unregister_formats();
664 crm_exit(exit_code);
665 }