1    	/*
2    	 * Copyright 2004-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 General Public License version 2
7    	 * or later (GPLv2+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdbool.h>                    // true, false, bool
13   	#include <stdlib.h>                     // NULL, free
14   	#include <sys/types.h>                  // time_t
15   	#include <time.h>                       // time
16   	#include <unistd.h>                     // unlink
17   	
18   	#include <glib.h>                       // g_hash_table_destroy
19   	#include <libxml/tree.h>                // xmlNode
20   	
21   	#include <crm_config.h>                 // PCMK_SCHEDULER_INPUT_DIR
22   	#include <crm/crm.h>                    // CRM_OP_HELLO, CRM_OP_PECALC
23   	#include <crm/common/ipc.h>             // crm_ipc_flags
24   	#include <crm/common/options.h>         // PCMK_OPT_CLUSTER_DELAY
25   	#include <crm/common/results.h>         // CRM_EX_*, pcmk_rc_ok, etc.
26   	#include <crm/common/scheduler.h>       // pcmk__scheduler, pcmk_free_scheduler
27   	#include <crm/common/scheduler_types.h> // pcmk_scheduler_t
28   	#include <crm/common/xml_names.h>       // PCMK_XA_EXECUTION_DATE
29   	#include <pacemaker-internal.h>         // pcmk__schedule_actions
30   	
31   	#include "pacemaker-schedulerd.h"       // logger_out, schedulerd_*
32   	
33   	static GHashTable *schedulerd_handlers = NULL;
34   	
35   	static pcmk_scheduler_t *
36   	init_scheduler(void)
37   	{
38   	    pcmk_scheduler_t *scheduler = pcmk_new_scheduler();
39   	
40   	    pcmk__mem_assert(scheduler);
41   	    scheduler->priv->out = logger_out;
42   	    return scheduler;
43   	}
44   	
45   	static xmlNode *
46   	handle_pecalc_request(pcmk__request_t *request)
47   	{
48   	    static struct series_s {
49   	        const char *name;
50   	        const char *param;
51   	
52   	        /* Maximum number of inputs of this kind to save to disk.
53   	         * If -1, save all; if 0, save none.
54   	         */
55   	        int wrap;
56   	    } series[] = {
57   	        { "pe-error", PCMK_OPT_PE_ERROR_SERIES_MAX, -1 },
58   	        { "pe-warn",  PCMK_OPT_PE_WARN_SERIES_MAX, 5000 },
59   	        { "pe-input", PCMK_OPT_PE_INPUT_SERIES_MAX, 4000 },
60   	    };
61   	
62   	    xmlNode *msg = request->xml;
63   	    xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CRM_XML, NULL, NULL);
64   	    xmlNode *xml_data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
65   	
66   	    static char *last_digest = NULL;
67   	    static char *filename = NULL;
68   	
69   	    unsigned int seq = 0U;
70   	    int series_id = 0;
71   	    int series_wrap = 0;
72   	    char *digest = NULL;
73   	    const char *value = NULL;
74   	    time_t execution_date = time(NULL);
75   	    xmlNode *converted = NULL;
76   	    xmlNode *reply = NULL;
77   	    bool is_repoke = false;
78   	    bool process = true;
79   	    pcmk_scheduler_t *scheduler = init_scheduler();
80   	
81   	    pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
82   	                       NULL, CRM_EX_INDETERMINATE);
83   	
84   	    digest = pcmk__digest_xml(xml_data, false);
85   	    converted = pcmk__xml_copy(NULL, xml_data);
86   	    if (pcmk__update_configured_schema(&converted, true) != pcmk_rc_ok) {
87   	        scheduler->priv->graph = pcmk__xe_create(NULL,
88   	                                                 PCMK__XE_TRANSITION_GRAPH);
89   	        pcmk__xe_set_int(scheduler->priv->graph, "transition_id", 0);
90   	        pcmk__xe_set_int(scheduler->priv->graph, PCMK_OPT_CLUSTER_DELAY, 0);
91   	        process = false;
92   	        free(digest);
93   	
94   	    } else if (pcmk__str_eq(digest, last_digest, pcmk__str_casei)) {
95   	        is_repoke = true;
96   	        free(digest);
97   	
98   	    } else {
99   	        free(last_digest);
100  	        last_digest = digest;
101  	    }
102  	
103  	    if (process) {
104  	        scheduler->input = converted;
105  	        pcmk__set_scheduler_flags(scheduler,
106  	                                  pcmk__sched_no_counts
107  	                                  |pcmk__sched_show_utilization);
108  	        pcmk__schedule_actions(scheduler);
109  	
110  	        // Don't free converted as part of scheduler
111  	        scheduler->input = NULL;
112  	    }
113  	
114  	    // Get appropriate index into series[] array
115  	    if (pcmk__is_set(scheduler->flags, pcmk__sched_processing_error)
116  	        || pcmk__config_has_error) {
117  	        series_id = 0;
118  	    } else if (pcmk__is_set(scheduler->flags, pcmk__sched_processing_warning)
119  	               || pcmk__config_has_warning) {
120  	        series_id = 1;
121  	    } else {
122  	        series_id = 2;
123  	    }
124  	
125  	    value = pcmk__cluster_option(scheduler->priv->options,
126  	                                 series[series_id].param);
127  	    if ((value == NULL)
128  	        || (pcmk__scan_min_int(value, &series_wrap, -1) != pcmk_rc_ok)) {
129  	        series_wrap = series[series_id].wrap;
130  	    }
131  	
132  	    if (pcmk__read_series_sequence(PCMK_SCHEDULER_INPUT_DIR, series[series_id].name,
133  	                                   &seq) != pcmk_rc_ok) {
134  	        // @TODO maybe handle errors better ...
135  	        seq = 0U;
136  	    }
137  	    pcmk__trace("Series %s: wrap=%d, seq=%u, pref=%s", series[series_id].name,
138  	                series_wrap, seq, value);
139  	
140  	    reply = pcmk__new_reply(msg, scheduler->priv->graph);
141  	
142  	    if (reply == NULL) {
143  	        pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
144  	                            "Failed building ping reply for client %s",
145  	                            pcmk__client_name(request->ipc_client));
146  	        goto done;
147  	    }
148  	
149  	    if (series_wrap == 0) { // Don't save any inputs of this kind
150  	        g_clear_pointer(&filename, free);
151  	
152  	    } else if (!is_repoke) { // Input changed, save to disk
153  	        free(filename);
154  	        filename = pcmk__series_filename(PCMK_SCHEDULER_INPUT_DIR,
155  	                                         series[series_id].name, seq, true);
156  	    }
157  	
158  	    pcmk__xe_set(reply, PCMK__XA_CRM_TGRAPH_IN, filename);
159  	
160  	    pcmk__log_transition_summary(scheduler, filename);
161  	
162  	    if (series_wrap == 0) {
163  	        pcmk__debug("Not saving input to disk (disabled by configuration)");
164  	
165  	    } else if (is_repoke) {
166  	        pcmk__info("Input has not changed since last time, not saving to disk");
167  	
168  	    } else {
169  	        unlink(filename);
170  	        pcmk__xe_set_time(xml_data, PCMK_XA_EXECUTION_DATE, execution_date);
171  	        pcmk__xml_write_file(xml_data, filename, true);
172  	        pcmk__write_series_sequence(PCMK_SCHEDULER_INPUT_DIR, series[series_id].name,
173  	                                    ++seq, series_wrap);
174  	    }
175  	
176  	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
177  	
178  	done:
179  	    pcmk__xml_free(converted);
180  	    pcmk_free_scheduler(scheduler);
181  	
182  	    return reply;
183  	}
184  	
185  	static xmlNode *
186  	handle_unknown_request(pcmk__request_t *request)
187  	{
188  	    pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
189  	                       NULL, CRM_EX_PROTOCOL);
190  	
191  	    pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
192  	                        "Unknown request type '%s' (bug?)",
193  	                        pcmk__s(request->op, ""));
194  	    return NULL;
195  	}
196  	
197  	static xmlNode *
198  	handle_hello_request(pcmk__request_t *request)
199  	{
200  	    pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
201  	                       NULL, CRM_EX_INDETERMINATE);
202  	
203  	    pcmk__trace("Received IPC hello from %s",
204  	                pcmk__client_name(request->ipc_client));
205  	
206  	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
207  	    return NULL;
208  	}
209  	
210  	static void
211  	schedulerd_register_handlers(void)
212  	{
213  	    pcmk__server_command_t handlers[] = {
214  	        { CRM_OP_HELLO, handle_hello_request },
215  	        { CRM_OP_PECALC, handle_pecalc_request },
216  	        { NULL, handle_unknown_request },
217  	    };
218  	
219  	    schedulerd_handlers = pcmk__register_handlers(handlers);
220  	}
221  	
222  	void
223  	schedulerd_unregister_handlers(void)
224  	{
CID (unavailable; MK=a46ec59c181f1e09d1a8663075e63264) (#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".
225  	    g_clear_pointer(&schedulerd_handlers, g_hash_table_destroy);
226  	}
227  	
228  	void
229  	schedulerd_handle_request(pcmk__request_t *request)
230  	{
231  	    xmlNode *reply = NULL;
232  	    char *log_msg = NULL;
233  	    const char *exec_status_s = NULL;
234  	    const char *reason = NULL;
235  	
236  	    if (schedulerd_handlers == NULL) {
237  	        schedulerd_register_handlers();
238  	    }
239  	
240  	    reply = pcmk__process_request(request, schedulerd_handlers);
241  	
242  	    if (reply != NULL) {
243  	        pcmk__log_xml_trace(reply, "Reply");
244  	
245  	        pcmk__ipc_send_xml(request->ipc_client, request->ipc_id, reply,
246  	                           crm_ipc_server_event);
247  	        pcmk__xml_free(reply);
248  	    }
249  	
250  	    exec_status_s = pcmk_exec_status_str(request->result.execution_status);
251  	    reason = request->result.exit_reason;
252  	
253  	    log_msg = pcmk__assert_asprintf("Processed %s request from %s %s: %s%s%s%s",
254  	                                    request->op,
255  	                                    pcmk__request_origin_type(request),
256  	                                    pcmk__request_origin(request),
257  	                                    exec_status_s,
258  	                                    (reason == NULL)? "" : " (",
259  	                                    pcmk__s(reason, ""),
260  	                                    (reason == NULL)? "" : ")");
261  	
262  	    if (!pcmk__result_ok(&request->result)) {
263  	        pcmk__warn("%s", log_msg);
264  	    } else {
265  	        pcmk__debug("%s", log_msg);
266  	    }
267  	
268  	    free(log_msg);
269  	    pcmk__reset_request(request);
270  	}
271