Line data Source code
1 : /*
2 : * Copyright 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 <glib.h> // GSList, GString
13 :
14 : #include "crmcommon_private.h"
15 :
16 : /*!
17 : * \internal
18 : * \brief Output an option's possible values
19 : *
20 : * \param[in,out] out Output object
21 : * \param[in] option Option whose possible values to add
22 : */
23 : static void
24 0 : add_possible_values_default(pcmk__output_t *out,
25 : const pcmk__cluster_option_t *option)
26 : {
27 0 : const char *id = _("Possible values");
28 0 : GString *buf = g_string_sized_new(256);
29 :
30 0 : CRM_ASSERT(option->type != NULL);
31 :
32 0 : if (pcmk_is_set(option->flags, pcmk__opt_generated)) {
33 0 : id = _("Possible values (generated by Pacemaker)");
34 : }
35 :
36 0 : if ((option->values != NULL) && (strcmp(option->type, "select") == 0)) {
37 0 : const char *delim = ", ";
38 0 : bool found_default = (option->default_value == NULL);
39 0 : char *str = pcmk__str_copy(option->values);
40 :
41 0 : for (const char *value = strtok(str, delim); value != NULL;
42 0 : value = strtok(NULL, delim)) {
43 :
44 0 : if (buf->len > 0) {
45 : g_string_append(buf, delim);
46 : }
47 : g_string_append_c(buf, '"');
48 : g_string_append(buf, value);
49 : g_string_append_c(buf, '"');
50 :
51 0 : if (!found_default && (strcmp(value, option->default_value) == 0)) {
52 0 : found_default = true;
53 0 : g_string_append(buf, _(" (default)"));
54 : }
55 : }
56 0 : free(str);
57 :
58 0 : } else if (option->default_value != NULL) {
59 0 : pcmk__g_strcat(buf,
60 0 : option->type, _(" (default: \""), option->default_value,
61 : "\")", NULL);
62 :
63 : } else {
64 0 : pcmk__g_strcat(buf, option->type, _(" (no default)"), NULL);
65 : }
66 :
67 0 : out->list_item(out, id, "%s", buf->str);
68 0 : g_string_free(buf, TRUE);
69 0 : }
70 :
71 : /*!
72 : * \internal
73 : * \brief Output a single option's metadata
74 : *
75 : * \param[in,out] out Output object
76 : * \param[in] option Option to add
77 : */
78 : static void
79 0 : add_option_metadata_default(pcmk__output_t *out,
80 : const pcmk__cluster_option_t *option)
81 : {
82 0 : const char *desc_short = option->description_short;
83 0 : const char *desc_long = option->description_long;
84 :
85 0 : CRM_ASSERT((desc_short != NULL) || (desc_long != NULL));
86 :
87 0 : if (desc_short == NULL) {
88 0 : desc_short = desc_long;
89 0 : desc_long = NULL;
90 : }
91 :
92 0 : out->list_item(out, option->name, "%s", _(desc_short));
93 :
94 0 : out->begin_list(out, NULL, NULL, NULL);
95 :
96 0 : if (desc_long != NULL) {
97 0 : out->list_item(out, NULL, "%s", _(desc_long));
98 : }
99 0 : add_possible_values_default(out, option);
100 0 : out->end_list(out);
101 0 : }
102 :
103 : /*!
104 : * \internal
105 : * \brief Output the metadata for a list of options
106 : *
107 : * \param[in,out] out Output object
108 : * \param[in] args Message-specific arguments
109 : *
110 : * \return Standard Pacemaker return code
111 : *
112 : * \note \p args should contain the following:
113 : * -# Fake resource agent name for the option list (ignored)
114 : * -# Short description of option list
115 : * -# Long description of option list
116 : * -# Filter: Group of <tt>enum pcmk__opt_flags</tt>; output an option
117 : * only if its \c flags member has all these flags set
118 : * -# <tt>NULL</tt>-terminated list of options whose metadata to format
119 : * -# All: If \c true, output all options; otherwise, exclude advanced and
120 : * deprecated options unless \c pcmk__opt_advanced and
121 : * \c pcmk__opt_deprecated flags (respectively) are set in the filter.
122 : */
123 : PCMK__OUTPUT_ARGS("option-list", "const char *", "const char *", "const char *",
124 : "uint32_t", "const pcmk__cluster_option_t *", "bool")
125 : static int
126 0 : option_list_default(pcmk__output_t *out, va_list args)
127 : {
128 0 : const char *name G_GNUC_UNUSED = va_arg(args, const char *);
129 0 : const char *desc_short = va_arg(args, const char *);
130 0 : const char *desc_long = va_arg(args, const char *);
131 0 : const uint32_t filter = va_arg(args, uint32_t);
132 0 : const pcmk__cluster_option_t *option_list =
133 : va_arg(args, pcmk__cluster_option_t *);
134 0 : const bool all = (bool) va_arg(args, int);
135 :
136 0 : const bool show_deprecated = all
137 0 : || pcmk_is_set(filter, pcmk__opt_deprecated);
138 0 : const bool show_advanced = all || pcmk_is_set(filter, pcmk__opt_advanced);
139 0 : bool old_fancy = false;
140 :
141 0 : GSList *deprecated = NULL;
142 0 : GSList *advanced = NULL;
143 :
144 0 : CRM_ASSERT((out != NULL) && (desc_short != NULL) && (desc_long != NULL)
145 : && (option_list != NULL));
146 :
147 0 : old_fancy = pcmk__output_text_get_fancy(out);
148 0 : pcmk__output_text_set_fancy(out, true);
149 :
150 0 : out->info(out, "%s", _(desc_short));
151 0 : out->spacer(out);
152 0 : out->info(out, "%s", _(desc_long));
153 0 : out->begin_list(out, NULL, NULL, NULL);
154 :
155 0 : for (const pcmk__cluster_option_t *option = option_list;
156 0 : option->name != NULL; option++) {
157 :
158 : // Store deprecated and advanced options to display later if appropriate
159 0 : if (pcmk_all_flags_set(option->flags, filter)) {
160 0 : if (pcmk_is_set(option->flags, pcmk__opt_deprecated)) {
161 0 : if (show_deprecated) {
162 0 : deprecated = g_slist_prepend(deprecated, (gpointer) option);
163 : }
164 :
165 0 : } else if (pcmk_is_set(option->flags, pcmk__opt_advanced)) {
166 0 : if (show_advanced) {
167 0 : advanced = g_slist_prepend(advanced, (gpointer) option);
168 : }
169 :
170 : } else {
171 0 : out->spacer(out);
172 0 : add_option_metadata_default(out, option);
173 : }
174 : }
175 : }
176 :
177 0 : if (advanced != NULL) {
178 0 : advanced = g_slist_reverse(advanced);
179 :
180 0 : out->spacer(out);
181 0 : out->begin_list(out, NULL, NULL, _("ADVANCED OPTIONS"));
182 0 : for (const GSList *iter = advanced; iter != NULL; iter = iter->next) {
183 0 : const pcmk__cluster_option_t *option = iter->data;
184 :
185 0 : out->spacer(out);
186 0 : add_option_metadata_default(out, option);
187 : }
188 0 : out->end_list(out);
189 0 : g_slist_free(advanced);
190 : }
191 :
192 0 : if (deprecated != NULL) {
193 0 : deprecated = g_slist_reverse(deprecated);
194 :
195 0 : out->spacer(out);
196 0 : out->begin_list(out, NULL, NULL,
197 : _("DEPRECATED OPTIONS (will be removed in a future "
198 : "release)"));
199 0 : for (const GSList *iter = deprecated; iter != NULL; iter = iter->next) {
200 0 : const pcmk__cluster_option_t *option = iter->data;
201 :
202 0 : out->spacer(out);
203 0 : add_option_metadata_default(out, option);
204 : }
205 0 : out->end_list(out);
206 0 : g_slist_free(deprecated);
207 : }
208 :
209 0 : out->end_list(out);
210 0 : pcmk__output_text_set_fancy(out, old_fancy);
211 0 : return pcmk_rc_ok;
212 : }
213 :
214 : /*!
215 : * \internal
216 : * \brief Add a description element to an OCF-like metadata XML node
217 : *
218 : * Include a translation based on the current locale if \c ENABLE_NLS is
219 : * defined.
220 : *
221 : * \param[in,out] out Output object
222 : * \param[in] for_long If \c true, add long description; otherwise, add
223 : * short description
224 : * \param[in] desc Textual description to add
225 : */
226 : static void
227 0 : add_desc_xml(pcmk__output_t *out, bool for_long, const char *desc)
228 : {
229 0 : const char *tag = (for_long? PCMK_XE_LONGDESC : PCMK_XE_SHORTDESC);
230 0 : xmlNode *node = pcmk__output_create_xml_text_node(out, tag, desc);
231 :
232 0 : crm_xml_add(node, PCMK_XA_LANG, PCMK__VALUE_EN);
233 :
234 : #ifdef ENABLE_NLS
235 : {
236 : static const char *locale = NULL;
237 :
238 : if (strcmp(desc, _(desc)) == 0) {
239 : return;
240 : }
241 :
242 : if (locale == NULL) {
243 : locale = strtok(setlocale(LC_ALL, NULL), "_");
244 : }
245 : node = pcmk__output_create_xml_text_node(out, tag, _(desc));
246 : crm_xml_add(node, PCMK_XA_LANG, locale);
247 : }
248 : #endif
249 0 : }
250 :
251 : /*!
252 : * \internal
253 : * \brief Output an option's possible values
254 : *
255 : * Add a \c PCMK_XE_OPTION element for each of the option's possible values.
256 : *
257 : * \param[in,out] out Output object
258 : * \param[in] option Option whose possible values to add
259 : */
260 : static void
261 0 : add_possible_values_xml(pcmk__output_t *out,
262 : const pcmk__cluster_option_t *option)
263 : {
264 0 : if ((option->values != NULL) && (strcmp(option->type, "select") == 0)) {
265 0 : const char *delim = ", ";
266 0 : char *str = pcmk__str_copy(option->values);
267 0 : const char *ptr = strtok(str, delim);
268 :
269 0 : while (ptr != NULL) {
270 0 : pcmk__output_create_xml_node(out, PCMK_XE_OPTION,
271 : PCMK_XA_VALUE, ptr,
272 : NULL);
273 0 : ptr = strtok(NULL, delim);
274 : }
275 0 : free(str);
276 : }
277 0 : }
278 :
279 : /*!
280 : * \internal
281 : * \brief Map an option type to one suitable for daemon metadata
282 : *
283 : * \param[in] type Option type to map
284 : *
285 : * \return String suitable for daemon metadata to display as an option type
286 : */
287 : static const char *
288 0 : map_legacy_option_type(const char *type)
289 : {
290 : // @COMPAT Drop this function when we drop daemon metadata commands
291 0 : if (pcmk__str_any_of(type, PCMK_VALUE_DURATION, PCMK_VALUE_TIMEOUT, NULL)) {
292 0 : return PCMK__VALUE_TIME;
293 :
294 0 : } else if (pcmk__str_any_of(type,
295 : PCMK_VALUE_NONNEGATIVE_INTEGER,
296 : PCMK_VALUE_SCORE, NULL)) {
297 0 : return PCMK_VALUE_INTEGER;
298 :
299 0 : } else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_none)) {
300 0 : return PCMK_VALUE_STRING;
301 :
302 : } else {
303 0 : return type;
304 : }
305 : }
306 :
307 : /*!
308 : * \internal
309 : * \brief Add a \c PCMK_XE_PARAMETER element to an OCF-like metadata XML node
310 : *
311 : * \param[in,out] out Output object
312 : * \param[in] option Option to add as a \c PCMK_XE_PARAMETER element
313 : */
314 : static void
315 0 : add_option_metadata_xml(pcmk__output_t *out,
316 : const pcmk__cluster_option_t *option)
317 : {
318 0 : const char *type = option->type;
319 0 : const char *desc_long = option->description_long;
320 0 : const char *desc_short = option->description_short;
321 0 : const bool advanced = pcmk_is_set(option->flags, pcmk__opt_advanced);
322 0 : const bool deprecated = pcmk_is_set(option->flags, pcmk__opt_deprecated);
323 0 : const bool generated = pcmk_is_set(option->flags, pcmk__opt_generated);
324 :
325 : // OCF requires "1"/"0" and does not allow "true"/"false
326 : // @COMPAT Variables no longer needed after we drop legacy mode
327 0 : const char *advanced_s = advanced? "1" : "0";
328 0 : const char *generated_s = generated? "1" : "0";
329 :
330 : // @COMPAT For daemon metadata only; drop when daemon metadata is dropped
331 0 : const bool legacy = pcmk__output_get_legacy_xml(out);
332 0 : char *desc_long_legacy = NULL;
333 0 : GString *desc_short_legacy = NULL;
334 :
335 : // The standard requires a parameter type
336 0 : CRM_ASSERT(type != NULL);
337 :
338 : // The standard requires long and short parameter descriptions
339 0 : CRM_ASSERT((desc_long != NULL) || (desc_short != NULL));
340 :
341 0 : if (desc_long == NULL) {
342 0 : desc_long = desc_short;
343 0 : } else if (desc_short == NULL) {
344 0 : desc_short = desc_long;
345 : }
346 :
347 0 : if (legacy) {
348 : // This is ugly but it will go away at a major release bump
349 0 : type = map_legacy_option_type(type);
350 :
351 0 : if (option->values != NULL) {
352 0 : desc_long_legacy = crm_strdup_printf("%s Allowed values: %s",
353 0 : desc_long, option->values);
354 0 : desc_long = desc_long_legacy;
355 : }
356 :
357 0 : if (deprecated || advanced) {
358 0 : const size_t init_sz = 1023;
359 :
360 0 : if (desc_long != option->description_long) {
361 : /* desc_long was NULL and got assigned desc_short, which was
362 : * non-empty. Let desc_long have the "real" description, and put
363 : * the flag in desc_short.
364 : */
365 0 : desc_short = "";
366 : } else {
367 0 : desc_short = pcmk__s(option->description_short, "");
368 : }
369 :
370 0 : if (deprecated) {
371 0 : pcmk__add_separated_word(&desc_short_legacy, init_sz,
372 : "*** Deprecated ***", NULL);
373 : }
374 0 : if (advanced) {
375 0 : pcmk__add_separated_word(&desc_short_legacy, init_sz,
376 : "*** Advanced Use Only ***", NULL);
377 : }
378 0 : pcmk__add_separated_word(&desc_short_legacy, 0, desc_short, NULL);
379 :
380 0 : desc_short = desc_short_legacy->str;
381 : }
382 :
383 : /* These must be NULL when used as attribute values later.
384 : * PCMK_XA_ADVANCED and PCMK_XA_GENERATED break validation for some
385 : * legacy tools.
386 : */
387 0 : advanced_s = NULL;
388 0 : generated_s = NULL;
389 : }
390 :
391 0 : pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETER,
392 0 : PCMK_XA_NAME, option->name,
393 : PCMK_XA_ADVANCED, advanced_s,
394 : PCMK_XA_GENERATED, generated_s,
395 : NULL);
396 :
397 0 : if (deprecated && !legacy) {
398 : // No need yet to support "replaced-with" or "desc"; add if needed
399 0 : pcmk__output_create_xml_node(out, PCMK_XE_DEPRECATED, NULL);
400 : }
401 0 : add_desc_xml(out, true, desc_long);
402 0 : add_desc_xml(out, false, desc_short);
403 :
404 0 : pcmk__output_xml_create_parent(out, PCMK_XE_CONTENT,
405 : PCMK_XA_TYPE, type,
406 0 : PCMK_XA_DEFAULT, option->default_value,
407 : NULL);
408 :
409 0 : add_possible_values_xml(out, option);
410 :
411 0 : pcmk__output_xml_pop_parent(out);
412 0 : pcmk__output_xml_pop_parent(out);
413 :
414 0 : free(desc_long_legacy);
415 0 : if (desc_short_legacy != NULL) {
416 0 : g_string_free(desc_short_legacy, TRUE);
417 : }
418 0 : }
419 :
420 : /*!
421 : * \internal
422 : * \brief Output the metadata for a list of options as OCF-like XML
423 : *
424 : * \param[in,out] out Output object
425 : * \param[in] args Message-specific arguments
426 : *
427 : * \return Standard Pacemaker return code
428 : *
429 : * \note \p args should contain the following:
430 : * -# Fake resource agent name for the option list
431 : * -# Short description of option list
432 : * -# Long description of option list
433 : * -# Filter: Group of <tt>enum pcmk__opt_flags</tt>; output an option
434 : * only if its \c flags member has all these flags set
435 : * -# <tt>NULL</tt>-terminated list of options whose metadata to format
436 : * -# Whether to output all options (ignored, treated as \c true)
437 : */
438 : PCMK__OUTPUT_ARGS("option-list", "const char *", "const char *", "const char *",
439 : "uint32_t", "const pcmk__cluster_option_t *", "bool")
440 : static int
441 0 : option_list_xml(pcmk__output_t *out, va_list args)
442 : {
443 0 : const char *name = va_arg(args, const char *);
444 0 : const char *desc_short = va_arg(args, const char *);
445 0 : const char *desc_long = va_arg(args, const char *);
446 0 : const uint32_t filter = va_arg(args, uint32_t);
447 0 : const pcmk__cluster_option_t *option_list =
448 : va_arg(args, pcmk__cluster_option_t *);
449 :
450 0 : CRM_ASSERT((out != NULL) && (name != NULL) && (desc_short != NULL)
451 : && (desc_long != NULL) && (option_list != NULL));
452 :
453 0 : pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_AGENT,
454 : PCMK_XA_NAME, name,
455 : PCMK_XA_VERSION, PACEMAKER_VERSION,
456 : NULL);
457 :
458 0 : pcmk__output_create_xml_text_node(out, PCMK_XE_VERSION, PCMK_OCF_VERSION);
459 0 : add_desc_xml(out, true, desc_long);
460 0 : add_desc_xml(out, false, desc_short);
461 :
462 0 : pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETERS, NULL);
463 :
464 0 : for (const pcmk__cluster_option_t *option = option_list;
465 0 : option->name != NULL; option++) {
466 :
467 0 : if (pcmk_all_flags_set(option->flags, filter)) {
468 0 : add_option_metadata_xml(out, option);
469 : }
470 : }
471 :
472 0 : pcmk__output_xml_pop_parent(out);
473 0 : pcmk__output_xml_pop_parent(out);
474 0 : return pcmk_rc_ok;
475 : }
476 :
477 : static pcmk__message_entry_t fmt_functions[] = {
478 : { "option-list", "default", option_list_default },
479 : { "option-list", "xml", option_list_xml },
480 :
481 : { NULL, NULL, NULL }
482 : };
483 :
484 : /*!
485 : * \internal
486 : * \brief Register the formatting functions for option lists
487 : *
488 : * \param[in,out] out Output object
489 : */
490 : void
491 0 : pcmk__register_option_messages(pcmk__output_t *out) {
492 0 : pcmk__register_messages(out, fmt_functions);
493 0 : }
|