Coverage Report

Created: 2024-10-12 00:30

/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
}