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