Coverage Report

Created: 2026-09-28 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pacemaker/lib/common/output_text.c
Line
Count
Source
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 <stdarg.h>
13
#include <stdbool.h>
14
#include <stdlib.h>
15
#include <glib.h>
16
#include <termios.h>
17
18
#include "crmcommon_private.h"
19
20
typedef struct {
21
    unsigned int len;
22
    char *singular_noun;
23
    char *plural_noun;
24
} text_list_data_t;
25
26
typedef struct {
27
    GQueue *parent_q;
28
    bool fancy;
29
} private_data_t;
30
31
static void
32
free_list_data(void *data)
33
0
{
34
0
    text_list_data_t *list_data = data;
35
36
0
    free(list_data->singular_noun);
37
0
    free(list_data->plural_noun);
38
0
    free(list_data);
39
0
}
40
41
static void
42
text_free_priv(pcmk__output_t *out)
43
0
{
44
0
    private_data_t *priv = NULL;
45
46
0
    if (out == NULL || out->priv == NULL) {
47
0
        return;
48
0
    }
49
50
0
    priv = out->priv;
51
52
0
    g_queue_free_full(priv->parent_q, free_list_data);
53
0
    g_clear_pointer(&out->priv, free);
54
0
}
55
56
static bool
57
text_init(pcmk__output_t *out)
58
0
{
59
0
    private_data_t *priv = NULL;
60
61
0
    pcmk__assert(out != NULL);
62
63
    /* If text_init was previously called on this output struct, just return. */
64
0
    if (out->priv != NULL) {
65
0
        return true;
66
0
    }
67
68
0
    out->priv = calloc(1, sizeof(private_data_t));
69
0
    if (out->priv == NULL) {
70
0
        return false;
71
0
    }
72
73
0
    priv = out->priv;
74
0
    priv->parent_q = g_queue_new();
75
0
    return true;
76
0
}
77
78
static void
79
text_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print,
80
            void **copy_dest)
81
0
{
82
0
    pcmk__assert((out != NULL) && (out->dest != NULL));
83
0
    fflush(out->dest);
84
0
}
85
86
static void
87
text_reset(pcmk__output_t *out)
88
0
{
89
0
    private_data_t *priv = NULL;
90
0
    bool old_fancy = false;
91
92
0
    pcmk__assert(out != NULL);
93
94
0
    if (out->dest != stdout) {
95
0
        out->dest = freopen(NULL, "w", out->dest);
96
0
    }
97
98
0
    pcmk__assert(out->dest != NULL);
99
100
    // Save priv->fancy before free/init sequence overwrites it
101
0
    priv = out->priv;
102
0
    old_fancy = priv->fancy;
103
104
0
    text_free_priv(out);
105
0
    text_init(out);
106
107
0
    priv = out->priv;
108
0
    priv->fancy = old_fancy;
109
0
}
110
111
static void
112
text_subprocess_output(pcmk__output_t *out, int exit_status,
113
                       const char *proc_stdout, const char *proc_stderr)
114
0
{
115
0
    pcmk__assert(out != NULL);
116
117
0
    if (proc_stdout != NULL) {
118
0
        fprintf(out->dest, "%s\n", proc_stdout);
119
0
    }
120
121
0
    if (proc_stderr != NULL) {
122
0
        fprintf(out->dest, "%s\n", proc_stderr);
123
0
    }
124
0
}
125
126
static void
127
text_version(pcmk__output_t *out)
128
0
{
129
0
    pcmk__assert((out != NULL) && (out->dest != NULL));
130
131
0
    fprintf(out->dest,
132
0
            "Pacemaker " PACEMAKER_VERSION "\n"
133
0
            "Written by Andrew Beekhof and the Pacemaker project "
134
0
            "contributors\n");
135
0
}
136
137
G_GNUC_PRINTF(2, 3)
138
static void
139
text_err(pcmk__output_t *out, const char *format, ...)
140
0
{
141
0
    va_list ap;
142
143
0
    pcmk__assert(out != NULL);
144
145
0
    va_start(ap, format);
146
147
    /* Informational output does not get indented, to separate it from other
148
     * potentially indented list output.
149
     */
150
0
    vfprintf(stderr, format, ap);
151
0
    va_end(ap);
152
153
    /* Add a newline. */
154
0
    fprintf(stderr, "\n");
155
0
}
156
157
G_GNUC_PRINTF(2, 3)
158
static int
159
text_info(pcmk__output_t *out, const char *format, ...)
160
0
{
161
0
    va_list ap;
162
163
0
    pcmk__assert(out != NULL);
164
165
0
    if (out->is_quiet(out)) {
166
0
        return pcmk_rc_no_output;
167
0
    }
168
169
0
    va_start(ap, format);
170
171
    /* Informational output does not get indented, to separate it from other
172
     * potentially indented list output.
173
     */
174
0
    vfprintf(out->dest, format, ap);
175
0
    va_end(ap);
176
177
    /* Add a newline. */
178
0
    fprintf(out->dest, "\n");
179
0
    return pcmk_rc_ok;
180
0
}
181
182
G_GNUC_PRINTF(2, 3)
183
static int
184
text_transient(pcmk__output_t *out, const char *format, ...)
185
0
{
186
0
    return pcmk_rc_no_output;
187
0
}
188
189
static void
190
text_output_xml(pcmk__output_t *out, const char *name, const char *buf)
191
0
{
192
0
    pcmk__assert(out != NULL);
193
0
    pcmk__indented_printf(out, "%s", buf);
194
0
}
195
196
G_GNUC_PRINTF(4, 5)
197
static void
198
text_begin_list(pcmk__output_t *out, const char *singular_noun,
199
                const char *plural_noun, const char *format, ...)
200
0
{
201
0
    private_data_t *priv = NULL;
202
0
    text_list_data_t *new_list = NULL;
203
0
    va_list ap;
204
205
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
206
0
    priv = out->priv;
207
208
0
    va_start(ap, format);
209
210
0
    if (priv->fancy && (format != NULL)) {
211
0
        pcmk__indented_vprintf(out, format, ap);
212
0
        fprintf(out->dest, ":\n");
213
0
    }
214
215
0
    va_end(ap);
216
217
0
    new_list = pcmk__assert_alloc(1, sizeof(text_list_data_t));
218
0
    new_list->len = 0;
219
0
    new_list->singular_noun = pcmk__str_copy(singular_noun);
220
0
    new_list->plural_noun = pcmk__str_copy(plural_noun);
221
222
0
    g_queue_push_tail(priv->parent_q, new_list);
223
0
}
224
225
G_GNUC_PRINTF(3, 4)
226
static void
227
text_list_item(pcmk__output_t *out, const char *id, const char *format, ...)
228
0
{
229
0
    private_data_t *priv = NULL;
230
0
    va_list ap;
231
232
0
    pcmk__assert(out != NULL);
233
234
0
    priv = out->priv;
235
0
    va_start(ap, format);
236
237
0
    if (priv->fancy) {
238
0
        if (id != NULL) {
239
            /* Not really a good way to do this all in one call, so make it two.
240
             * The first handles the indentation and list styling.  The second
241
             * just prints right after that one.
242
             */
243
0
            pcmk__indented_printf(out, "%s: ", id);
244
0
            vfprintf(out->dest, format, ap);
245
0
        } else {
246
0
            pcmk__indented_vprintf(out, format, ap);
247
0
        }
248
0
    } else {
249
0
        pcmk__indented_vprintf(out, format, ap);
250
0
    }
251
252
0
    fputc('\n', out->dest);
253
0
    fflush(out->dest);
254
0
    va_end(ap);
255
256
0
    out->increment_list(out);
257
0
}
258
259
static void
260
text_increment_list(pcmk__output_t *out)
261
0
{
262
0
    private_data_t *priv = NULL;
263
0
    void *tail = NULL;
264
265
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
266
0
    priv = out->priv;
267
268
0
    tail = g_queue_peek_tail(priv->parent_q);
269
0
    pcmk__assert(tail != NULL);
270
0
    ((text_list_data_t *) tail)->len++;
271
0
}
272
273
static void
274
text_end_list(pcmk__output_t *out)
275
0
{
276
0
    private_data_t *priv = NULL;
277
0
    text_list_data_t *node = NULL;
278
279
0
    pcmk__assert((out != NULL) && (out->priv != NULL));
280
0
    priv = out->priv;
281
282
0
    node = g_queue_pop_tail(priv->parent_q);
283
284
0
    if (node->singular_noun != NULL && node->plural_noun != NULL) {
285
0
        if (node->len == 1) {
286
0
            pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun);
287
0
        } else {
288
0
            pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun);
289
0
        }
290
0
    }
291
292
0
    free_list_data(node);
293
0
}
294
295
static bool
296
text_is_quiet(pcmk__output_t *out)
297
0
{
298
0
    pcmk__assert(out != NULL);
299
0
    return out->quiet;
300
0
}
301
302
static void
303
text_spacer(pcmk__output_t *out)
304
0
{
305
0
    pcmk__assert(out != NULL);
306
0
    fprintf(out->dest, "\n");
307
0
}
308
309
static void
310
text_progress(pcmk__output_t *out, bool end)
311
0
{
312
0
    pcmk__assert(out != NULL);
313
314
0
    if (out->dest == stdout) {
315
0
        fprintf(out->dest, ".");
316
317
0
        if (end) {
318
0
            fprintf(out->dest, "\n");
319
0
        }
320
0
    }
321
0
}
322
323
pcmk__output_t *
324
pcmk__mk_text_output(char **argv)
325
0
{
326
0
    pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
327
328
0
    if (retval == NULL) {
329
0
        return NULL;
330
0
    }
331
332
0
    retval->fmt_name = "text";
333
0
    retval->request = pcmk__quote_cmdline(argv);
334
335
0
    retval->init = text_init;
336
0
    retval->free_priv = text_free_priv;
337
0
    retval->finish = text_finish;
338
0
    retval->reset = text_reset;
339
340
0
    retval->register_message = pcmk__register_message;
341
0
    retval->message = pcmk__call_message;
342
343
0
    retval->subprocess_output = text_subprocess_output;
344
0
    retval->version = text_version;
345
0
    retval->info = text_info;
346
0
    retval->transient = text_transient;
347
0
    retval->err = text_err;
348
0
    retval->output_xml = text_output_xml;
349
350
0
    retval->begin_list = text_begin_list;
351
0
    retval->list_item = text_list_item;
352
0
    retval->increment_list = text_increment_list;
353
0
    retval->end_list = text_end_list;
354
355
0
    retval->is_quiet = text_is_quiet;
356
0
    retval->spacer = text_spacer;
357
0
    retval->progress = text_progress;
358
0
    retval->prompt = pcmk__text_prompt;
359
360
0
    return retval;
361
0
}
362
363
/*!
364
 * \internal
365
 * \brief Check whether fancy output is enabled for a text output object
366
 *
367
 * This returns \c false if the output object is not of text format.
368
 *
369
 * \param[in] out  Output object
370
 *
371
 * \return \c true if \p out has fancy output enabled, or \c false otherwise
372
 */
373
bool
374
pcmk__output_text_get_fancy(pcmk__output_t *out)
375
0
{
376
0
    pcmk__assert(out != NULL);
377
378
0
    if (pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) {
379
0
        private_data_t *priv = out->priv;
380
381
0
        pcmk__assert(priv != NULL);
382
0
        return priv->fancy;
383
0
    }
384
0
    return false;
385
0
}
386
387
/*!
388
 * \internal
389
 * \brief Enable or disable fancy output for a text output object
390
 *
391
 * This does nothing if the output object is not of text format.
392
 *
393
 * \param[in,out] out      Output object
394
 * \param[in]     enabled  Whether fancy output should be enabled for \p out
395
 */
396
void
397
pcmk__output_text_set_fancy(pcmk__output_t *out, bool enabled)
398
0
{
399
0
    pcmk__assert(out != NULL);
400
401
0
    if (pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) {
402
0
        private_data_t *priv = out->priv;
403
404
0
        pcmk__assert(priv != NULL);
405
0
        priv->fancy = enabled;
406
0
    }
407
0
}
408
409
G_GNUC_PRINTF(2, 0)
410
void
411
pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args)
412
0
{
413
0
    pcmk__assert(out != NULL);
414
0
    CRM_CHECK(pcmk__str_eq(out->fmt_name, "text", pcmk__str_none), return);
415
0
    vfprintf(out->dest, format, args);
416
0
}
417
418
G_GNUC_PRINTF(2, 3)
419
void
420
pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...)
421
0
{
422
0
    va_list ap;
423
424
0
    pcmk__assert(out != NULL);
425
426
0
    va_start(ap, format);
427
0
    pcmk__formatted_vprintf(out, format, ap);
428
0
    va_end(ap);
429
0
}
430
431
G_GNUC_PRINTF(2, 0)
432
void
433
pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args)
434
0
{
435
0
    private_data_t *priv = NULL;
436
437
0
    pcmk__assert(out != NULL);
438
0
    CRM_CHECK(pcmk__str_eq(out->fmt_name, "text", pcmk__str_none), return);
439
440
0
    priv = out->priv;
441
442
0
    if (priv->fancy) {
443
0
        int level = 0;
444
0
        private_data_t *priv = out->priv;
445
446
0
        pcmk__assert(priv != NULL);
447
448
0
        level = g_queue_get_length(priv->parent_q);
449
450
0
        for (int i = 0; i < level; i++) {
451
0
            fprintf(out->dest, "  ");
452
0
        }
453
454
0
        if (level > 0) {
455
0
            fprintf(out->dest, "* ");
456
0
        }
457
0
    }
458
459
0
    pcmk__formatted_vprintf(out, format, args);
460
0
}
461
462
G_GNUC_PRINTF(2, 3)
463
void
464
pcmk__indented_printf(pcmk__output_t *out, const char *format, ...)
465
0
{
466
0
    va_list ap;
467
468
0
    pcmk__assert(out != NULL);
469
470
0
    va_start(ap, format);
471
0
    pcmk__indented_vprintf(out, format, ap);
472
0
    va_end(ap);
473
0
}
474
475
void
476
pcmk__text_prompt(const char *prompt, char **dest)
477
0
{
478
0
    int rc = 0;
479
0
    struct termios settings;
480
0
    tcflag_t orig_c_lflag = 0;
481
0
    size_t buf_size = 0;
482
0
    ssize_t nread = 0;
483
484
0
    pcmk__assert((prompt != NULL) && (dest != NULL) && (*dest == NULL));
485
486
0
    rc = tcgetattr(0, &settings);
487
0
    if (rc != 0) {
488
0
        return;
489
0
    }
490
491
0
    orig_c_lflag = settings.c_lflag;
492
0
    settings.c_lflag &= ~ECHO;
493
494
0
    rc = tcsetattr(0, TCSANOW, &settings);
495
0
    if (rc != 0) {
496
0
        return;
497
0
    }
498
499
0
    fprintf(stderr, "%s: ", prompt);
500
501
0
    nread = getline(dest, &buf_size, stdin);
502
503
0
    if (nread < 2) {
504
        // Error or empty line ("\n")
505
0
        g_clear_pointer(dest, free);
506
507
0
    } else {
508
        // Strip the trailing newline but no other whitespace
509
0
        (*dest)[nread - 1] = '\0';
510
0
    }
511
512
0
    fprintf(stderr, "\n");
513
514
0
    if (orig_c_lflag != 0) {
515
0
        settings.c_lflag = orig_c_lflag;
516
        tcsetattr(0, TCSANOW, &settings);
517
0
    }
518
0
}