1 /*
2 * Copyright 2019-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 <stdbool.h>
13 #include <glib.h>
14
15 #include <crm/common/util.h>
16 #include <crm/common/xml.h>
17 #include <libxml/tree.h>
18
19 #include "crmcommon_private.h"
20
21 static GHashTable *formatters = NULL;
22
23 #if defined(PCMK__UNIT_TESTING)
24 // LCOV_EXCL_START
25 GHashTable *
26 pcmk__output_formatters(void) {
27 return formatters;
28 }
29
30 void
31 pcmk__set_output_formatters(GHashTable *value)
32 {
33 formatters = value;
34 }
35 // LCOV_EXCL_STOP
36 #endif
37
38 void
39 pcmk__output_free(pcmk__output_t *out) {
40 if (out == NULL) {
41 return;
42 }
43
44 out->free_priv(out);
45
46 g_clear_pointer(&out->messages, g_hash_table_destroy);
47 g_free(out->request);
48 free(out);
49 }
50
51 /*!
52 * \internal
53 * \brief Create a new \p pcmk__output_t structure
54 *
55 * This function does not register any message functions with the newly created
56 * object.
57 *
58 * \param[in,out] out Where to store the new output object
59 * \param[in] fmt_name How to format output
60 * \param[in] filename Where to write formatted output. This can be a
61 * filename (the file will be overwritten if it already
62 * exists), or \p NULL or \p "-" for stdout. For no
63 * output, pass a filename of \p "/dev/null".
64 * \param[in] argv List of command line arguments
65 *
66 * \return Standard Pacemaker return code
67 */
68 int
69 pcmk__bare_output_new(pcmk__output_t **out, const char *fmt_name,
70 const char *filename, char **argv)
71 {
72 pcmk__output_factory_t create = NULL;
73
|
(1) Event path: |
Condition "formatters != NULL", taking true branch. |
|
(2) Event path: |
Condition "out != NULL", taking true branch. |
74 pcmk__assert((formatters != NULL) && (out != NULL));
75
76 /* If no name was given, just try "text". It's up to each tool to register
77 * what it supports so this also may not be valid.
78 */
|
(3) Event path: |
Condition "fmt_name == NULL", taking true branch. |
79 if (fmt_name == NULL) {
80 create = g_hash_table_lookup(formatters, "text");
|
(4) Event path: |
Falling through to end of if statement. |
81 } else {
82 create = g_hash_table_lookup(formatters, fmt_name);
83 }
84
|
(5) Event path: |
Condition "create == NULL", taking false branch. |
85 if (create == NULL) {
86 return pcmk_rc_unknown_format;
87 }
88
89 *out = create(argv);
|
(6) Event path: |
Condition "*out == NULL", taking false branch. |
90 if (*out == NULL) {
91 return ENOMEM;
92 }
93
94 /* Static analysis can't figure out that passing pcmk__str_null_matches
95 * to pcmk__str_eq means filename can't be NULL in the else block, so
96 * we'll just take care of that here.
97 */
|
(7) Event path: |
Condition "filename == NULL", taking true branch. |
98 if (filename == NULL) {
99 filename = "-";
100 }
101
|
(8) Event path: |
Condition "pcmk__str_eq(filename, "-", pcmk__str_none)", taking false branch. |
102 if (pcmk__str_eq(filename, "-", pcmk__str_none)) {
103 (*out)->dest = stdout;
104 } else {
105 (*out)->dest = fopen(filename, "w");
|
(9) Event path: |
Condition "(*out)->dest == NULL", taking true branch. |
106 if ((*out)->dest == NULL) {
107 int rc = errno;
108
|
CID (unavailable; MK=71fec4fd9999eba407654d775cf7fe34) (#1 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(10) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(11) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
109 g_clear_pointer(out, pcmk__output_free);
110 return rc;
111 }
112 }
113
114 (*out)->quiet = false;
115 (*out)->messages = pcmk__strkey_table(free, NULL);
116
117 if ((*out)->init(*out) == false) {
118 g_clear_pointer(out, pcmk__output_free);
119 return ENOMEM;
120 }
121
122 setenv("OCF_OUTPUT_FORMAT", (*out)->fmt_name, 1);
123
124 return pcmk_rc_ok;
125 }
126
127 int
128 pcmk__output_new(pcmk__output_t **out, const char *fmt_name,
129 const char *filename, char **argv)
130 {
131 int rc = pcmk__bare_output_new(out, fmt_name, filename, argv);
132
133 if (rc == pcmk_rc_ok) {
134 // Register libcrmcommon messages
135 pcmk__register_option_messages(*out);
136 pcmk__register_patchset_messages(*out);
137 }
138 return rc;
139 }
140
141 int
142 pcmk__register_format(GOptionGroup *group, const char *name,
143 pcmk__output_factory_t create,
144 const GOptionEntry *options)
145 {
146 char *name_copy = NULL;
147
148 pcmk__assert((create != NULL) && !pcmk__str_empty(name));
149
150 // cppcheck doesn't understand the above pcmk__assert line
151 // cppcheck-suppress ctunullpointer
152 name_copy = strdup(name);
153 if (name_copy == NULL) {
154 return ENOMEM;
155 }
156
157 if (formatters == NULL) {
158 formatters = pcmk__strkey_table(free, NULL);
159 }
160
161 if (options != NULL && group != NULL) {
162 g_option_group_add_entries(group, options);
163 }
164
165 g_hash_table_insert(formatters, name_copy, create);
166 return pcmk_rc_ok;
167 }
168
169 void
170 pcmk__register_formats(GOptionGroup *group,
171 const pcmk__supported_format_t *formats)
172 {
173 if (formats == NULL) {
174 return;
175 }
176 for (const pcmk__supported_format_t *entry = formats; entry->name != NULL;
177 entry++) {
178 pcmk__register_format(group, entry->name, entry->create, entry->options);
179 }
180 }
181
182 void
183 pcmk__unregister_formats(void)
184 {
185 g_clear_pointer(&formatters, g_hash_table_destroy);
186 }
187
188 int
189 pcmk__call_message(pcmk__output_t *out, const char *message_id, ...) {
190 va_list args;
191 int rc = pcmk_rc_ok;
192 pcmk__message_fn_t fn;
193
194 pcmk__assert((out != NULL) && !pcmk__str_empty(message_id));
195
196 fn = g_hash_table_lookup(out->messages, message_id);
197 if (fn == NULL) {
198 pcmk__debug("Called unknown output message '%s' for format '%s'",
199 message_id, out->fmt_name);
200 return EINVAL;
201 }
202
203 va_start(args, message_id);
204 rc = fn(out, args);
205 va_end(args);
206
207 return rc;
208 }
209
210 void
211 pcmk__register_message(pcmk__output_t *out, const char *message_id,
212 pcmk__message_fn_t fn)
213 {
214 pcmk__assert((out != NULL) && !pcmk__str_empty(message_id) && (fn != NULL));
215 g_hash_table_replace(out->messages, pcmk__str_copy(message_id), fn);
216 }
217
218 void
219 pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table)
220 {
221 for (const pcmk__message_entry_t *entry = table; entry->message_id != NULL;
222 entry++) {
223 if (pcmk__strcase_any_of(entry->fmt_name, "default", out->fmt_name, NULL)) {
224 pcmk__register_message(out, entry->message_id, entry->fn);
225 }
226 }
227 }
228
229 void
230 pcmk__output_and_clear_error(GError **error, pcmk__output_t *out)
231 {
232 if (error == NULL || *error == NULL) {
233 return;
234 }
235
236 if (out != NULL) {
237 out->err(out, "%s: %s", g_get_prgname(), (*error)->message);
238 } else {
239 fprintf(stderr, "%s: %s\n", g_get_prgname(), (*error)->message);
240 }
241
242 g_clear_error(error);
243 }
244
245 /*!
246 * \internal
247 * \brief Create an XML-only output object
248 *
249 * Create an output object that supports only the XML format, and free
250 * existing XML if supplied (particularly useful for libpacemaker public API
251 * functions that want to free any previous result supplied by the caller).
252 *
253 * \param[out] out Where to put newly created output object
254 * \param[in,out] xml If \c *xml is non-NULL, this will be freed
255 *
256 * \return Standard Pacemaker return code
257 */
258 int
259 pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml) {
260 pcmk__supported_format_t xml_format[] = {
261 PCMK__SUPPORTED_FORMAT_XML,
262 { NULL, NULL, NULL }
263 };
264
265 if (xml == NULL) {
266 return EINVAL;
267 }
268
269 g_clear_pointer(xml, pcmk__xml_free);
270 pcmk__register_formats(NULL, xml_format);
271 return pcmk__output_new(out, "xml", NULL, NULL);
272 }
273
274 /*!
275 * \internal
276 * \brief Finish and free an XML-only output object
277 *
278 * \param[in,out] out Output object to free
279 * \param[in] exit_status The exit value of the whole program
280 * \param[out] xml If not NULL, where to store XML output
281 */
282 void
283 pcmk__xml_output_finish(pcmk__output_t *out, crm_exit_t exit_status,
284 xmlNodePtr *xml)
285 {
286 if (out == NULL) {
287 return;
288 }
289
290 out->finish(out, exit_status, FALSE, (void **) xml);
291 pcmk__output_free(out);
292 }
293
294 /*!
295 * \internal
296 * \brief Create a new output object using the "log" format
297 *
298 * \param[out] out Where to store newly allocated output object
299 *
300 * \return Standard Pacemaker return code
301 */
302 int
303 pcmk__log_output_new(pcmk__output_t **out)
304 {
305 int rc = pcmk_rc_ok;
306 const char* argv[] = { "", NULL };
307 pcmk__supported_format_t formats[] = {
308 PCMK__SUPPORTED_FORMAT_LOG,
309 { NULL, NULL, NULL }
310 };
311
312 pcmk__register_formats(NULL, formats);
313 rc = pcmk__output_new(out, "log", NULL, (char **) argv);
314 if ((rc != pcmk_rc_ok) || (*out == NULL)) {
315 pcmk__err("Can't log certain messages due to internal error: %s",
316 pcmk_rc_str(rc));
317 return rc;
318 }
319 return pcmk_rc_ok;
320 }
321
322 /*!
323 * \internal
324 * \brief Create a new output object using the "text" format
325 *
326 * \param[out] out Where to store newly allocated output object
327 * \param[in] filename Name of output destination file
328 *
329 * \return Standard Pacemaker return code
330 */
331 int
332 pcmk__text_output_new(pcmk__output_t **out, const char *filename)
333 {
334 int rc = pcmk_rc_ok;
335 const char* argv[] = { "", NULL };
336 pcmk__supported_format_t formats[] = {
337 PCMK__SUPPORTED_FORMAT_TEXT,
338 { NULL, NULL, NULL }
339 };
340
341 pcmk__register_formats(NULL, formats);
342 rc = pcmk__output_new(out, "text", filename, (char **) argv);
343 if ((rc != pcmk_rc_ok) || (*out == NULL)) {
344 pcmk__err("Can't create text output object to internal error: %s",
345 pcmk_rc_str(rc));
346 return rc;
347 }
348 return pcmk_rc_ok;
349 }
350