/src/pacemaker/lib/common/options_display.c
Line | Count | Source (jump to first uncovered line) |
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 | | add_possible_values_default(pcmk__output_t *out, |
25 | | const pcmk__cluster_option_t *option) |
26 | 0 | { |
27 | 0 | const char *id = _("Possible values"); |
28 | 0 | GString *buf = g_string_sized_new(256); |
29 | |
|
30 | 0 | pcmk__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 | 0 | } |
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 | 0 | g_string_append(buf, delim); |
46 | 0 | } |
47 | 0 | g_string_append_c(buf, '"'); |
48 | 0 | g_string_append(buf, value); |
49 | 0 | 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 | 0 | } |
55 | 0 | } |
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 | 0 | "\")", NULL); |
62 | |
|
63 | 0 | } else { |
64 | 0 | pcmk__g_strcat(buf, option->type, _(" (no default)"), NULL); |
65 | 0 | } |
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 | | add_option_metadata_default(pcmk__output_t *out, |
80 | | const pcmk__cluster_option_t *option) |
81 | 0 | { |
82 | 0 | const char *desc_short = option->description_short; |
83 | 0 | const char *desc_long = option->description_long; |
84 | |
|
85 | 0 | pcmk__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 | 0 | } |
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 | 0 | } |
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 | | option_list_default(pcmk__output_t *out, va_list args) |
127 | 0 | { |
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 | 0 | 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 | pcmk__assert((out != NULL) && (desc_short != NULL) |
145 | 0 | && (desc_long != NULL) && (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 | 0 | } |
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 | 0 | } |
169 | |
|
170 | 0 | } else { |
171 | 0 | out->spacer(out); |
172 | 0 | add_option_metadata_default(out, option); |
173 | 0 | } |
174 | 0 | } |
175 | 0 | } |
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 | 0 | } |
188 | 0 | out->end_list(out); |
189 | 0 | g_slist_free(advanced); |
190 | 0 | } |
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 | 0 | _("DEPRECATED OPTIONS (will be removed in a future " |
198 | 0 | "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 | 0 | } |
205 | 0 | out->end_list(out); |
206 | 0 | g_slist_free(deprecated); |
207 | 0 | } |
208 | |
|
209 | 0 | out->end_list(out); |
210 | 0 | pcmk__output_text_set_fancy(out, old_fancy); |
211 | 0 | return pcmk_rc_ok; |
212 | 0 | } |
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 | | add_desc_xml(pcmk__output_t *out, bool for_long, const char *desc) |
228 | 0 | { |
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 | | add_possible_values_xml(pcmk__output_t *out, |
262 | | const pcmk__cluster_option_t *option) |
263 | 0 | { |
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 | 0 | PCMK_XA_VALUE, ptr, |
272 | 0 | NULL); |
273 | 0 | ptr = strtok(NULL, delim); |
274 | 0 | } |
275 | 0 | free(str); |
276 | 0 | } |
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 | | map_legacy_option_type(const char *type) |
289 | 0 | { |
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 | 0 | PCMK_VALUE_NONNEGATIVE_INTEGER, |
296 | 0 | 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 | 0 | } else { |
303 | 0 | return type; |
304 | 0 | } |
305 | 0 | } |
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 | | add_option_metadata_xml(pcmk__output_t *out, |
316 | | const pcmk__cluster_option_t *option) |
317 | 0 | { |
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 | pcmk__assert(type != NULL); |
337 | | |
338 | | // The standard requires long and short parameter descriptions |
339 | 0 | pcmk__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 | 0 | } |
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 | 0 | } |
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 | 0 | } else { |
367 | 0 | desc_short = pcmk__s(option->description_short, ""); |
368 | 0 | } |
369 | |
|
370 | 0 | if (deprecated) { |
371 | 0 | pcmk__add_separated_word(&desc_short_legacy, init_sz, |
372 | 0 | "*** Deprecated ***", NULL); |
373 | 0 | } |
374 | 0 | if (advanced) { |
375 | 0 | pcmk__add_separated_word(&desc_short_legacy, init_sz, |
376 | 0 | "*** Advanced Use Only ***", NULL); |
377 | 0 | } |
378 | 0 | pcmk__add_separated_word(&desc_short_legacy, 0, desc_short, NULL); |
379 | |
|
380 | 0 | desc_short = desc_short_legacy->str; |
381 | 0 | } |
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 | 0 | } |
390 | |
|
391 | 0 | pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETER, |
392 | 0 | PCMK_XA_NAME, option->name, |
393 | 0 | PCMK_XA_ADVANCED, advanced_s, |
394 | 0 | PCMK_XA_GENERATED, generated_s, |
395 | 0 | 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 | 0 | } |
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 | 0 | PCMK_XA_TYPE, type, |
406 | 0 | PCMK_XA_DEFAULT, option->default_value, |
407 | 0 | 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 | 0 | } |
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 | | option_list_xml(pcmk__output_t *out, va_list args) |
442 | 0 | { |
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 | 0 | va_arg(args, pcmk__cluster_option_t *); |
449 | |
|
450 | 0 | pcmk__assert((out != NULL) && (name != NULL) && (desc_short != NULL) |
451 | 0 | && (desc_long != NULL) && (option_list != NULL)); |
452 | |
|
453 | 0 | pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_AGENT, |
454 | 0 | PCMK_XA_NAME, name, |
455 | 0 | PCMK_XA_VERSION, PACEMAKER_VERSION, |
456 | 0 | 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 | 0 | } |
470 | 0 | } |
471 | |
|
472 | 0 | pcmk__output_xml_pop_parent(out); |
473 | 0 | pcmk__output_xml_pop_parent(out); |
474 | 0 | return pcmk_rc_ok; |
475 | 0 | } |
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 | } |