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) {
76   	    if (pcmk__str_any_of(option_name, "--param-key", "-k", NULL)) {
77   	        pcmk__str_update(&key, optarg);
78   	    } else if (pcmk__str_any_of(option_name, "--param-val", "-v", NULL)) {
79   	        pcmk__str_update(&val, optarg);
80   	    }
81   	
82   	    if (key != NULL && val != NULL) {
83   	        options.params = lrmd_key_value_add(options.params, key, val);
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  	{
CID (unavailable; MK=d03b4f313bf3654cb70c50d6bf4e0234) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(1) Event assign_union_field: The union field "in" of "_pp" is written.
(2) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
178  	    g_clear_pointer(&lrmd_conn, lrmd_api_delete);
179  	}
180  	
181  	static void
182  	read_events(lrmd_event_data_t * event)
183  	{
184  	    char buf[1024] = { '\0', };
185  	
186  	    pcmk__assert(snprintf(buf, sizeof(buf),
187  	                          "NEW_EVENT event_type:%s rsc_id:%s action:%s rc:%s "
188  	                          "op_status:%s",
189  	                          lrmd_event_type2str(event->type), event->rsc_id,
190  	                          pcmk__s(event->op_type, "none"),
191  	                          crm_exit_str((crm_exit_t) event->rc),
192  	                          pcmk_exec_status_str(event->op_status)) >= 0);
193  	    pcmk__info("%s", buf);
194  	
195  	    if (options.listen && pcmk__str_eq(options.listen, buf, pcmk__str_casei)) {
196  	        print_result("LISTEN EVENT SUCCESSFUL");
197  	        test_exit(CRM_EX_OK);
198  	    }
199  	
200  	    if (exec_call_id && (event->call_id == exec_call_id)) {
201  	        if (event->op_status == 0 && event->rc == 0) {
202  	            print_result("API-CALL SUCCESSFUL for 'exec'");
203  	        } else {
204  	            print_result("API-CALL FAILURE for 'exec', rc:%d lrmd_op_status:%s",
205  	                         event->rc, pcmk_exec_status_str(event->op_status));
206  	            test_exit(CRM_EX_ERROR);
207  	        }
208  	
209  	        if (!options.listen) {
210  	            test_exit(CRM_EX_OK);
211  	        }
212  	    }
213  	}
214  	
215  	static gboolean
216  	timeout_err(gpointer data)
217  	{
218  	    print_result("LISTEN EVENT FAILURE - timeout occurred, never found");
219  	    test_exit(CRM_EX_TIMEOUT);
220  	    return FALSE;
221  	}
222  	
223  	static void
224  	connection_events(lrmd_event_data_t * event)
225  	{
226  	    int rc = event->connection_rc;
227  	
228  	    if (event->type != lrmd_event_connect) {
229  	        /* ignore */
230  	        return;
231  	    }
232  	
233  	    if (!rc) {
234  	        pcmk__info("Executor client connection established");
235  	        start_test(NULL);
236  	        return;
237  	    } else {
238  	        sleep(1);
239  	        try_connect();
240  	        pcmk__notice("Executor client connection failed");
241  	    }
242  	}
243  	
244  	static void
245  	try_connect(void)
246  	{
247  	    int tries = 10;
248  	    static int num_tries = 0;
249  	    int rc = 0;
250  	
251  	    lrmd_conn->cmds->set_callback(lrmd_conn, connection_events);
252  	    for (; num_tries < tries; num_tries++) {
253  	        rc = lrmd_conn->cmds->connect_async(lrmd_conn, crm_system_name, 3000);
254  	
255  	        if (!rc) {
256  	            return;             /* we'll hear back in async callback */
257  	        }
258  	        sleep(1);
259  	    }
260  	
261  	    print_result("API CONNECTION FAILURE");
262  	    test_exit(CRM_EX_ERROR);
263  	}
264  	
265  	static gboolean
266  	start_test(gpointer user_data)
267  	{
268  	    int rc = 0;
269  	
270  	    if (!options.no_connect) {
271  	        if (!lrmd_conn->cmds->is_connected(lrmd_conn)) {
272  	            try_connect();
273  	            /* async connect -- this function will get called back into */
274  	            return 0;
275  	        }
276  	    }
277  	    lrmd_conn->cmds->set_callback(lrmd_conn, read_events);
278  	
279  	    if (options.timeout) {
280  	        pcmk__create_timer(options.timeout, timeout_err, NULL);
281  	    }
282  	
283  	    if (!options.api_call) {
284  	        return 0;
285  	    }
286  	
287  	    if (pcmk__str_eq(options.api_call, "exec", pcmk__str_casei)) {
288  	        rc = lrmd_conn->cmds->exec(lrmd_conn,
289  	                                   options.rsc_id,
290  	                                   options.action,
291  	                                   NULL,
292  	                                   options.interval_ms,
293  	                                   options.timeout,
294  	                                   options.start_delay,
295  	                                   options.exec_call_opts,
296  	                                   options.params);
297  	
298  	        if (rc > 0) {
299  	            exec_call_id = rc;
300  	            print_result("API-CALL 'exec' action pending, waiting on response");
301  	        }
302  	
303  	    } else if (pcmk__str_eq(options.api_call, "register_rsc", pcmk__str_casei)) {
304  	        rc = lrmd_conn->cmds->register_rsc(lrmd_conn,
305  	                                           options.rsc_id,
306  	                                           options.class, options.provider, options.type, 0);
307  	    } else if (pcmk__str_eq(options.api_call, "get_rsc_info", pcmk__str_casei)) {
308  	        lrmd_rsc_info_t *rsc_info;
309  	
310  	        rsc_info = lrmd_conn->cmds->get_rsc_info(lrmd_conn, options.rsc_id, 0);
311  	
312  	        if (rsc_info) {
313  	            print_result("RSC_INFO: id:%s class:%s provider:%s type:%s",
314  	                         rsc_info->id, rsc_info->standard,
315  	                         (rsc_info->provider? rsc_info->provider : "<none>"),
316  	                         rsc_info->type);
317  	            lrmd_free_rsc_info(rsc_info);
318  	            rc = pcmk_ok;
319  	        } else {
320  	            rc = -1;
321  	        }
322  	    } else if (pcmk__str_eq(options.api_call, "unregister_rsc", pcmk__str_casei)) {
323  	        rc = lrmd_conn->cmds->unregister_rsc(lrmd_conn, options.rsc_id, 0);
324  	    } else if (pcmk__str_eq(options.api_call, "cancel", pcmk__str_casei)) {
325  	        rc = lrmd_conn->cmds->cancel(lrmd_conn, options.rsc_id, options.action,
326  	                                     options.interval_ms);
327  	    } else if (pcmk__str_eq(options.api_call, "metadata", pcmk__str_casei)) {
328  	        char *output = NULL;
329  	
330  	        rc = lrmd_conn->cmds->get_metadata(lrmd_conn,
331  	                                           options.class,
332  	                                           options.provider, options.type, &output, 0);
333  	        if (rc == pcmk_ok) {
334  	            print_result("%s", output);
335  	            free(output);
336  	        }
337  	    } else if (pcmk__str_eq(options.api_call, "list_agents", pcmk__str_casei)) {
338  	        lrmd_list_t *list = NULL;
339  	        lrmd_list_t *iter = NULL;
340  	
341  	        rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, options.class, options.provider);
342  	
343  	        if (rc > 0) {
344  	            print_result("%d agents found", rc);
345  	            for (iter = list; iter != NULL; iter = iter->next) {
346  	                print_result("%s", iter->val);
347  	            }
348  	            lrmd_list_freeall(list);
349  	            rc = 0;
350  	        } else {
351  	            print_result("API_CALL FAILURE - no agents found");
352  	            rc = -1;
353  	        }
354  	    } else if (pcmk__str_eq(options.api_call, "list_ocf_providers", pcmk__str_casei)) {
355  	        lrmd_list_t *list = NULL;
356  	        lrmd_list_t *iter = NULL;
357  	
358  	        rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, options.type, &list);
359  	
360  	        if (rc > 0) {
361  	            print_result("%d providers found", rc);
362  	            for (iter = list; iter != NULL; iter = iter->next) {
363  	                print_result("%s", iter->val);
364  	            }
365  	            lrmd_list_freeall(list);
366  	            rc = 0;
367  	        } else {
368  	            print_result("API_CALL FAILURE - no providers found");
369  	            rc = -1;
370  	        }
371  	
372  	    } else if (pcmk__str_eq(options.api_call, "list_standards", pcmk__str_casei)) {
373  	        lrmd_list_t *list = NULL;
374  	        lrmd_list_t *iter = NULL;
375  	
376  	        rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list);
377  	
378  	        if (rc > 0) {
379  	            print_result("%d standards found", rc);
380  	            for (iter = list; iter != NULL; iter = iter->next) {
381  	                print_result("%s", iter->val);
382  	            }
383  	            lrmd_list_freeall(list);
384  	            rc = 0;
385  	        } else {
386  	            print_result("API_CALL FAILURE - no providers found");
387  	            rc = -1;
388  	        }
389  	
390  	    } else if (pcmk__str_eq(options.api_call, "get_recurring_ops", pcmk__str_casei)) {
391  	        GList *op_list = NULL;
392  	        GList *op_item = NULL;
393  	        rc = lrmd_conn->cmds->get_recurring_ops(lrmd_conn, options.rsc_id, 0, 0,
394  	                                                &op_list);
395  	
396  	        for (op_item = op_list; op_item != NULL; op_item = op_item->next) {
397  	            lrmd_op_info_t *op_info = op_item->data;
398  	
399  	            print_result("RECURRING_OP: %s_%s_%s timeout=%sms",
400  	                         op_info->rsc_id, op_info->action,
401  	                         op_info->interval_ms_s, op_info->timeout_ms_s);
402  	            lrmd_free_op_info(op_info);
403  	        }
404  	        g_list_free(op_list);
405  	
406  	    } else if (options.api_call) {
407  	        print_result("API-CALL FAILURE unknown action '%s'", options.action);
408  	        test_exit(CRM_EX_ERROR);
409  	    }
410  	
411  	    if (rc < 0) {
412  	        print_result("API-CALL FAILURE for '%s' api_rc:%d",
413  	                     options.api_call, rc);
414  	        test_exit(CRM_EX_ERROR);
415  	    }
416  	
417  	    if (options.api_call && rc == pcmk_ok) {
418  	        print_result("API-CALL SUCCESSFUL for '%s'", options.api_call);
419  	        if (!options.listen) {
420  	            test_exit(CRM_EX_OK);
421  	        }
422  	    }
423  	
424  	    if (options.no_wait) {
425  	        /* just make the call and exit regardless of anything else. */
426  	        test_exit(CRM_EX_OK);
427  	    }
428  	
429  	    return 0;
430  	}
431  	
432  	/*!
433  	 * \internal
434  	 * \brief Generate resource parameters from CIB if none explicitly given
435  	 *
436  	 * \return Standard Pacemaker return code
437  	 */
438  	static int
439  	generate_params(void)
440  	{
441  	    int rc = pcmk_rc_ok;
442  	    pcmk_scheduler_t *scheduler = NULL;
443  	    xmlNode *cib_xml_copy = NULL;
444  	    pcmk_resource_t *rsc = NULL;
445  	    GHashTable *params = NULL;
446  	    GHashTableIter iter;
447  	    char *key = NULL;
448  	    char *value = NULL;
449  	
450  	    if (options.params != NULL) {
451  	        return pcmk_rc_ok; // User specified parameters explicitly
452  	    }
453  	
454  	    // Retrieve and update CIB
455  	    rc = cib__signon_query(NULL, NULL, &cib_xml_copy);
456  	    if (rc != pcmk_rc_ok) {
457  	        return rc;
458  	    }
459  	    rc = pcmk__update_configured_schema(&cib_xml_copy, false);
460  	    if (rc != pcmk_rc_ok) {
461  	        return rc;
462  	    }
463  	
464  	    // Calculate cluster status
465  	    scheduler = pcmk_new_scheduler();
466  	    if (scheduler == NULL) {
467  	        pcmk__crit("Could not allocate scheduler data");
468  	        return ENOMEM;
469  	    }
470  	    pcmk__set_scheduler_flags(scheduler, pcmk__sched_no_counts);
471  	    scheduler->input = cib_xml_copy;
472  	    scheduler->priv->now = crm_time_new(NULL);
473  	    cluster_status(scheduler);
474  	
475  	    // Find resource in CIB
476  	    rsc = pe_find_resource_with_flags(scheduler->priv->resources,
477  	                                      options.rsc_id,
478  	                                      pcmk_rsc_match_history
479  	                                      |pcmk_rsc_match_basename);
480  	    if (rsc == NULL) {
481  	        pcmk__err("Resource does not exist in config");
482  	        pcmk_free_scheduler(scheduler);
483  	        return EINVAL;
484  	    }
485  	
486  	    // Add resource instance parameters to options.params
487  	    params = pe_rsc_params(rsc, NULL, scheduler);
488  	    if (params != NULL) {
489  	        g_hash_table_iter_init(&iter, params);
490  	        while (g_hash_table_iter_next(&iter, (gpointer *) &key,
491  	                                      (gpointer *) &value)) {
492  	            options.params = lrmd_key_value_add(options.params, key, value);
493  	        }
494  	    }
495  	
496  	    // Add resource meta-attributes to options.params
497  	    g_hash_table_iter_init(&iter, rsc->priv->meta);
498  	    while (g_hash_table_iter_next(&iter, (gpointer *) &key,
499  	                                  (gpointer *) &value)) {
500  	        char *crm_name = crm_meta_name(key);
501  	
502  	        options.params = lrmd_key_value_add(options.params, crm_name, value);
503  	        free(crm_name);
504  	    }
505  	
506  	    pcmk_free_scheduler(scheduler);
507  	    return rc;
508  	}
509  	
510  	static GOptionContext *
511  	build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
512  	    GOptionContext *context = NULL;
513  	
514  	    context = pcmk__build_arg_context(args, NULL, group, NULL);
515  	
516  	    pcmk__add_main_args(context, basic_entries);
517  	    pcmk__add_arg_group(context, "api-call", "API Call Options:",
518  	                        "Parameters for api-call option", api_call_entries);
519  	
520  	    return context;
521  	}
522  	
523  	int
524  	main(int argc, char **argv)
525  	{
526  	    GError *error = NULL;
527  	    crm_exit_t exit_code = CRM_EX_OK;
528  	    crm_trigger_t *trig = NULL;
529  	
530  	    pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
531  	    /* Typically we'd pass all the single character options that take an argument
532  	     * as the second parameter here (and there's a bunch of those in this tool).
533  	     * However, we control how this program is called so we can just not call it
534  	     * in a way where the preprocessing ever matters.
535  	     */
536  	    gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
537  	    GOptionContext *context = build_arg_context(args, NULL);
538  	
539  	    if (!g_option_context_parse_strv(context, &processed_args, &error)) {
540  	        exit_code = CRM_EX_USAGE;
541  	        goto done;
542  	    }
543  	
544  	    /* We have to use crm_log_init here to set up the logging because there's
545  	     * different handling for daemons vs. command line programs, and
546  	     * pcmk__cli_init_logging is set up to only handle the latter.
547  	     */
548  	    crm_log_init(NULL, LOG_INFO, TRUE, (args->verbosity? TRUE : FALSE), argc,
549  	                 argv, FALSE);
550  	
551  	    for (int i = 0; i < args->verbosity; i++) {
552  	        crm_bump_log_level(argc, argv);
553  	    }
554  	
555  	    if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents",
556  	                                                "list_standards", "list_ocf_providers", NULL)) {
557  	        options.no_connect = TRUE;
558  	    }
559  	
560  	    if (options.is_running) {
561  	        int rc = pcmk_rc_ok;
562  	
563  	        if (options.rsc_id == NULL) {
564  	            exit_code = CRM_EX_USAGE;
565  	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
566  	                        "--is-running requires --rsc-id");
567  	            goto done;
568  	        }
569  	
570  	        options.interval_ms = 0;
571  	        if (options.timeout == 0) {
572  	            options.timeout = 30000;
573  	        }
574  	
575  	        rc = generate_params();
576  	        if (rc != pcmk_rc_ok) {
577  	            exit_code = pcmk_rc2exitc(rc);
578  	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
579  	                        "Can not determine resource status: "
580  	                        "unable to get parameters from CIB");
581  	            goto done;
582  	        }
583  	        options.api_call = "exec";
584  	        options.action = PCMK_ACTION_MONITOR;
585  	        options.exec_call_opts = lrmd_opt_notify_orig_only;
586  	    }
587  	
588  	    if (!options.api_call && !options.listen) {
589  	        exit_code = CRM_EX_USAGE;
590  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
591  	                    "Must specify at least one of --api-call, --listen, "
592  	                    "or --is-running");
593  	        goto done;
594  	    }
595  	
596  	    if (options.use_tls) {
597  	        lrmd_conn = lrmd_remote_api_new(NULL, "localhost", 0);
598  	    } else {
599  	        lrmd_conn = lrmd_api_new();
600  	    }
601  	    trig = mainloop_add_trigger(G_PRIORITY_HIGH, start_test, NULL);
602  	    mainloop_set_trigger(trig);
603  	    mainloop_add_signal(SIGTERM, test_shutdown);
604  	
605  	    pcmk__info("Starting");
606  	    mainloop = g_main_loop_new(NULL, FALSE);
607  	    g_main_loop_run(mainloop);
608  	
609  	done:
610  	    g_strfreev(processed_args);
611  	    pcmk__free_arg_context(context);
612  	
613  	    free(key);
614  	    free(val);
615  	
616  	    pcmk__output_and_clear_error(&error, NULL);
617  	    return test_exit(exit_code);
618  	}
619