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)) {
|
(3) Event path: |
Condition "_p", taking true branch. |
72 g_clear_pointer(&fencer_api, stonith__api_free);
73 }
74
|
(4) Event path: |
Condition "fencer_api == NULL", taking true branch. |
75 if (fencer_api == NULL) {
76 int rc = pcmk_ok;
77
78 fencer_api = stonith__api_new();
|
(5) Event path: |
Condition "fencer_api == NULL", taking false branch. |
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);
|
(6) Event path: |
Condition "rc != pcmk_rc_ok", taking true branch. |
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);
|
CID (unavailable; MK=a6b038bdf697acc6b473561d5888dd8a) (#2 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(7) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(8) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
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