utils.c 50.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* nmcli - command-line tool to control NetworkManager
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
17
 * Copyright 2010 Lennart Poettering
Lubomir Rintel's avatar
Lubomir Rintel committed
18
 * Copyright 2010 - 2018 Red Hat, Inc.
19 20
 */

21
#include "nm-default.h"
22

23 24
#include "utils.h"

25
#include <stdio.h>
26
#include <stdlib.h>
27 28 29
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
30
#include <sys/auxv.h>
31
#include <sys/prctl.h>
32

33
#include "nm-client-utils.h"
34
#include "nm-meta-setting-access.h"
35

36
#include "common.h"
37
#include "nmcli.h"
38
#include "settings.h"
39

40 41 42
#define ML_HEADER_WIDTH 79
#define ML_VALUE_INDENT 40

43 44 45
/*****************************************************************************/

static const char *
46
_meta_type_nmc_generic_info_get_name (const NMMetaAbstractInfo *abstract_info, gboolean for_header)
47
{
48 49 50 51 52
	const NmcMetaGenericInfo *info = (const NmcMetaGenericInfo *) abstract_info;

	if (for_header)
		return info->name_header ?: info->name;
	return info->name;
53 54 55 56 57 58 59 60 61 62 63
}

static const NMMetaAbstractInfo *const*
_meta_type_nmc_generic_info_get_nested (const NMMetaAbstractInfo *abstract_info,
                                        guint *out_len,
                                        gpointer *out_to_free)
{
	const NmcMetaGenericInfo *info;

	info = (const NmcMetaGenericInfo *) abstract_info;

64
	NM_SET_OUT (out_len, NM_PTRARRAY_LEN (info->nested));
65 66 67
	return (const NMMetaAbstractInfo *const*) info->nested;
}

68
static gconstpointer
69 70
_meta_type_nmc_generic_info_get_fcn (const NMMetaAbstractInfo *abstract_info,
                                     const NMMetaEnvironment *environment,
71 72
                                     gpointer environment_user_data,
                                     gpointer target,
73
                                     gpointer target_data,
74 75
                                     NMMetaAccessorGetType get_type,
                                     NMMetaAccessorGetFlags get_flags,
76
                                     NMMetaAccessorGetOutFlags *out_flags,
77
                                     gboolean *out_is_default,
78
                                     gpointer *out_to_free)
79 80 81
{
	const NmcMetaGenericInfo *info = (const NmcMetaGenericInfo *) abstract_info;

82
	nm_assert (!out_to_free || !*out_to_free);
83
	nm_assert (out_flags && !*out_flags);
84

85 86 87
	if (!NM_IN_SET (get_type,
	                NM_META_ACCESSOR_GET_TYPE_PARSABLE,
	                NM_META_ACCESSOR_GET_TYPE_PRETTY,
88
	                NM_META_ACCESSOR_GET_TYPE_COLOR))
89 90
		g_return_val_if_reached (NULL);

91 92
	/* omitting the out_to_free value is only allowed for COLOR. */
	nm_assert (out_to_free || NM_IN_SET (get_type, NM_META_ACCESSOR_GET_TYPE_COLOR));
93

94
	if (info->get_fcn) {
95 96
		return info->get_fcn (environment,
		                      environment_user_data,
97 98 99
		                      info,
		                      target,
		                      target_data,
100 101 102
		                      get_type,
		                      get_flags,
		                      out_flags,
103
		                      out_is_default,
104 105 106 107
		                      out_to_free);
	}

	if (info->nested) {
108
		NMC_HANDLE_COLOR (NM_META_COLOR_NONE);
109
		return info->name;
110 111 112
	}

	g_return_val_if_reached (NULL);
113 114 115 116 117 118 119 120 121 122 123
}

const NMMetaType nmc_meta_type_generic_info = {
	.type_name =         "nmc-generic-info",
	.get_name =          _meta_type_nmc_generic_info_get_name,
	.get_nested =        _meta_type_nmc_generic_info_get_nested,
	.get_fcn =           _meta_type_nmc_generic_info_get_fcn,
};

/*****************************************************************************/

Thomas Haller's avatar
Thomas Haller committed
124
static const char *
125
colorize_string (const NmcConfig *nmc_config,
126
                 NMMetaColor color,
Thomas Haller's avatar
Thomas Haller committed
127 128 129 130 131
                 const char *str,
                 char **out_to_free)
{
	const char *out = str;

132 133
	if (nmc_config && nmc_config->use_colors) {
		*out_to_free = nmc_colorize (nmc_config, color, "%s", str);
Thomas Haller's avatar
Thomas Haller committed
134 135 136 137 138 139 140 141
		out = *out_to_free;
	}

	return out;
}

/*****************************************************************************/

142 143 144 145 146 147
static gboolean
parse_global_arg (NmCli *nmc, const char *arg)
{
	if (nmc_arg_is_option (arg, "ask"))
		nmc->ask = TRUE;
	else if (nmc_arg_is_option (arg, "show-secrets"))
148
		nmc->nmc_config_mutable.show_secrets = TRUE;
149 150 151 152 153
	else
		return FALSE;

	return TRUE;
}
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
/**
 * next_arg:
 * @nmc: NmCli data
 * @*argc: pointer to left number of arguments to parse
 * @***argv: pointer to const char *array of arguments still to parse
 * @...: a %NULL terminated list of cmd options to match (e.g., "--active")
 *
 * Takes care of autocompleting options when needed and performs
 * match against passed options while moving forward the pointer
 * to the remaining arguments.
 *
 * Returns: the number of the matched option  if a match is found against
 * one of the custom options passed; 0 if no custom option matched and still
 * some args need to be processed or autocompletion has been performed;
 * -1 otherwise (no more args).
 */
170
int
171
next_arg (NmCli *nmc, int *argc, char ***argv, ...)
172
{
173 174 175 176
	va_list args;
	const char *cmd_option;

	g_assert (*argc >= 0);
177

178
	do {
179 180 181
		int cmd_option_pos = 1;

		if (*argc > 0) {
182 183 184
			(*argc)--;
			(*argv)++;
		}
185
		if (*argc == 0)
186
			return -1;
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202

		va_start (args, argv);

		if (nmc && nmc->complete && *argc == 1) {
			while ((cmd_option = va_arg (args, const char *)))
				nmc_complete_strings (**argv, cmd_option, NULL);

			if (***argv == '-')
				nmc_complete_strings (**argv, "--ask", "--show-secrets", NULL);

			va_end (args);
			return 0;
		}

		/* Check command dependent options first */
		while ((cmd_option = va_arg (args, const char *))) {
203 204 205 206 207 208 209 210 211 212 213 214
			if (cmd_option[0] == '-' && cmd_option[1] == '-') {
				/* Match as an option (leading "--" stripped) */
				if (nmc_arg_is_option (**argv, cmd_option + 2)) {
					va_end (args);
					return cmd_option_pos;
				}
			} else {
				/* Match literally. */
				if (strcmp (**argv, cmd_option) == 0) {
					va_end (args);
					return cmd_option_pos;
				}
215 216 217 218 219 220
			}
			cmd_option_pos++;
		}

		va_end (args);

221
	} while (nmc && parse_global_arg (nmc, **argv));
222

223 224 225
	return 0;
}

226 227 228
gboolean
nmc_arg_is_help (const char *arg)
{
229 230
	if (!arg)
		return FALSE;
231 232 233
	if (   matches (arg, "help")
	    || (g_str_has_prefix (arg, "-")  && matches (arg + 1, "help"))
	    || (g_str_has_prefix (arg, "--") && matches (arg + 2, "help"))) {
234 235 236 237 238
		return TRUE;
	}
	return FALSE;
}

239 240 241 242 243 244 245 246 247 248 249 250 251
gboolean
nmc_arg_is_option (const char *str, const char *opt_name)
{
	const char *p;

	if (!str || !*str)
		return FALSE;

	if (str[0] != '-')
		return FALSE;

	p = (str[1] == '-') ? str + 2 : str + 1;

252
	return (*p ? matches (p, opt_name) : FALSE);
253 254
}

255 256 257 258
/*
 * Helper function to parse command-line arguments.
 * arg_arr: description of arguments to look for
 * last:    whether these are last expected arguments
259 260
 * argc:    command-line argument array size
 * argv:    command-line argument array
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
 * error:   error set on a failure (when FALSE is returned)
 * Returns: TRUE on success, FALSE on an error and sets 'error'
 */
gboolean
nmc_parse_args (nmc_arg_t *arg_arr, gboolean last, int *argc, char ***argv, GError **error)
{
	nmc_arg_t *p;
	gboolean found;
	gboolean have_mandatory;

	g_return_val_if_fail (arg_arr != NULL, FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	while (*argc > 0) {
		found = FALSE;

		for (p = arg_arr; p->name; p++) {
			if (strcmp (**argv, p->name) == 0) {

				if (p->found) {
					/* Don't allow repeated arguments, because the argument of the same
					 * name could be used later on the line for another purpose. Assume
					 * that's the case and return.
					 */
					return TRUE;
				}

				if (p->has_value) {
289 290 291
					(*argc)--;
					(*argv)++;
					if (!*argc) {
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
						g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
						             _("Error: value for '%s' argument is required."), *(*argv-1));
						return FALSE;
					}
					*(p->value) = **argv;
				}
				p->found = TRUE;
				found = TRUE;
				break;
			}
		}

		if (!found) {
			have_mandatory = TRUE;
			for (p = arg_arr; p->name; p++) {
				if (p->mandatory && !p->found) {
					have_mandatory = FALSE;
					break;
				}
			}

			if (have_mandatory && !last)
				return TRUE;

316
			if (p->name)
317 318 319 320 321 322 323 324
				g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
				             _("Error: Argument '%s' was expected, but '%s' provided."), p->name, **argv);
			else
				g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
				             _("Error: Unexpected argument '%s'"), **argv);
			return FALSE;
		}

325
		next_arg (NULL, argc, argv, NULL);
326 327 328 329 330
	}

	return TRUE;
}

331
/*
332
 *  Convert SSID to a hex string representation.
333 334 335
 *  Caller has to free the returned string using g_free()
 */
char *
336
ssid_to_hex (const char *str, gsize len)
337 338 339 340 341 342
{
	GString *printable;
	char *printable_str;
	int i;

	if (str == NULL || len == 0)
Jiří Klimeš's avatar
Jiří Klimeš committed
343
		return NULL;
344 345 346 347 348 349 350 351 352

	printable = g_string_new (NULL);
	for (i = 0; i < len; i++) {
		g_string_append_printf (printable, "%02X", (unsigned char) str[i]);
	}
	printable_str = g_string_free (printable, FALSE);
	return printable_str;
}

353 354 355 356 357 358 359 360 361 362
/*
 * Erase terminal line using ANSI escape sequences.
 * It prints <ESC>[2K sequence to erase the line and then \r to return back
 * to the beginning of the line.
 *
 * http://www.termsys.demon.co.uk/vtansi.htm
 */
void
nmc_terminal_erase_line (void)
{
363 364 365
	/* We intentionally use printf(), not g_print() here, to ensure that
	 * GLib doesn't mistakenly try to convert the string.
	 */
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
	printf ("\33[2K\r");
	fflush (stdout);
}

/*
 * Print animated progress for an operation.
 * Repeated calls of the function will show rotating slash in terminal followed
 * by the string passed in 'str' argument.
 */
void
nmc_terminal_show_progress (const char *str)
{
	static int idx = 0;
	const char slashes[4] = {'|', '/', '-', '\\'};

	nmc_terminal_erase_line ();
382
	g_print ("%c %s", slashes[idx++], str ?: "");
383 384 385 386 387
	fflush (stdout);
	if (idx == 4)
		idx = 0;
}

388
char *
389
nmc_colorize (const NmcConfig *nmc_config, NMMetaColor color, const char *fmt, ...)
390 391
{
	va_list args;
392
	char *str, *colored;
393
	const char *ansi_seq = NULL;
394 395 396 397 398

	va_start (args, fmt);
	str = g_strdup_vprintf (fmt, args);
	va_end (args);

399 400
	if (nmc_config->use_colors)
		ansi_seq =  nmc_config->palette[color];
401

402 403
	if (ansi_seq == NULL)
		return str;
404

405
	colored = g_strdup_printf ("\33[%sm%s\33[0m", ansi_seq, str);
406 407
	g_free (str);
	return colored;
408 409
}

410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
/*
 * Count characters belonging to terminal color escape sequences.
 * @start points to beginning of the string, @end points to the end,
 * or NULL if the string is nul-terminated.
 */
static int
nmc_count_color_escape_chars (const char *start, const char *end)
{
	int num = 0;
	gboolean inside = FALSE;

	if (end == NULL)
		end = start + strlen (start);

	while (start < end) {
		if (*start == '\33' && *(start+1) == '[')
			inside = TRUE;
		if (inside)
			num++;
Thomas Haller's avatar
Thomas Haller committed
429
		if (*start == 'm')
430 431 432 433 434 435
			inside = FALSE;
		start++;
	}
	return num;
}

436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
/* Filter out possible ANSI color escape sequences */
/* It directly modifies the passed string @str. */
void
nmc_filter_out_colors_inplace (char *str)
{
	const char *p1;
	char *p2;
	gboolean copy_char = TRUE;

	if (!str)
		return;

	p1 = p2 = str;
	while (*p1) {
		if (*p1 == '\33' && *(p1+1) == '[')
			copy_char = FALSE;
		if (copy_char)
			*p2++ = *p1;
		if (!copy_char && *p1 == 'm')
			copy_char = TRUE;
		p1++;
	}
	*p2 = '\0';
}

/* Filter out possible ANSI color escape sequences */
char *
nmc_filter_out_colors (const char *str)
{
	char *filtered;

	if (!str)
		return NULL;

	filtered = g_strdup (str);
	nmc_filter_out_colors_inplace (filtered);
	return filtered;
}

475 476 477 478 479 480 481 482 483
/*
 * Ask user for input and return the string.
 * The caller is responsible for freeing the returned string.
 */
char *
nmc_get_user_input (const char *ask_str)
{
	char *line = NULL;
	size_t line_ln = 0;
Pavel Šimerda's avatar
Pavel Šimerda committed
484
	ssize_t num;
485

486
	g_print ("%s", ask_str);
Pavel Šimerda's avatar
Pavel Šimerda committed
487
	num = getline (&line, &line_ln, stdin);
488 489

	/* Remove newline from the string */
Pavel Šimerda's avatar
Pavel Šimerda committed
490
	if (num < 1 || (num == 1 && line[0] == '\n')) {
491 492 493
		g_free (line);
		line = NULL;
	} else {
Pavel Šimerda's avatar
Pavel Šimerda committed
494 495
		if (line[num-1] == '\n')
			line[num-1] = '\0';
496 497 498 499 500
	}

	return line;
}

501 502 503 504
/*
 * Split string in 'line' according to 'delim' to (argument) array.
 */
int
505 506
nmc_string_to_arg_array (const char *line, const char *delim, gboolean unquote,
                         char ***argv, int *argc)
507
{
Thomas Haller's avatar
Thomas Haller committed
508
	gs_free const char **arr0 = NULL;
509 510
	char **arr;

511
	arr0 = nm_utils_strsplit_set (line ?: "", delim ?: " \t", FALSE);
Thomas Haller's avatar
Thomas Haller committed
512 513 514 515
	if (!arr0)
		arr = g_new0 (char *, 1);
	else
		arr = g_strdupv ((char **) arr0);
516 517 518 519 520 521 522

	if (unquote) {
		int i = 0;
		char *s;
		size_t l;
		const char *quotes = "\"'";

Thomas Haller's avatar
Thomas Haller committed
523
		while (arr[i]) {
524 525 526 527 528 529 530 531 532 533 534
			s = arr[i];
			l = strlen (s);
			if (l >= 2) {
				if (strchr (quotes, s[0]) && s[l-1] == s[0]) {
					memmove (s, s+1, l-2);
					s[l-2] = '\0';
				}
			}
			i++;
		}
	}
535 536

	*argv = arr;
537
	*argc = g_strv_length (arr);
538 539 540
	return 0;
}

541 542 543 544 545 546 547
/*
 * Convert string array (char **) to description string in the form of:
 * "[string1, string2, ]"
 *
 * Returns: a newly allocated string. Caller must free it with g_free().
 */
char *
548
nmc_util_strv_for_display (const char *const*strv, gboolean brackets)
549 550 551 552 553
{
	GString *result;
	guint i = 0;

	result = g_string_sized_new (150);
554 555
	if (brackets)
		g_string_append_c (result, '[');
556 557 558 559 560 561
	while (strv && strv[i]) {
		if (result->len > 1)
			g_string_append (result, ", ");
		g_string_append (result, strv[i]);
		i++;
	}
562 563
	if (brackets)
		g_string_append_c (result, ']');
564 565 566 567

	return g_string_free (result, FALSE);
}

568
/*
569
 * Find out how many columns an UTF-8 string occupies on the screen.
570 571 572 573 574
 */
int
nmc_string_screen_width (const char *start, const char *end)
{
	int width = 0;
575
	const char *p = start;
576 577 578 579

	if (end == NULL)
		end = start + strlen (start);

580 581 582
	while (p < end) {
		width += g_unichar_iswide (g_utf8_get_char (p)) ? 2 : g_unichar_iszerowidth (g_utf8_get_char (p)) ? 0 : 1;
		p = g_utf8_next_char (p);
583
	}
584 585 586

	/* Subtract color escape sequences as they don't occupy space. */
	return width - nmc_count_color_escape_chars (start, NULL);
587 588
}

589
void
590
set_val_str (NmcOutputField fields_array[], guint32 idx, char *value)
591 592
{
	fields_array[idx].value = value;
593 594 595 596 597 598 599 600 601 602
	fields_array[idx].value_is_array = FALSE;
	fields_array[idx].free_value = TRUE;
}

void
set_val_strc (NmcOutputField fields_array[], guint32 idx, const char *value)
{
	fields_array[idx].value = (char *) value;
	fields_array[idx].value_is_array = FALSE;
	fields_array[idx].free_value = FALSE;
603 604 605
}

void
606
set_val_arr (NmcOutputField fields_array[], guint32 idx, char **value)
607 608
{
	fields_array[idx].value = value;
609 610 611 612 613 614 615 616 617 618
	fields_array[idx].value_is_array = TRUE;
	fields_array[idx].free_value = TRUE;
}

void
set_val_arrc (NmcOutputField fields_array[], guint32 idx, const char **value)
{
	fields_array[idx].value = (char **) value;
	fields_array[idx].value_is_array = TRUE;
	fields_array[idx].free_value = FALSE;
619 620
}

621
void
622
set_val_color_all (NmcOutputField fields_array[], NMMetaColor color)
623 624 625
{
	int i;

626
	for (i = 0; fields_array[i].info; i++) {
627 628 629 630
		fields_array[i].color = color;
	}
}

631 632 633 634 635 636
/*
 * Free 'value' members in array of NmcOutputField
 */
void
nmc_free_output_field_values (NmcOutputField fields_array[])
{
637 638
	NmcOutputField *iter = fields_array;

639
	while (iter && iter->info) {
640 641 642 643 644 645 646 647
		if (iter->free_value) {
			if (iter->value_is_array)
				g_strfreev ((char **) iter->value);
			else
				g_free ((char *) iter->value);
			iter->value = NULL;
		}
		iter++;
648 649 650
	}
}

651 652 653
/*****************************************************************************/

#define PRINT_DATA_COL_PARENT_NIL (G_MAXUINT)
654

655 656 657 658 659 660 661 662 663 664 665
typedef struct _PrintDataCol {
	union {
		const struct _PrintDataCol *parent_col;

		/* while constructing the list of columns in _output_selection_append(), we keep track
		 * of the parent by index. The reason is, that at that point our columns are still
		 * tracked in a GArray which is growing (hence, the pointers are changing).
		 * Later, _output_selection_complete() converts the index into the actual pointer.
		 */
		guint _parent_idx;
	};
666
	const NMMetaSelectionItem *selection_item;
667 668 669
	guint self_idx;
	bool is_leaf;
} PrintDataCol;
670

671 672 673
static gboolean
_output_selection_append (GArray *cols,
                          guint parent_idx,
674
                          const NMMetaSelectionItem *selection_item,
675 676 677 678 679 680 681
                          GPtrArray *gfree_keeper,
                          GError **error)
{
	gs_free gpointer nested_to_free = NULL;
	guint col_idx;
	guint i;
	const NMMetaAbstractInfo *const*nested;
682 683
	NMMetaSelectionResultList *selection;
	const NMMetaSelectionItem *si;
684 685 686 687 688 689

	col_idx = cols->len;

	{
		PrintDataCol col = {
			.selection_item = selection_item,
690
			._parent_idx = parent_idx,
691 692 693 694 695
			.self_idx = col_idx,
			.is_leaf = TRUE,
		};
		g_array_append_val (cols, col);
	}
696

697
	nested = nm_meta_abstract_info_get_nested (selection_item->info, NULL, &nested_to_free);
698

699 700 701
	if (selection_item->sub_selection) {
		if (!nested) {
			gs_free char *allowed_fields = NULL;
702

703 704
			if (parent_idx != PRINT_DATA_COL_PARENT_NIL) {
				si = g_array_index (cols, PrintDataCol, parent_idx).selection_item;
705
				allowed_fields = nm_meta_abstract_info_get_nested_names_str (si->info, si->self_selection);
706
			}
707 708 709 710 711 712 713 714 715 716 717 718
			if (!allowed_fields) {
				g_set_error (error, NMCLI_ERROR, 1, _("invalid field '%s%s%s'; no such field"),
				             selection_item->self_selection ?: "", selection_item->self_selection ? "." : "",
				             selection_item->sub_selection);
			} else {
				g_set_error (error, NMCLI_ERROR, 1, _("invalid field '%s%s%s'; allowed fields: [%s]"),
				             selection_item->self_selection ?: "", selection_item->self_selection ? "." : "",
				             selection_item->sub_selection,
				             allowed_fields);
			}
			return FALSE;
		}
719

720 721
		selection = nm_meta_selection_create_parse_one (nested, selection_item->self_selection,
		                                                selection_item->sub_selection, FALSE, error);
722 723 724 725
		if (!selection)
			return FALSE;
		nm_assert (selection->num == 1);
	} else if (nested) {
726
		selection = nm_meta_selection_create_all (nested);
727 728 729
		nm_assert (selection && selection->num > 0);
	} else
		selection = NULL;
730

731 732
	if (selection) {
		g_ptr_array_add (gfree_keeper, selection);
733

734 735
		for (i = 0; i < selection->num; i++) {
			si = &selection->items[i];
736 737 738 739 740
			if (!_output_selection_append (cols,
			                               col_idx,
			                               si,
			                               gfree_keeper,
			                               error))
741
				return FALSE;
742
		}
743

744 745 746
		if (!NM_IN_SET(selection_item->info->meta_type,
		               &nm_meta_type_setting_info_editor,
		               &nmc_meta_type_generic_info))
747
			g_array_index (cols, PrintDataCol, col_idx).is_leaf = FALSE;
748 749
	}

750 751
	return TRUE;
}
752

753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
static void
_output_selection_complete (GArray *cols)
{
	guint i;

	nm_assert (cols);
	nm_assert (g_array_get_element_size (cols) == sizeof (PrintDataCol));

	for (i = 0; i < cols->len; i++) {
		PrintDataCol *col = &g_array_index (cols, PrintDataCol, i);

		if (col->_parent_idx == PRINT_DATA_COL_PARENT_NIL)
			col->parent_col = NULL;
		else {
			nm_assert (col->_parent_idx < i);
			col->parent_col = &g_array_index (cols, PrintDataCol, col->_parent_idx);
		}
	}
}

773
/*****************************************************************************/
774

775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797
/**
 * _output_selection_parse:
 * @fields: a %NULL terminated array of meta-data fields
 * @fields_str: a comma separated selector for fields. Nested fields
 *   can be specified using '.' notation.
 * @out_cols: (transfer full): the result, parsed as an GArray of PrintDataCol items.
 *   The order of the items is as specified by @fields_str. Meta data
 *   items that contain nested elements are unpacked (note the is_leaf
 *   and parent properties of PrintDataCol).
 * @out_gfree_keeper: (transfer full): an output GPtrArray that owns
 *   strings to which @out_cols points to. The lifetime of @out_cols
 *   and @out_gfree_keeper should correspond.
 * @error:
 *
 * Returns: %TRUE on success.
 */
static gboolean
_output_selection_parse (const NMMetaAbstractInfo *const*fields,
                         const char *fields_str,
                         GArray **out_cols,
                         GPtrArray **out_gfree_keeper,
                         GError **error)
{
798
	NMMetaSelectionResultList *selection;
799 800 801 802
	gs_unref_ptrarray GPtrArray *gfree_keeper = NULL;
	gs_unref_array GArray *cols = NULL;
	guint i;

803
	selection = nm_meta_selection_create_parse_list (fields, fields_str, FALSE, error);
804 805 806 807 808
	if (!selection)
		return FALSE;

	if (!selection->num) {
		g_set_error (error, NMCLI_ERROR, 1, _("failure to select field"));
809
		g_free (selection);
810 811 812 813 814 815 816 817 818
		return FALSE;
	}

	gfree_keeper = g_ptr_array_new_with_free_func (g_free);
	g_ptr_array_add (gfree_keeper, selection);

	cols = g_array_new (FALSE, TRUE, sizeof (PrintDataCol));

	for (i = 0; i < selection->num; i++) {
819
		const NMMetaSelectionItem *si = &selection->items[i];
820

821
		if (!_output_selection_append (cols, PRINT_DATA_COL_PARENT_NIL,
822 823 824 825
		                               si, gfree_keeper, error))
			return FALSE;
	}

826 827
	_output_selection_complete (cols);

828 829 830
	*out_cols = g_steal_pointer (&cols);
	*out_gfree_keeper = g_steal_pointer (&gfree_keeper);
	return TRUE;
831 832
}

833
/*****************************************************************************/
834

835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853
/**
 * parse_output_fields:
 * @field_str: comma-separated field names to parse
 * @fields_array: array of allowed fields
 * @parse_groups: whether the fields can contain group prefix (e.g. general.driver)
 * @group_fields: (out) (allow-none): array of field names for particular groups
 * @error: (out) (allow-none): location to store error, or %NULL
 *
 * Parses comma separated fields in @fields_str according to @fields_array.
 * When @parse_groups is %TRUE, fields can be in the form 'group.field'. Then
 * @group_fields will be filled with the required field for particular group.
 * @group_fields array corresponds to the returned array.
 * Examples:
 *   @field_str:     "type,name,uuid" | "ip4,general.device" | "ip4.address,ip6"
 *   returned array:   2    0    1    |   7         0        |     7         9
 *   @group_fields:   NULL NULL NULL  |  NULL    "device"    | "address"    NULL
 *
 * Returns: #GArray with indices representing fields in @fields_array.
 *   Caller is responsible for freeing the array.
854 855
 */
GArray *
856
parse_output_fields (const char *fields_str,
857
                     const NMMetaAbstractInfo *const*fields_array,
858
                     gboolean parse_groups,
859
                     GPtrArray **out_group_fields,
860
                     GError **error)
861
{
862
	gs_free NMMetaSelectionResultList *selection = NULL;
863 864 865
	GArray *array;
	GPtrArray *group_fields = NULL;
	guint i;
866

867 868
	g_return_val_if_fail (!error || !*error, NULL);
	g_return_val_if_fail (!out_group_fields || !*out_group_fields, NULL);
869

870
	selection = nm_meta_selection_create_parse_list (fields_array, fields_str, TRUE, error);
871 872
	if (!selection)
		return NULL;
873

874 875 876
	array = g_array_sized_new (FALSE, FALSE, sizeof (int), selection->num);
	if (parse_groups && out_group_fields)
		group_fields = g_ptr_array_new_full (selection->num, g_free);
877

878 879
	for (i = 0; i < selection->num; i++) {
		int idx = selection->items[i].idx;
880

881 882 883
		g_array_append_val (array, idx);
		if (group_fields)
			g_ptr_array_add (group_fields, g_strdup (selection->items[i].sub_selection));
884
	}
885

886 887 888
	if (group_fields)
		*out_group_fields = group_fields;
	return array;
889 890
}

891
NmcOutputField *
892
nmc_dup_fields_array (const NMMetaAbstractInfo *const*fields, NmcOfFlags flags)
893 894
{
	NmcOutputField *row;
895
	gsize l;
896

897 898
	for (l = 0; fields[l]; l++) {
	}
899

900 901 902 903
	row = g_new0 (NmcOutputField, l + 1);
	for (l = 0; fields[l]; l++)
		row[l].info = fields[l];
	row[0].flags = flags;
904 905 906 907
	return row;
}

void
908
nmc_empty_output_fields (NmcOutputData *output_data)
909 910 911 912
{
	guint i;

	/* Free values in field structure */
913 914
	for (i = 0; i < output_data->output_data->len; i++) {
		NmcOutputField *fld_arr = g_ptr_array_index (output_data->output_data, i);
915 916 917 918
		nmc_free_output_field_values (fld_arr);
	}

	/* Empty output_data array */
919 920
	if (output_data->output_data->len > 0)
		g_ptr_array_remove_range (output_data->output_data, 0, output_data->output_data->len);
Beniamino Galvani's avatar
Beniamino Galvani committed
921 922

	g_ptr_array_unref (output_data->output_data);
923 924
}

925 926 927 928 929 930
/*****************************************************************************/

typedef struct {
	guint col_idx;
	const PrintDataCol *col;
	const char *title;
931
	bool title_to_free:1;
932 933 934 935 936

	/* whether the column should be printed. If not %TRUE,
	 * the column will be skipped. */
	bool to_print:1;

937 938 939
	int width;
} PrintDataHeaderCell;

940 941 942 943 944
typedef enum {
	PRINT_DATA_CELL_FORMAT_TYPE_PLAIN = 0,
	PRINT_DATA_CELL_FORMAT_TYPE_STRV,
} PrintDataCellFormatType;

945 946 947
typedef struct {
	guint row_idx;
	const PrintDataHeaderCell *header_cell;
948
	NMMetaColor color;
949 950 951 952 953
	union {
		const char *plain;
		const char *const*strv;
	} text;
	PrintDataCellFormatType text_format:3;
954 955 956 957 958 959
	bool text_to_free:1;
} PrintDataCell;

static void
_print_data_header_cell_clear (gpointer cell_p)
{
960 961 962 963 964 965 966
	PrintDataHeaderCell *cell = cell_p;

	if (cell->title_to_free) {
		g_free ((char *) cell->title);
		cell->title_to_free = FALSE;
	}
	cell->title = NULL;
967 968 969 970 971
}

static void
_print_data_cell_clear_text (PrintDataCell *cell)
{
972 973 974
	switch (cell->text_format) {
	case PRINT_DATA_CELL_FORMAT_TYPE_PLAIN:
		if (cell->text_to_free)
975
			g_free ((char *) cell->text.plain);
976 977 978 979
		cell->text.plain = NULL;
		break;
	case PRINT_DATA_CELL_FORMAT_TYPE_STRV:
		if (cell->text_to_free)
980
			g_strfreev ((char **) cell->text.strv);
981 982 983 984 985
		cell->text.strv = NULL;
		break;
	};
	cell->text_format = PRINT_DATA_CELL_FORMAT_TYPE_PLAIN;
	cell->text_to_free = FALSE;
986 987 988 989 990 991 992 993 994 995 996 997 998
}

static void
_print_data_cell_clear (gpointer cell_p)
{
	PrintDataCell *cell = cell_p;

	_print_data_cell_clear_text (cell);
}

static void
_print_fill (const NmcConfig *nmc_config,
             gpointer const *targets,
999
             gpointer targets_data,
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
             const PrintDataCol *cols,
             guint cols_len,
             GArray **out_header_row,
             GArray **out_cells)
{
	GArray *cells;
	GArray *header_row;
	guint i_row, i_col;
	guint targets_len;
	NMMetaAccessorGetType text_get_type;
1010
	NMMetaAccessorGetFlags text_get_flags;
1011 1012 1013 1014 1015 1016 1017 1018 1019


	header_row = g_array_sized_new (FALSE, TRUE, sizeof (PrintDataHeaderCell), cols_len);
	g_array_set_clear_func (header_row, _print_data_header_cell_clear);

	for (i_col = 0; i_col < cols_len; i_col++) {
		const PrintDataCol *col;
		PrintDataHeaderCell *header_cell;
		guint col_idx;
1020
		const NMMetaAbstractInfo *info;
1021 1022 1023 1024 1025

		col = &cols[i_col];
		if (!col->is_leaf)
			continue;

1026 1027
		info = col->selection_item->info;

1028 1029 1030 1031 1032 1033 1034
		col_idx = header_row->len;
		g_array_set_size (header_row, col_idx + 1);

		header_cell = &g_array_index (header_row, PrintDataHeaderCell, col_idx);

		header_cell->col_idx = col_idx;
		header_cell->col = col;
1035 1036 1037 1038

		/* by default, the entire column is skipped. That is the case,
		 * unless we have a cell (below) which opts-in to be printed. */
		header_cell->to_print = FALSE;
1039

1040 1041
		header_cell->title = nm_meta_abstract_info_get_name (info, TRUE);
		if (   nmc_config->multiline_output
1042
		    && col->parent_col
1043 1044 1045 1046
		    && NM_IN_SET (info->meta_type,
		                  &nm_meta_type_property_info,
		                  &nmc_meta_type_generic_info)) {
			header_cell->title = g_strdup_printf ("%s.%s",
1047
			                                      nm_meta_abstract_info_get_name (col->parent_col->selection_item->info, FALSE),
1048 1049 1050
			                                      header_cell->title);
			header_cell->title_to_free = TRUE;
		}
1051 1052 1053 1054 1055 1056 1057 1058
	}

	targets_len = NM_PTRARRAY_LEN (targets);

	cells = g_array_sized_new (FALSE, TRUE, sizeof (PrintDataCell), targets_len * header_row->len);
	g_array_set_clear_func (cells, _print_data_cell_clear);
	g_array_set_size (cells, targets_len * header_row->len);

1059
	text_get_type = nmc_print_output_to_accessor_get_type (nmc_config->print_output);
1060
	text_get_flags = NM_META_ACCESSOR_GET_FLAGS_ACCEPT_STRV;
1061 1062
	if (nmc_config->show_secrets)
		text_get_flags |= NM_META_ACCESSOR_GET_FLAGS_SHOW_SECRETS;
1063 1064 1065 1066 1067 1068 1069 1070

	for (i_row = 0; i_row < targets_len; i_row++) {
		gpointer target = targets[i_row];
		PrintDataCell *cells_line = &g_array_index (cells, PrintDataCell, i_row * header_row->len);

		for (i_col = 0; i_col < header_row->len; i_col++) {
			char *to_free = NULL;
			PrintDataCell *cell = &cells_line[i_col];
1071
			PrintDataHeaderCell *header_cell;
1072
			const NMMetaAbstractInfo *info;
1073 1074
			NMMetaAccessorGetOutFlags text_out_flags, color_out_flags;
			gconstpointer value;
1075
			gboolean is_default;
1076 1077 1078 1079 1080 1081

			header_cell = &g_array_index (header_row, PrintDataHeaderCell, i_col);
			info = header_cell->col->selection_item->info;