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