Coverage Report

Created: 2026-04-10 06:58

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