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 Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <glib.h>
13   	#include <stdbool.h>
14   	#include <unistd.h>
15   	
16   	#include <crm/crm.h>
17   	#include <crm/services.h>
18   	#include <crm/common/mainloop.h>
19   	
20   	#include <crm/pengine/status.h>
21   	#include <crm/pengine/internal.h>
22   	#include <crm/cib.h>
23   	#include <crm/cib/internal.h>
24   	#include <crm/lrmd.h>
25   	
26   	#define SUMMARY "cts-exec-helper - inject commands into the Pacemaker executor and watch for events"
27   	
28   	static int exec_call_id = 0;
29   	static gboolean start_test(gpointer user_data);
30   	static void try_connect(void);
31   	
32   	static char *key = NULL;
33   	static char *val = NULL;
34   	
35   	static struct {
36   	    int verbose;
37   	    int quiet;
38   	    guint interval_ms;
39   	    int timeout;
40   	    int start_delay;
41   	    int cancel_call_id;
42   	    gboolean no_wait;
43   	    gboolean is_running;
44   	    gboolean no_connect;
45   	    int exec_call_opts;
46   	    const char *api_call;
47   	    const char *rsc_id;
48   	    const char *provider;
49   	    const char *class;
50   	    const char *type;
51   	    const char *action;
52   	    const char *listen;
53   	    gboolean use_tls;
54   	    lrmd_key_value_t *params;
55   	} options;
56   	
57   	static gboolean
58   	interval_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
59   	    return pcmk_parse_interval_spec(optarg,
60   	                                    &options.interval_ms) == pcmk_rc_ok;
61   	}
62   	
63   	static gboolean
64   	notify_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
65   	    if (pcmk__str_any_of(option_name, "--notify-orig", "-n", NULL)) {
66   	        options.exec_call_opts = lrmd_opt_notify_orig_only;
67   	    } else if (pcmk__str_any_of(option_name, "--notify-changes", "-o", NULL)) {
68   	        options.exec_call_opts = lrmd_opt_notify_changes_only;
69   	    }
70   	
71   	    return TRUE;
72   	}
73   	
74   	static gboolean
75   	param_key_val_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
(1) Event path: Condition "pcmk__str_any_of(option_name, "--param-key", "-k", NULL)", taking false branch.
76   	    if (pcmk__str_any_of(option_name, "--param-key", "-k", NULL)) {
77   	        pcmk__str_update(&key, optarg);
(2) Event path: Condition "pcmk__str_any_of(option_name, "--param-val", "-v", NULL)", taking false branch.
78   	    } else if (pcmk__str_any_of(option_name, "--param-val", "-v", NULL)) {
79   	        pcmk__str_update(&val, optarg);
80   	    }
81   	
(3) Event path: Condition "key != NULL", taking true branch.
(4) Event path: Condition "val != NULL", taking true branch.
82   	    if (key != NULL && val != NULL) {
83   	        options.params = lrmd_key_value_add(options.params, key, val);
CID (unavailable; MK=70c090167e2571910bed83b837b87b7e) (#1 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(5) Event assign_union_field: The union field "in" of "_pp" is written.
(6) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
84   	        g_clear_pointer(&key, free);
85   	        g_clear_pointer(&val, free);
86   	    }
87   	
88   	    return TRUE;
89   	}
90   	
91   	static GOptionEntry basic_entries[] = {
92   	    { "api-call", 'c', 0, G_OPTION_ARG_STRING, &options.api_call,
93   	      "Directly relates to executor API functions",
94   	      NULL },
95   	
96   	    { "is-running", 'R', 0, G_OPTION_ARG_NONE, &options.is_running,
97   	      "Determine if a resource is registered and running",
98   	      NULL },
99   	
100  	    { "listen", 'l', 0, G_OPTION_ARG_STRING, &options.listen,
101  	      "Listen for a specific event string",
102  	      NULL },
103  	
104  	    { "no-wait", 'w', 0, G_OPTION_ARG_NONE, &options.no_wait,
105  	      "Make api call and do not wait for result",
106  	      NULL },
107  	
108  	    { "notify-changes", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb,
109  	      "Only notify client changes to recurring operations",
110  	      NULL },
111  	
112  	    { "notify-orig", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb,
113  	      "Only notify this client of the results of an API action",
114  	      NULL },
115  	
116  	    { "tls", 'S', 0, G_OPTION_ARG_NONE, &options.use_tls,
117  	      "Use TLS backend for local connection",
118  	      NULL },
119  	
120  	    { NULL }
121  	};
122  	
123  	static GOptionEntry api_call_entries[] = {
124  	    { "action", 'a', 0, G_OPTION_ARG_STRING, &options.action,
125  	      NULL, NULL },
126  	
127  	    { "cancel-call-id", 'x', 0, G_OPTION_ARG_INT, &options.cancel_call_id,
128  	      NULL, NULL },
129  	
130  	    { "class", 'C', 0, G_OPTION_ARG_STRING, &options.class,
131  	      NULL, NULL },
132  	
133  	    { "interval", 'i', 0, G_OPTION_ARG_CALLBACK, interval_cb,
134  	      NULL, NULL },
135  	
136  	    { "param-key", 'k', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb,
137  	      NULL, NULL },
138  	
139  	    { "param-val", 'v', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb,
140  	      NULL, NULL },
141  	
142  	    { "provider", 'P', 0, G_OPTION_ARG_STRING, &options.provider,
143  	      NULL, NULL },
144  	
145  	    { "rsc-id", 'r', 0, G_OPTION_ARG_STRING, &options.rsc_id,
146  	      NULL, NULL },
147  	
148  	    { "start-delay", 's', 0, G_OPTION_ARG_INT, &options.start_delay,
149  	      NULL, NULL },
150  	
151  	    { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
152  	      NULL, NULL },
153  	
154  	    { "type", 'T', 0, G_OPTION_ARG_STRING, &options.type,
155  	      NULL, NULL },
156  	
157  	    { NULL }
158  	};
159  	
160  	static GMainLoop *mainloop = NULL;
161  	static lrmd_t *lrmd_conn = NULL;
162  	
163  	static crm_exit_t
164  	test_exit(crm_exit_t exit_code)
165  	{
166  	    lrmd_api_delete(lrmd_conn);
167  	    return crm_exit(exit_code);
168  	}
169  	
170  	#define print_result(fmt, args...)  \
171  	    if (!options.quiet) {           \
172  	        printf(fmt "\n", ##args);   \
173  	    }
174  	
175  	static void
176  	test_shutdown(int nsig)
177  	{
178  	    lrmd_api_delete(lrmd_conn);
179  	    lrmd_conn = NULL;
180  	}
181  	
182  	static void
183  	read_events(lrmd_event_data_t * event)
184  	{
185  	    char buf[1024] = { '\0', };
186  	
187  	    pcmk__assert(snprintf(buf, sizeof(buf),
188  	                          "NEW_EVENT event_type:%s rsc_id:%s action:%s rc:%s "
189  	                          "op_status:%s",
190  	                          lrmd_event_type2str(event->type), event->rsc_id,
191  	                          pcmk__s(event->op_type, "none"),
192  	                          crm_exit_str((crm_exit_t) event->rc),
193  	                          pcmk_exec_status_str(event->op_status)) >= 0);
194  	    pcmk__info("%s", buf);
195  	
196  	    if (options.listen && pcmk__str_eq(options.listen, buf, pcmk__str_casei)) {
197  	        print_result("LISTEN EVENT SUCCESSFUL");
198  	        test_exit(CRM_EX_OK);
199  	    }
200  	
201  	    if (exec_call_id && (event->call_id == exec_call_id)) {
202  	        if (event->op_status == 0 && event->rc == 0) {
203  	            print_result("API-CALL SUCCESSFUL for 'exec'");
204  	        } else {
205  	            print_result("API-CALL FAILURE for 'exec', rc:%d lrmd_op_status:%s",
206  	                         event->rc, pcmk_exec_status_str(event->op_status));
207  	            test_exit(CRM_EX_ERROR);
208  	        }
209  	
210  	        if (!options.listen) {
211  	            test_exit(CRM_EX_OK);
212  	        }
213  	    }
214  	}
215  	
216  	static gboolean
217  	timeout_err(gpointer data)
218  	{
219  	    print_result("LISTEN EVENT FAILURE - timeout occurred, never found");
220  	    test_exit(CRM_EX_TIMEOUT);
221  	    return FALSE;
222  	}
223  	
224  	static void
225  	connection_events(lrmd_event_data_t * event)
226  	{
227  	    int rc = event->connection_rc;
228  	
229  	    if (event->type != lrmd_event_connect) {
230  	        /* ignore */
231  	        return;
232  	    }
233  	
234  	    if (!rc) {
235  	        pcmk__info("Executor client connection established");
236  	        start_test(NULL);
237  	        return;
238  	    } else {
239  	        sleep(1);
240  	        try_connect();
241  	        pcmk__notice("Executor client connection failed");
242  	    }
243  	}
244  	
245  	static void
246  	try_connect(void)
247  	{
248  	    int tries = 10;
249  	    static int num_tries = 0;
250  	    int rc = 0;
251  	
252  	    lrmd_conn->cmds->set_callback(lrmd_conn, connection_events);
253  	    for (; num_tries < tries; num_tries++) {
254  	        rc = lrmd_conn->cmds->connect_async(lrmd_conn, crm_system_name, 3000);
255  	
256  	        if (!rc) {
257  	            return;             /* we'll hear back in async callback */
258  	        }
259  	        sleep(1);
260  	    }
261  	
262  	    print_result("API CONNECTION FAILURE");
263  	    test_exit(CRM_EX_ERROR);
264  	}
265  	
266  	static gboolean
267  	start_test(gpointer user_data)
268  	{
269  	    int rc = 0;
270  	
271  	    if (!options.no_connect) {
272  	        if (!lrmd_conn->cmds->is_connected(lrmd_conn)) {
273  	            try_connect();
274  	            /* async connect -- this function will get called back into */
275  	            return 0;
276  	        }
277  	    }
278  	    lrmd_conn->cmds->set_callback(lrmd_conn, read_events);
279  	
280  	    if (options.timeout) {
281  	        pcmk__create_timer(options.timeout, timeout_err, NULL);
282  	    }
283  	
284  	    if (!options.api_call) {
285  	        return 0;
286  	    }
287  	
288  	    if (pcmk__str_eq(options.api_call, "exec", pcmk__str_casei)) {
289  	        rc = lrmd_conn->cmds->exec(lrmd_conn,
290  	                                   options.rsc_id,
291  	                                   options.action,
292  	                                   NULL,
293  	                                   options.interval_ms,
294  	                                   options.timeout,
295  	                                   options.start_delay,
296  	                                   options.exec_call_opts,
297  	                                   options.params);
298  	
299  	        if (rc > 0) {
300  	            exec_call_id = rc;
301  	            print_result("API-CALL 'exec' action pending, waiting on response");
302  	        }
303  	
304  	    } else if (pcmk__str_eq(options.api_call, "register_rsc", pcmk__str_casei)) {
305  	        rc = lrmd_conn->cmds->register_rsc(lrmd_conn,
306  	                                           options.rsc_id,
307  	                                           options.class, options.provider, options.type, 0);
308  	    } else if (pcmk__str_eq(options.api_call, "get_rsc_info", pcmk__str_casei)) {
309  	        lrmd_rsc_info_t *rsc_info;
310  	
311  	        rsc_info = lrmd_conn->cmds->get_rsc_info(lrmd_conn, options.rsc_id, 0);
312  	
313  	        if (rsc_info) {
314  	            print_result("RSC_INFO: id:%s class:%s provider:%s type:%s",
315  	                         rsc_info->id, rsc_info->standard,
316  	                         (rsc_info->provider? rsc_info->provider : "<none>"),
317  	                         rsc_info->type);
318  	            lrmd_free_rsc_info(rsc_info);
319  	            rc = pcmk_ok;
320  	        } else {
321  	            rc = -1;
322  	        }
323  	    } else if (pcmk__str_eq(options.api_call, "unregister_rsc", pcmk__str_casei)) {
324  	        rc = lrmd_conn->cmds->unregister_rsc(lrmd_conn, options.rsc_id, 0);
325  	    } else if (pcmk__str_eq(options.api_call, "cancel", pcmk__str_casei)) {
326  	        rc = lrmd_conn->cmds->cancel(lrmd_conn, options.rsc_id, options.action,
327  	                                     options.interval_ms);
328  	    } else if (pcmk__str_eq(options.api_call, "metadata", pcmk__str_casei)) {
329  	        char *output = NULL;
330  	
331  	        rc = lrmd_conn->cmds->get_metadata(lrmd_conn,
332  	                                           options.class,
333  	                                           options.provider, options.type, &output, 0);
334  	        if (rc == pcmk_ok) {
335  	            print_result("%s", output);
336  	            free(output);
337  	        }
338  	    } else if (pcmk__str_eq(options.api_call, "list_agents", pcmk__str_casei)) {
339  	        lrmd_list_t *list = NULL;
340  	        lrmd_list_t *iter = NULL;
341  	
342  	        rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, options.class, options.provider);
343  	
344  	        if (rc > 0) {
345  	            print_result("%d agents found", rc);
346  	            for (iter = list; iter != NULL; iter = iter->next) {
347  	                print_result("%s", iter->val);
348  	            }
349  	            lrmd_list_freeall(list);
350  	            rc = 0;
351  	        } else {
352  	            print_result("API_CALL FAILURE - no agents found");
353  	            rc = -1;
354  	        }
355  	    } else if (pcmk__str_eq(options.api_call, "list_ocf_providers", pcmk__str_casei)) {
356  	        lrmd_list_t *list = NULL;
357  	        lrmd_list_t *iter = NULL;
358  	
359  	        rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, options.type, &list);
360  	
361  	        if (rc > 0) {
362  	            print_result("%d providers found", rc);
363  	            for (iter = list; iter != NULL; iter = iter->next) {
364  	                print_result("%s", iter->val);
365  	            }
366  	            lrmd_list_freeall(list);
367  	            rc = 0;
368  	        } else {
369  	            print_result("API_CALL FAILURE - no providers found");
370  	            rc = -1;
371  	        }
372  	
373  	    } else if (pcmk__str_eq(options.api_call, "list_standards", pcmk__str_casei)) {
374  	        lrmd_list_t *list = NULL;
375  	        lrmd_list_t *iter = NULL;
376  	
377  	        rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list);
378  	
379  	        if (rc > 0) {
380  	            print_result("%d standards found", rc);
381  	            for (iter = list; iter != NULL; iter = iter->next) {
382  	                print_result("%s", iter->val);
383  	            }
384  	            lrmd_list_freeall(list);
385  	            rc = 0;
386  	        } else {
387  	            print_result("API_CALL FAILURE - no providers found");
388  	            rc = -1;
389  	        }
390  	
391  	    } else if (pcmk__str_eq(options.api_call, "get_recurring_ops", pcmk__str_casei)) {
392  	        GList *op_list = NULL;
393  	        GList *op_item = NULL;
394  	        rc = lrmd_conn->cmds->get_recurring_ops(lrmd_conn, options.rsc_id, 0, 0,
395  	                                                &op_list);
396  	
397  	        for (op_item = op_list; op_item != NULL; op_item = op_item->next) {
398  	            lrmd_op_info_t *op_info = op_item->data;
399  	
400  	            print_result("RECURRING_OP: %s_%s_%s timeout=%sms",
401  	                         op_info->rsc_id, op_info->action,
402  	                         op_info->interval_ms_s, op_info->timeout_ms_s);
403  	            lrmd_free_op_info(op_info);
404  	        }
405  	        g_list_free(op_list);
406  	
407  	    } else if (options.api_call) {
408  	        print_result("API-CALL FAILURE unknown action '%s'", options.action);
409  	        test_exit(CRM_EX_ERROR);
410  	    }
411  	
412  	    if (rc < 0) {
413  	        print_result("API-CALL FAILURE for '%s' api_rc:%d",
414  	                     options.api_call, rc);
415  	        test_exit(CRM_EX_ERROR);
416  	    }
417  	
418  	    if (options.api_call && rc == pcmk_ok) {
419  	        print_result("API-CALL SUCCESSFUL for '%s'", options.api_call);
420  	        if (!options.listen) {
421  	            test_exit(CRM_EX_OK);
422  	        }
423  	    }
424  	
425  	    if (options.no_wait) {
426  	        /* just make the call and exit regardless of anything else. */
427  	        test_exit(CRM_EX_OK);
428  	    }
429  	
430  	    return 0;
431  	}
432  	
433  	/*!
434  	 * \internal
435  	 * \brief Generate resource parameters from CIB if none explicitly given
436  	 *
437  	 * \return Standard Pacemaker return code
438  	 */
439  	static int
440  	generate_params(void)
441  	{
442  	    int rc = pcmk_rc_ok;
443  	    pcmk_scheduler_t *scheduler = NULL;
444  	    xmlNode *cib_xml_copy = NULL;
445  	    pcmk_resource_t *rsc = NULL;
446  	    GHashTable *params = NULL;
447  	    GHashTableIter iter;
448  	    char *key = NULL;
449  	    char *value = NULL;
450  	
451  	    if (options.params != NULL) {
452  	        return pcmk_rc_ok; // User specified parameters explicitly
453  	    }
454  	
455  	    // Retrieve and update CIB
456  	    rc = cib__signon_query(NULL, NULL, &cib_xml_copy);
457  	    if (rc != pcmk_rc_ok) {
458  	        return rc;
459  	    }
460  	    rc = pcmk__update_configured_schema(&cib_xml_copy, false);
461  	    if (rc != pcmk_rc_ok) {
462  	        return rc;
463  	    }
464  	
465  	    // Calculate cluster status
466  	    scheduler = pcmk_new_scheduler();
467  	    if (scheduler == NULL) {
468  	        pcmk__crit("Could not allocate scheduler data");
469  	        return ENOMEM;
470  	    }
471  	    pcmk__set_scheduler_flags(scheduler, pcmk__sched_no_counts);
472  	    scheduler->input = cib_xml_copy;
473  	    scheduler->priv->now = crm_time_new(NULL);
474  	    cluster_status(scheduler);
475  	
476  	    // Find resource in CIB
477  	    rsc = pe_find_resource_with_flags(scheduler->priv->resources,
478  	                                      options.rsc_id,
479  	                                      pcmk_rsc_match_history
480  	                                      |pcmk_rsc_match_basename);
481  	    if (rsc == NULL) {
482  	        pcmk__err("Resource does not exist in config");
483  	        pcmk_free_scheduler(scheduler);
484  	        return EINVAL;
485  	    }
486  	
487  	    // Add resource instance parameters to options.params
488  	    params = pe_rsc_params(rsc, NULL, scheduler);
489  	    if (params != NULL) {
490  	        g_hash_table_iter_init(&iter, params);
491  	        while (g_hash_table_iter_next(&iter, (gpointer *) &key,
492  	                                      (gpointer *) &value)) {
493  	            options.params = lrmd_key_value_add(options.params, key, value);
494  	        }
495  	    }
496  	
497  	    // Add resource meta-attributes to options.params
498  	    g_hash_table_iter_init(&iter, rsc->priv->meta);
499  	    while (g_hash_table_iter_next(&iter, (gpointer *) &key,
500  	                                  (gpointer *) &value)) {
501  	        char *crm_name = crm_meta_name(key);
502  	
503  	        options.params = lrmd_key_value_add(options.params, crm_name, value);
504  	        free(crm_name);
505  	    }
506  	
507  	    pcmk_free_scheduler(scheduler);
508  	    return rc;
509  	}
510  	
511  	static GOptionContext *
512  	build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
513  	    GOptionContext *context = NULL;
514  	
515  	    context = pcmk__build_arg_context(args, NULL, group, NULL);
516  	
517  	    pcmk__add_main_args(context, basic_entries);
518  	    pcmk__add_arg_group(context, "api-call", "API Call Options:",
519  	                        "Parameters for api-call option", api_call_entries);
520  	
521  	    return context;
522  	}
523  	
524  	int
525  	main(int argc, char **argv)
526  	{
527  	    GError *error = NULL;
528  	    crm_exit_t exit_code = CRM_EX_OK;
529  	    crm_trigger_t *trig = NULL;
530  	
531  	    pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
532  	    /* Typically we'd pass all the single character options that take an argument
533  	     * as the second parameter here (and there's a bunch of those in this tool).
534  	     * However, we control how this program is called so we can just not call it
535  	     * in a way where the preprocessing ever matters.
536  	     */
537  	    gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
538  	    GOptionContext *context = build_arg_context(args, NULL);
539  	
540  	    if (!g_option_context_parse_strv(context, &processed_args, &error)) {
541  	        exit_code = CRM_EX_USAGE;
542  	        goto done;
543  	    }
544  	
545  	    /* We have to use crm_log_init here to set up the logging because there's
546  	     * different handling for daemons vs. command line programs, and
547  	     * pcmk__cli_init_logging is set up to only handle the latter.
548  	     */
549  	    crm_log_init(NULL, LOG_INFO, TRUE, (args->verbosity? TRUE : FALSE), argc,
550  	                 argv, FALSE);
551  	
552  	    for (int i = 0; i < args->verbosity; i++) {
553  	        crm_bump_log_level(argc, argv);
554  	    }
555  	
556  	    if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents",
557  	                                                "list_standards", "list_ocf_providers", NULL)) {
558  	        options.no_connect = TRUE;
559  	    }
560  	
561  	    if (options.is_running) {
562  	        int rc = pcmk_rc_ok;
563  	
564  	        if (options.rsc_id == NULL) {
565  	            exit_code = CRM_EX_USAGE;
566  	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
567  	                        "--is-running requires --rsc-id");
568  	            goto done;
569  	        }
570  	
571  	        options.interval_ms = 0;
572  	        if (options.timeout == 0) {
573  	            options.timeout = 30000;
574  	        }
575  	
576  	        rc = generate_params();
577  	        if (rc != pcmk_rc_ok) {
578  	            exit_code = pcmk_rc2exitc(rc);
579  	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
580  	                        "Can not determine resource status: "
581  	                        "unable to get parameters from CIB");
582  	            goto done;
583  	        }
584  	        options.api_call = "exec";
585  	        options.action = PCMK_ACTION_MONITOR;
586  	        options.exec_call_opts = lrmd_opt_notify_orig_only;
587  	    }
588  	
589  	    if (!options.api_call && !options.listen) {
590  	        exit_code = CRM_EX_USAGE;
591  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
592  	                    "Must specify at least one of --api-call, --listen, "
593  	                    "or --is-running");
594  	        goto done;
595  	    }
596  	
597  	    if (options.use_tls) {
598  	        lrmd_conn = lrmd_remote_api_new(NULL, "localhost", 0);
599  	    } else {
600  	        lrmd_conn = lrmd_api_new();
601  	    }
602  	    trig = mainloop_add_trigger(G_PRIORITY_HIGH, start_test, NULL);
603  	    mainloop_set_trigger(trig);
604  	    mainloop_add_signal(SIGTERM, test_shutdown);
605  	
606  	    pcmk__info("Starting");
607  	    mainloop = g_main_loop_new(NULL, FALSE);
608  	    g_main_loop_run(mainloop);
609  	
610  	done:
611  	    g_strfreev(processed_args);
612  	    pcmk__free_arg_context(context);
613  	
614  	    free(key);
615  	    free(val);
616  	
617  	    pcmk__output_and_clear_error(&error, NULL);
618  	    return test_exit(exit_code);
619  	}
620