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  	    GHashTable *meta = NULL;
448  	    GHashTableIter iter;
449  	    char *key = NULL;
450  	    char *value = NULL;
451  	
452  	    if (options.params != NULL) {
453  	        return pcmk_rc_ok; // User specified parameters explicitly
454  	    }
455  	
456  	    // Retrieve and update CIB
457  	    rc = cib__signon_query(NULL, NULL, &cib_xml_copy);
458  	    if (rc != pcmk_rc_ok) {
459  	        return rc;
460  	    }
461  	    rc = pcmk__update_configured_schema(&cib_xml_copy, false);
462  	    if (rc != pcmk_rc_ok) {
463  	        return rc;
464  	    }
465  	
466  	    // Calculate cluster status
467  	    scheduler = pcmk_new_scheduler();
468  	    if (scheduler == NULL) {
469  	        pcmk__crit("Could not allocate scheduler data");
470  	        return ENOMEM;
471  	    }
472  	    pcmk__set_scheduler_flags(scheduler, pcmk__sched_no_counts);
473  	    scheduler->input = cib_xml_copy;
474  	    scheduler->priv->now = crm_time_new(NULL);
475  	    cluster_status(scheduler);
476  	
477  	    // Find resource in CIB
478  	    rsc = pe_find_resource_with_flags(scheduler->priv->resources,
479  	                                      options.rsc_id,
480  	                                      pcmk_rsc_match_history
481  	                                      |pcmk_rsc_match_basename);
482  	    if (rsc == NULL) {
483  	        pcmk__err("Resource does not exist in config");
484  	        pcmk_free_scheduler(scheduler);
485  	        return EINVAL;
486  	    }
487  	
488  	    // Add resource instance parameters to options.params
489  	    params = pe_rsc_params(rsc, NULL, scheduler);
490  	    if (params != NULL) {
491  	        g_hash_table_iter_init(&iter, params);
492  	        while (g_hash_table_iter_next(&iter, (gpointer *) &key,
493  	                                      (gpointer *) &value)) {
494  	            options.params = lrmd_key_value_add(options.params, key, value);
495  	        }
496  	    }
497  	
498  	    // Add resource meta-attributes to options.params
499  	    meta = pcmk__strkey_table(free, free);
500  	    get_meta_attributes(meta, rsc, NULL, scheduler);
501  	    g_hash_table_iter_init(&iter, meta);
502  	    while (g_hash_table_iter_next(&iter, (gpointer *) &key,
503  	                                  (gpointer *) &value)) {
504  	        char *crm_name = crm_meta_name(key);
505  	
506  	        options.params = lrmd_key_value_add(options.params, crm_name, value);
507  	        free(crm_name);
508  	    }
509  	    g_hash_table_destroy(meta);
510  	
511  	    pcmk_free_scheduler(scheduler);
512  	    return rc;
513  	}
514  	
515  	static GOptionContext *
516  	build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
517  	    GOptionContext *context = NULL;
518  	
519  	    context = pcmk__build_arg_context(args, NULL, group, NULL);
520  	
521  	    pcmk__add_main_args(context, basic_entries);
522  	    pcmk__add_arg_group(context, "api-call", "API Call Options:",
523  	                        "Parameters for api-call option", api_call_entries);
524  	
525  	    return context;
526  	}
527  	
528  	int
529  	main(int argc, char **argv)
530  	{
531  	    GError *error = NULL;
532  	    crm_exit_t exit_code = CRM_EX_OK;
533  	    crm_trigger_t *trig = NULL;
534  	
535  	    pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
536  	    /* Typically we'd pass all the single character options that take an argument
537  	     * as the second parameter here (and there's a bunch of those in this tool).
538  	     * However, we control how this program is called so we can just not call it
539  	     * in a way where the preprocessing ever matters.
540  	     */
541  	    gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
542  	    GOptionContext *context = build_arg_context(args, NULL);
543  	
544  	    if (!g_option_context_parse_strv(context, &processed_args, &error)) {
545  	        exit_code = CRM_EX_USAGE;
546  	        goto done;
547  	    }
548  	
549  	    /* We have to use crm_log_init here to set up the logging because there's
550  	     * different handling for daemons vs. command line programs, and
551  	     * pcmk__cli_init_logging is set up to only handle the latter.
552  	     */
553  	    crm_log_init(NULL, LOG_INFO, TRUE, (args->verbosity? TRUE : FALSE), argc,
554  	                 argv, FALSE);
555  	
556  	    for (int i = 0; i < args->verbosity; i++) {
557  	        crm_bump_log_level(argc, argv);
558  	    }
559  	
560  	    if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents",
561  	                                                "list_standards", "list_ocf_providers", NULL)) {
562  	        options.no_connect = TRUE;
563  	    }
564  	
565  	    if (options.is_running) {
566  	        int rc = pcmk_rc_ok;
567  	
568  	        if (options.rsc_id == NULL) {
569  	            exit_code = CRM_EX_USAGE;
570  	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
571  	                        "--is-running requires --rsc-id");
572  	            goto done;
573  	        }
574  	
575  	        options.interval_ms = 0;
576  	        if (options.timeout == 0) {
577  	            options.timeout = 30000;
578  	        }
579  	
580  	        rc = generate_params();
581  	        if (rc != pcmk_rc_ok) {
582  	            exit_code = pcmk_rc2exitc(rc);
583  	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
584  	                        "Can not determine resource status: "
585  	                        "unable to get parameters from CIB");
586  	            goto done;
587  	        }
588  	        options.api_call = "exec";
589  	        options.action = PCMK_ACTION_MONITOR;
590  	        options.exec_call_opts = lrmd_opt_notify_orig_only;
591  	    }
592  	
593  	    if (!options.api_call && !options.listen) {
594  	        exit_code = CRM_EX_USAGE;
595  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
596  	                    "Must specify at least one of --api-call, --listen, "
597  	                    "or --is-running");
598  	        goto done;
599  	    }
600  	
601  	    if (options.use_tls) {
602  	        lrmd_conn = lrmd_remote_api_new(NULL, "localhost", 0);
603  	    } else {
604  	        lrmd_conn = lrmd_api_new();
605  	    }
606  	    trig = mainloop_add_trigger(G_PRIORITY_HIGH, start_test, NULL);
607  	    mainloop_set_trigger(trig);
608  	    mainloop_add_signal(SIGTERM, test_shutdown);
609  	
610  	    pcmk__info("Starting");
611  	    mainloop = g_main_loop_new(NULL, FALSE);
612  	    g_main_loop_run(mainloop);
613  	
614  	done:
615  	    g_strfreev(processed_args);
616  	    pcmk__free_arg_context(context);
617  	
618  	    free(key);
619  	    free(val);
620  	
621  	    pcmk__output_and_clear_error(&error, NULL);
622  	    return test_exit(exit_code);
623  	}
624