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