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 <errno.h>                  // ENOTCONN
13   	#include <signal.h>                 // SIGTERM
14   	#include <stdbool.h>                // true
15   	#include <stdlib.h>                 // unsetenv
16   	#include <syslog.h>                 // LOG_INFO
17   	
18   	#include <glib.h>                   // G_OPTION_*
19   	#include <qb/qblog.h>               // QB_XS
20   	
21   	#include <crm/common/ipc.h>         // crm_ipc_flags
22   	#include <crm/common/logging.h>     // crm_log_init, crm_log_preinit
23   	#include <crm/common/mainloop.h>    // mainloop_add_signal
24   	#include <crm/common/options.h>     // PCMK_VALUE_NONE
25   	#include <crm/common/results.h>     // pcmk_rc_str, pcmk_rc_*, crm_exit
26   	#include <crm/crm.h>                // crm_system_name
27   	#include <crm/fencing/internal.h>   // stonith__api_free, stonith__api_connect_retry
28   	#include <crm/lrmd_internal.h>      // lrmd__remote_send_xml
29   	#include <crm/stonith-ng.h>         // stonith_s, stonith_t, stonith_state
30   	
31   	#include "pacemaker-execd.h"
32   	
33   	#ifdef PCMK__COMPILE_REMOTE
34   	#  define EXECD_TYPE "remote"
35   	#  define EXECD_NAME PCMK__SERVER_REMOTED
36   	#  define SUMMARY "resource agent executor daemon for Pacemaker Remote nodes"
37   	#else
38   	#  define EXECD_TYPE "local"
39   	#  define EXECD_NAME PCMK__SERVER_EXECD
40   	#  define SUMMARY "resource agent executor daemon for Pacemaker cluster nodes"
41   	#endif
42   	
43   	static GMainLoop *mainloop = NULL;
44   	static stonith_t *fencer_api = NULL;
45   	time_t start_time;
46   	
47   	static struct {
48   	    gchar **log_files;
49   	#ifdef PCMK__COMPILE_REMOTE
50   	    gchar *port;
51   	#endif  // PCMK__COMPILE_REMOTE
52   	} options;
53   	
54   	#ifdef PCMK__COMPILE_REMOTE
55   	/* whether shutdown request has been sent */
56   	static gboolean shutting_down = FALSE;
57   	#endif
58   	
59   	static void exit_executor(void);
60   	
61   	static void
62   	fencer_connection_destroy_cb(stonith_t *st, stonith_event_t *e)
63   	{
64   	    fencer_api->state = stonith_disconnected;
65   	    execd_fencer_connection_failed();
66   	}
67   	
68   	stonith_t *
69   	execd_get_fencer_connection(void)
70   	{
(1) Event path: Condition "fencer_api != NULL", taking true branch.
(2) Event path: Condition "fencer_api->state == stonith_disconnected", taking true branch.
71   	    if ((fencer_api != NULL) && (fencer_api->state == stonith_disconnected)) {
CID (unavailable; MK=a6b038bdf697acc6b473561d5888dd8a) (#1 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(3) Event assign_union_field: The union field "in" of "_pp" is written.
(4) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
72   	        g_clear_pointer(&fencer_api, stonith__api_free);
73   	    }
74   	
75   	    if (fencer_api == NULL) {
76   	        int rc = pcmk_ok;
77   	
78   	        fencer_api = stonith__api_new();
79   	        if (fencer_api == NULL) {
80   	            pcmk__err("Could not connect to fencer: API memory allocation "
81   	                      "failed");
82   	            return NULL;
83   	        }
84   	
85   	        rc = stonith__api_connect_retry(fencer_api, crm_system_name, 10);
86   	        if (rc != pcmk_rc_ok) {
87   	            pcmk__err("Could not connect to fencer in 10 attempts: %s "
88   	                      QB_XS " rc=%d",
89   	                      pcmk_rc_str(rc), rc);
90   	            g_clear_pointer(&fencer_api, stonith__api_free);
91   	
92   	        } else {
93   	            stonith_api_operations_t *cmds = fencer_api->cmds;
94   	
95   	            cmds->register_notification(fencer_api,
96   	                                        PCMK__VALUE_ST_NOTIFY_DISCONNECT,
97   	                                        fencer_connection_destroy_cb);
98   	        }
99   	    }
100  	    return fencer_api;
101  	}
102  	
103  	/*!
104  	 * \internal
105  	 * \brief Free a client connection, and exit if appropriate
106  	 *
107  	 * \param[in,out] client  Client connection to free
108  	 */
109  	void
110  	lrmd_client_destroy(pcmk__client_t *client)
111  	{
112  	    pcmk__free_client(client);
113  	
114  	#ifdef PCMK__COMPILE_REMOTE
115  	    /* If we were waiting to shut down, we can now safely do so
116  	     * if there are no more proxied IPC providers
117  	     */
118  	    if (shutting_down && (ipc_proxy_get_provider() == NULL)) {
119  	        exit_executor();
120  	    }
121  	#endif
122  	}
123  	
124  	// \return Standard Pacemaker return code
125  	int
126  	lrmd_server_send_reply(pcmk__client_t *client, uint32_t id, xmlNode *reply)
127  	{
128  	    pcmk__trace("Sending reply (%d) to client (%s)", id, client->id);
129  	    switch (PCMK__CLIENT_TYPE(client)) {
130  	        case pcmk__client_ipc:
131  	            return pcmk__ipc_send_xml(client, id, reply, crm_ipc_flags_none);
132  	#ifdef PCMK__COMPILE_REMOTE
133  	        case pcmk__client_tls:
134  	            return lrmd__remote_send_xml(client->remote, reply, id, "reply");
135  	#endif
136  	        default:
137  	            pcmk__err("Could not send reply: unknown type for client %s "
138  	                      QB_XS " flags=%#llx",
139  	                      pcmk__client_name(client), client->flags);
140  	    }
141  	    return ENOTCONN;
142  	}
143  	
144  	// \return Standard Pacemaker return code
145  	int
146  	lrmd_server_send_notify(pcmk__client_t *client, xmlNode *msg)
147  	{
148  	    pcmk__trace("Sending notification to client (%s)", client->id);
149  	    switch (PCMK__CLIENT_TYPE(client)) {
150  	        case pcmk__client_ipc:
151  	            if (client->ipcs == NULL) {
152  	                pcmk__trace("Could not notify local client: disconnected");
153  	                return ENOTCONN;
154  	            }
155  	            return pcmk__ipc_send_xml(client, 0, msg, crm_ipc_server_event);
156  	#ifdef PCMK__COMPILE_REMOTE
157  	        case pcmk__client_tls:
158  	            if (client->remote == NULL) {
159  	                pcmk__trace("Could not notify remote client: disconnected");
160  	                return ENOTCONN;
161  	            } else {
162  	                return lrmd__remote_send_xml(client->remote, msg, 0, "notify");
163  	            }
164  	#endif
165  	        default:
166  	            pcmk__err("Could not notify client %s with unknown transport "
167  	                      QB_XS " flags=%#llx",
168  	                      pcmk__client_name(client), client->flags);
169  	    }
170  	    return ENOTCONN;
171  	}
172  	
173  	/*!
174  	 * \internal
175  	 * \brief Clean up and exit immediately
176  	 */
177  	static void
178  	exit_executor(void)
179  	{
180  	    const guint nclients = pcmk__ipc_client_count();
181  	
182  	    pcmk__info("Terminating with %d client%s", nclients,
183  	               pcmk__plural_s(nclients));
184  	    stonith__api_free(fencer_api);
185  	    execd_ipc_cleanup();
186  	
187  	#ifdef PCMK__COMPILE_REMOTE
188  	    execd_stop_tls_server();
189  	    ipc_proxy_cleanup();
190  	#endif
191  	
192  	    if (mainloop) {
193  	        lrmd_drain_alerts(mainloop);
194  	    }
195  	
196  	    execd_unregister_handlers();
197  	    g_hash_table_destroy(rsc_list);
198  	
199  	    // @TODO End mainloop instead so all cleanup is done
200  	    crm_exit(CRM_EX_OK);
201  	}
202  	
203  	/*!
204  	 * \internal
205  	 * \brief Request cluster shutdown if appropriate, otherwise exit immediately
206  	 *
207  	 * \param[in] nsig  Signal that caused invocation (ignored)
208  	 */
209  	static void
210  	lrmd_shutdown(int nsig)
211  	{
212  	#ifdef PCMK__COMPILE_REMOTE
213  	    pcmk__client_t *ipc_proxy = ipc_proxy_get_provider();
214  	
215  	    /* If there are active proxied IPC providers, then we may be running
216  	     * resources, so notify the cluster that we wish to shut down.
217  	     */
218  	    if (ipc_proxy) {
219  	        if (shutting_down) {
220  	            pcmk__notice("Waiting for cluster to stop resources before "
221  	                         "exiting");
222  	            return;
223  	        }
224  	
225  	        pcmk__info("Sending shutdown request to cluster");
226  	        if (ipc_proxy_shutdown_req(ipc_proxy) < 0) {
227  	            pcmk__crit("Shutdown request failed, exiting immediately");
228  	
229  	        } else {
230  	            /* We requested a shutdown. Now, we need to wait for an
231  	             * acknowledgement from the proxy host, then wait for all proxy
232  	             * hosts to disconnect (which ensures that all resources have been
233  	             * stopped).
234  	             */
235  	            shutting_down = TRUE;
236  	
237  	            /* Stop accepting new proxy connections */
238  	            execd_stop_tls_server();
239  	
240  	            /* Currently, we let the OS kill us if the clients don't disconnect
241  	             * in a reasonable time. We could instead set a long timer here
242  	             * (shorter than what the OS is likely to use) and exit immediately
243  	             * if it pops.
244  	             */
245  	            return;
246  	        }
247  	    }
248  	#endif
249  	    exit_executor();
250  	}
251  	
252  	/*!
253  	 * \internal
254  	 * \brief Log a shutdown acknowledgment
255  	 */
256  	void
257  	handle_shutdown_ack(void)
258  	{
259  	#ifdef PCMK__COMPILE_REMOTE
260  	    if (shutting_down) {
261  	        pcmk__info("IPC proxy provider acknowledged shutdown request");
262  	        return;
263  	    }
264  	#endif
265  	    pcmk__debug("Ignoring unexpected shutdown acknowledgment from IPC proxy "
266  	                "provider");
267  	}
268  	
269  	/*!
270  	 * \internal
271  	 * \brief Handle rejection of shutdown request
272  	 */
273  	void
274  	handle_shutdown_nack(void)
275  	{
276  	#ifdef PCMK__COMPILE_REMOTE
277  	    if (shutting_down) {
278  	        pcmk__info("Exiting immediately after IPC proxy provider indicated no "
279  	                   "resources will be stopped");
280  	        exit_executor();
281  	        return;
282  	    }
283  	#endif
284  	    pcmk__debug("Ignoring unexpected shutdown rejection from IPC proxy "
285  	                "provider");
286  	}
287  	
288  	static GOptionEntry entries[] = {
289  	    { "logfile", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY,
290  	      &options.log_files, "Send logs to the additional named logfile", NULL },
291  	
292  	#ifdef PCMK__COMPILE_REMOTE
293  	
294  	    { "port", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.port,
295  	      "Port to listen on (defaults to " G_STRINGIFY(DEFAULT_REMOTE_PORT) ")", NULL },
296  	#endif  // PCMK__COMPILE_REMOTE
297  	
298  	    { NULL }
299  	};
300  	
301  	static pcmk__supported_format_t formats[] = {
302  	    PCMK__SUPPORTED_FORMAT_NONE,
303  	    PCMK__SUPPORTED_FORMAT_TEXT,
304  	    PCMK__SUPPORTED_FORMAT_XML,
305  	    { NULL, NULL, NULL }
306  	};
307  	
308  	static GOptionContext *
309  	build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
310  	{
311  	    GOptionContext *context = NULL;
312  	
313  	    context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
314  	    pcmk__add_main_args(context, entries);
315  	    return context;
316  	}
317  	
318  	int
319  	main(int argc, char **argv)
320  	{
321  	    int rc = pcmk_rc_ok;
322  	    crm_exit_t exit_code = CRM_EX_OK;
323  	
324  	    const char *option = NULL;
325  	
326  	    pcmk__output_t *out = NULL;
327  	
328  	    GError *error = NULL;
329  	
330  	    GOptionGroup *output_group = NULL;
331  	    pcmk__common_args_t *args = NULL;
332  	    gchar **processed_args = NULL;
333  	    GOptionContext *context = NULL;
334  	
335  	#ifdef PCMK__COMPILE_REMOTE
336  	    // If necessary, create PID 1 now before any file descriptors are opened
337  	    remoted_spawn_pidone(argc, argv);
338  	#endif
339  	
340  	    args = pcmk__new_common_args(SUMMARY);
341  	#ifdef PCMK__COMPILE_REMOTE
342  	    processed_args = pcmk__cmdline_preproc(argv, "lp");
343  	#else
344  	    processed_args = pcmk__cmdline_preproc(argv, "l");
345  	#endif  // PCMK__COMPILE_REMOTE
346  	    context = build_arg_context(args, &output_group);
347  	
348  	    crm_log_preinit(EXECD_NAME, argc, argv);
349  	
350  	    pcmk__register_formats(output_group, formats);
351  	    if (!g_option_context_parse_strv(context, &processed_args, &error)) {
352  	        exit_code = CRM_EX_USAGE;
353  	        goto done;
354  	    }
355  	
356  	    rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
357  	    if (rc != pcmk_rc_ok) {
358  	        exit_code = CRM_EX_ERROR;
359  	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
360  	                    "Error creating output format %s: %s",
361  	                    args->output_ty, pcmk_rc_str(rc));
362  	        goto done;
363  	    }
364  	
365  	    if (args->version) {
366  	        out->version(out);
367  	        goto done;
368  	    }
369  	
370  	    // Open additional log files
371  	    pcmk__add_logfiles(options.log_files, out);
372  	
373  	    pcmk__cli_init_logging(EXECD_NAME, args->verbosity);
374  	    crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
375  	
376  	    // ocf_log() (in resource-agents) uses the capitalized env options below
377  	    option = pcmk__env_option(PCMK__ENV_LOGFACILITY);
378  	    if (!pcmk__str_eq(option, PCMK_VALUE_NONE,
379  	                      pcmk__str_casei|pcmk__str_null_matches)
380  	        && !pcmk__str_eq(option, "/dev/null", pcmk__str_none)) {
381  	
382  	        pcmk__set_env_option("LOGFACILITY", option, true);
383  	    }
384  	
385  	    option = pcmk__env_option(PCMK__ENV_LOGFILE);
386  	    if (!pcmk__str_eq(option, PCMK_VALUE_NONE,
387  	                      pcmk__str_casei|pcmk__str_null_matches)) {
388  	        pcmk__set_env_option("LOGFILE", option, true);
389  	
390  	        if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) {
391  	            pcmk__set_env_option("DEBUGLOG", option, true);
392  	        }
393  	    }
394  	
395  	#ifdef PCMK__COMPILE_REMOTE
396  	    if (options.port != NULL) {
397  	        pcmk__set_env_option(PCMK__ENV_REMOTE_PORT, options.port, false);
398  	    }
399  	#endif  // PCMK__COMPILE_REMOTE
400  	
401  	    start_time = time(NULL);
402  	
403  	    pcmk__notice("Starting Pacemaker " EXECD_TYPE " executor");
404  	
405  	    /* The presence of this variable allegedly controls whether child
406  	     * processes like httpd will try and use Systemd's sd_notify
407  	     * API
408  	     */
409  	    unsetenv("NOTIFY_SOCKET");
410  	
411  	    {
412  	        // Temporary directory for resource agent use (leave owned by root)
413  	        int rc = pcmk__build_path(PCMK__OCF_TMP_DIR, 0755);
414  	
415  	        if (rc != pcmk_rc_ok) {
416  	            pcmk__warn("Could not create resource agent temporary directory "
417  	                       PCMK__OCF_TMP_DIR ": %s",
418  	                       pcmk_rc_str(rc));
419  	        }
420  	    }
421  	
422  	    rsc_list = pcmk__strkey_table(NULL, execd_free_rsc);
423  	
424  	    execd_ipc_init();
425  	
426  	#ifdef PCMK__COMPILE_REMOTE
427  	    if (lrmd_init_remote_tls_server() < 0) {
428  	        pcmk__err("Failed to create TLS listener: shutting down and staying "
429  	                  "down");
430  	        exit_code = CRM_EX_FATAL;
431  	        goto done;
432  	    }
433  	    ipc_proxy_init();
434  	#endif
435  	
436  	    mainloop_add_signal(SIGTERM, lrmd_shutdown);
437  	    mainloop = g_main_loop_new(NULL, FALSE);
438  	    pcmk__notice("Pacemaker " EXECD_TYPE " executor successfully started and "
439  	                 "accepting connections");
440  	    pcmk__notice("OCF resource agent search path is %s", PCMK__OCF_RA_PATH);
441  	    g_main_loop_run(mainloop);
442  	
443  	    /* should never get here */
444  	    exit_executor();
445  	
446  	done:
447  	    g_strfreev(options.log_files);
448  	#ifdef PCMK__COMPILE_REMOTE
449  	    g_free(options.port);
450  	#endif  // PCMK__COMPILE_REMOTE
451  	
452  	    g_strfreev(processed_args);
453  	    pcmk__free_arg_context(context);
454  	
455  	    pcmk__output_and_clear_error(&error, out);
456  	
457  	    if (out != NULL) {
458  	        out->finish(out, exit_code, true, NULL);
459  	        pcmk__output_free(out);
460  	    }
461  	    pcmk__unregister_formats();
462  	    crm_exit(exit_code);
463  	}
464