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