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   	
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);
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  	
(1) Event path: Condition "prompt != NULL", taking true branch.
(2) Event path: Condition "dest != NULL", taking true branch.
461  	    pcmk__assert((prompt != NULL) && (dest != NULL));
462  	
(3) Event path: Condition "!echo", taking true branch.
463  	    if (!echo) {
464  	        rc = tcgetattr(0, &settings);
(4) Event path: Condition "rc == 0", taking true branch.
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  	
(5) Event path: Condition "rc == 0", taking true branch.
472  	    if (rc == 0) {
473  	        fprintf(stderr, "%s: ", prompt);
474  	
CID (unavailable; MK=e269bfde771bd598a2548429eefdb889) (#1 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(6) Event assign_union_field: The union field "in" of "_pp" is written.
(7) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
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