// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2010 Lennart Poettering
* Copyright (C) 2010 - 2018 Red Hat, Inc.
*/
#include "nm-default.h"
#include "utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/auxv.h>
#include <sys/prctl.h>
#include "nm-client-utils.h"
#include "nm-meta-setting-access.h"
#include "common.h"
#include "nmcli.h"
#include "settings.h"
#define ML_HEADER_WIDTH 79
#define ML_VALUE_INDENT 40
/*****************************************************************************/
static const char *
_meta_type_nmc_generic_info_get_name (const NMMetaAbstractInfo *abstract_info, gboolean for_header)
{
const NmcMetaGenericInfo *info = (const NmcMetaGenericInfo *) abstract_info;
if (for_header)
return info->name_header ?: info->name;
return info->name;
}
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;
NM_SET_OUT (out_len, NM_PTRARRAY_LEN (info->nested));
return (const NMMetaAbstractInfo *const*) info->nested;
}
static gconstpointer
_meta_type_nmc_generic_info_get_fcn (const NMMetaAbstractInfo *abstract_info,
const NMMetaEnvironment *environment,
gpointer environment_user_data,
gpointer target,
gpointer target_data,
NMMetaAccessorGetType get_type,
NMMetaAccessorGetFlags get_flags,
NMMetaAccessorGetOutFlags *out_flags,
gboolean *out_is_default,
gpointer *out_to_free)
{
const NmcMetaGenericInfo *info = (const NmcMetaGenericInfo *) abstract_info;
nm_assert (!out_to_free || !*out_to_free);
nm_assert (out_flags && !*out_flags);
if (!NM_IN_SET (get_type,
NM_META_ACCESSOR_GET_TYPE_PARSABLE,
NM_META_ACCESSOR_GET_TYPE_PRETTY,
NM_META_ACCESSOR_GET_TYPE_COLOR))
g_return_val_if_reached (NULL);
/* 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));
if (info->get_fcn) {
return info->get_fcn (environment,
environment_user_data,
info,
target,
target_data,
get_type,
get_flags,
out_flags,
out_is_default,
out_to_free);
}
if (info->nested) {
NMC_HANDLE_COLOR (NM_META_COLOR_NONE);
return info->name;
}
g_return_val_if_reached (NULL);
}
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,
};
/*****************************************************************************/
static const char *
colorize_string (const NmcConfig *nmc_config,
NMMetaColor color,
const char *str,
char **out_to_free)
{
const char *out = str;
if (nmc_config && nmc_config->use_colors) {
*out_to_free = nmc_colorize (nmc_config, color, "%s", str);
out = *out_to_free;
}
return out;
}
/*****************************************************************************/
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"))
nmc->nmc_config_mutable.show_secrets = TRUE;
else
return FALSE;
return TRUE;
}
/**
* 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).
*/
int
next_arg (NmCli *nmc, int *argc, const char *const**argv, ...)
{
va_list args;
const char *cmd_option;
g_assert (*argc >= 0);
do {
int cmd_option_pos = 1;
if (*argc > 0) {
(*argc)--;
(*argv)++;
}
if (*argc == 0)
return -1;
va_start (args, argv);
if (nmc && nmc->complete && *argc == 1) {
while ((cmd_option = va_arg (args, const char *)))
nmc_complete_strings (**argv, cmd_option);
if (***argv == '-')
nmc_complete_strings (**argv, "--ask", "--show-secrets");
va_end (args);
return 0;
}
/* Check command dependent options first */
while ((cmd_option = va_arg (args, const char *))) {
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;
}
}
cmd_option_pos++;
}
va_end (args);
} while (nmc && parse_global_arg (nmc, **argv));
return 0;
}
gboolean
nmc_arg_is_help (const char *arg)
{
if (!arg)
return FALSE;
if ( matches (arg, "help")
|| (g_str_has_prefix (arg, "-") && matches (arg + 1, "help"))
|| (g_str_has_prefix (arg, "--") && matches (arg + 2, "help"))) {
return TRUE;
}
return FALSE;
}
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;
return (*p ? matches (p, opt_name) : FALSE);
}
/*
* Helper function to parse command-line arguments.
* arg_arr: description of arguments to look for
* last: whether these are last expected arguments
* argc: command-line argument array size
* argv: command-line argument array
* 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, const char *const**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) {
(*argc)--;
(*argv)++;
if (!*argc) {
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;
if (p->name)
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;
}
next_arg (NULL, argc, argv, NULL);
}
return TRUE;
}
/*
* Convert SSID to a hex string representation.
* Caller has to free the returned string using g_free()
*/
char *
ssid_to_hex (const char *str, gsize len)
{
if (len == 0)
return NULL;
return nm_utils_bin2hexstr_full (str,
len,
'\0',
TRUE,
NULL);
}
/*
* 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)
{
/* We intentionally use printf(), not g_print() here, to ensure that
* GLib doesn't mistakenly try to convert the string.
*/
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 ();
g_print ("%c %s", slashes[idx++], str ?: "");
fflush (stdout);
if (idx == 4)
idx = 0;
}
char *
nmc_colorize (const NmcConfig *nmc_config, NMMetaColor color, const char *fmt, ...)
{
va_list args;
char *str, *colored;
const char *ansi_seq = NULL;
va_start (args, fmt);
str = g_strdup_vprintf (fmt, args);
va_end (args);
if (nmc_config->use_colors)
ansi_seq = nmc_config->palette[color];
if (ansi_seq == NULL)
return str;
colored = g_strdup_printf ("\33[%sm%s\33[0m", ansi_seq, str);
g_free (str);
return colored;
}
/*
* 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++;
if (*start == 'm')
inside = FALSE;
start++;
}
return num;
}
/* 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;
}
/*
* 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;
ssize_t num;
g_print ("%s", ask_str);
num = getline (&line, &line_ln, stdin);
/* Remove newline from the string */
if (num < 1 || (num == 1 && line[0] == '\n')) {
g_free (line);
line = NULL;
} else {
if (line[num-1] == '\n')
line[num-1] = '\0';
}
return line;
}
/*
* Split string in 'line' according to 'delim' to (argument) array.
*/
int
nmc_string_to_arg_array (const char *line, const char *delim, gboolean unquote,
char ***argv, int *argc)
{
gs_free const char **arr0 = NULL;
char **arr;
arr0 = nm_utils_strsplit_set (line ?: "",
delim ?: " \t");
if (!arr0)
arr = g_new0 (char *, 1);
else
arr = g_strdupv ((char **) arr0);
if (unquote) {
int i = 0;
char *s;
size_t l;
const char *quotes = "\"'";
while (arr[i]) {
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++;
}
}
*argv = arr;
*argc = g_strv_length (arr);
return 0;
}
/*
* 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 *
nmc_util_strv_for_display (const char *const*strv, gboolean brackets)
{
GString *result;
guint i = 0;
result = g_string_sized_new (150);
if (brackets)
g_string_append_c (result, '[');
while (strv && strv[i]) {
if (result->len > 1)
g_string_append (result, ", ");
g_string_append (result, strv[i]);
i++;
}
if (brackets)
g_string_append_c (result, ']');
return g_string_free (result, FALSE);
}
/*
* Find out how many columns an UTF-8 string occupies on the screen.
*/
int
nmc_string_screen_width (const char *start, const char *end)
{
int width = 0;
const char *p = start;
if (end == NULL)
end = start + strlen (start);
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);
}
/* Subtract color escape sequences as they don't occupy space. */
return width - nmc_count_color_escape_chars (start, NULL);
}
void
set_val_str (NmcOutputField fields_array[], guint32 idx, char *value)
{
fields_array[idx].value = value;
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;
}
void
set_val_arr (NmcOutputField fields_array[], guint32 idx, char **value)
{
fields_array[idx].value = value;
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;
}
void
set_val_color_all (NmcOutputField fields_array[], NMMetaColor color)
{
int i;
for (i = 0; fields_array[i].info; i++) {
fields_array[i].color = color;
}
}
/*
* Free 'value' members in array of NmcOutputField
*/
void
nmc_free_output_field_values (NmcOutputField fields_array[])
{
NmcOutputField *iter = fields_array;
while (iter && iter->info) {
if (iter->free_value) {
if (iter->value_is_array)
g_strfreev ((char **) iter->value);
else
g_free ((char *) iter->value);
iter->value = NULL;
}
iter++;
}
}
/*****************************************************************************/
#define PRINT_DATA_COL_PARENT_NIL (G_MAXUINT)
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;
};
const NMMetaSelectionItem *selection_item;
guint self_idx;
bool is_leaf;
} PrintDataCol;
static gboolean
_output_selection_append (GArray *cols,
guint parent_idx,
const NMMetaSelectionItem *selection_item,
GPtrArray *gfree_keeper,
GError **error)
{
gs_free gpointer nested_to_free = NULL;
guint col_idx;
guint i;
const NMMetaAbstractInfo *const*nested;
NMMetaSelectionResultList *selection;
col_idx = cols->len;
{
PrintDataCol col = {
.selection_item = selection_item,
._parent_idx = parent_idx,
.self_idx = col_idx,
.is_leaf = TRUE,
};
g_array_append_val (cols, col);
}
nested = nm_meta_abstract_info_get_nested (selection_item->info, NULL, &nested_to_free);
if (selection_item->sub_selection) {
if (!nested) {
gs_free char *allowed_fields = NULL;
if (parent_idx != PRINT_DATA_COL_PARENT_NIL) {
const NMMetaSelectionItem *si;
si = g_array_index (cols, PrintDataCol, parent_idx).selection_item;
allowed_fields = nm_meta_abstract_info_get_nested_names_str (si->info, si->self_selection);
}
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;
}
selection = nm_meta_selection_create_parse_one (nested, selection_item->self_selection,
selection_item->sub_selection, FALSE, error);
if (!selection)
return FALSE;
nm_assert (selection->num == 1);
} else if (nested) {
selection = nm_meta_selection_create_all (nested);
nm_assert (selection && selection->num > 0);
} else
selection = NULL;
if (selection) {
g_ptr_array_add (gfree_keeper, selection);
for (i = 0; i < selection->num; i++) {
if (!_output_selection_append (cols,
col_idx,
&selection->items[i],
gfree_keeper,
error))
return FALSE;
}
if (!NM_IN_SET(selection_item->info->meta_type,
&nm_meta_type_setting_info_editor,
&nmc_meta_type_generic_info))
g_array_index (cols, PrintDataCol, col_idx).is_leaf = FALSE;
}
return TRUE;
}
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);
}
}
}
/*****************************************************************************/
/**
* _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,
PrintDataCol **out_cols_data,
guint *out_cols_len,
GPtrArray **out_gfree_keeper,
GError **error)
{
NMMetaSelectionResultList *selection;
gs_unref_ptrarray GPtrArray *gfree_keeper = NULL;
gs_unref_array GArray *cols = NULL;
guint i;
selection = nm_meta_selection_create_parse_list (fields, fields_str, FALSE, error);
if (!selection)
return FALSE;
if (!selection->num) {
g_set_error (error, NMCLI_ERROR, 1, _("failure to select field"));
g_free (selection);
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++) {
if (!_output_selection_append (cols,
PRINT_DATA_COL_PARENT_NIL,
&selection->items[i],
gfree_keeper,
error))
return FALSE;
}
_output_selection_complete (cols);
*out_cols_len = cols->len;
*out_cols_data = (PrintDataCol *) g_array_free (g_steal_pointer (&cols), FALSE);
*out_gfree_keeper = g_steal_pointer (&gfree_keeper);
return TRUE;
}
/*****************************************************************************/
/**
* 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.
*/
GArray *
parse_output_fields (const char *fields_str,
const NMMetaAbstractInfo *const*fields_array,
gboolean parse_groups,
GPtrArray **out_group_fields,
GError **error)
{
gs_free NMMetaSelectionResultList *selection = NULL;
GArray *array;
GPtrArray *group_fields = NULL;
guint i;
g_return_val_if_fail (!error || !*error, NULL);
g_return_val_if_fail (!out_group_fields || !*out_group_fields, NULL);
selection = nm_meta_selection_create_parse_list (fields_array, fields_str, TRUE, error);
if (!selection)
return NULL;
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);
for (i = 0; i < selection->num; i++) {
int idx = selection->items[i].idx;
g_array_append_val (array, idx);
if (group_fields)
g_ptr_array_add (group_fields, g_strdup (selection->items[i].sub_selection));
}
if (group_fields)
*out_group_fields = group_fields;
return array;
}
NmcOutputField *
nmc_dup_fields_array (const NMMetaAbstractInfo *const*fields, NmcOfFlags flags)
{
NmcOutputField *row;
gsize l;
for (l = 0; fields[l]; l++) {
}
row = g_new0 (NmcOutputField, l + 1);
for (l = 0; fields[l]; l++)
row[l].info = fields[l];
row[0].flags = flags;
return row;
}
void
nmc_empty_output_fields (NmcOutputData *output_data)
{
guint i;
/* Free values in field structure */
for (i = 0; i < output_data->output_data->len; i++) {
NmcOutputField *fld_arr = g_ptr_array_index (output_data->output_data, i);
nmc_free_output_field_values (fld_arr);
}
/* Empty output_data array */
if (output_data->output_data->len > 0)
g_ptr_array_remove_range (output_data->output_data, 0, output_data->output_data->len);
g_ptr_array_unref (output_data->output_data);
}
/*****************************************************************************/
typedef struct {
guint col_idx;
const PrintDataCol *col;
const char *title;
bool title_to_free:1;
/* whether the column should be printed. If not %TRUE,
* the column will be skipped. */
bool to_print:1;
int width;
} PrintDataHeaderCell;
typedef enum {
PRINT_DATA_CELL_FORMAT_TYPE_PLAIN = 0,
PRINT_DATA_CELL_FORMAT_TYPE_STRV,
} PrintDataCellFormatType;
typedef struct {
guint row_idx;
const PrintDataHeaderCell *header_cell;
NMMetaColor color;
union {
const char *plain;
const char *const*strv;
} text;
PrintDataCellFormatType text_format:3;
bool text_to_free:1;
} PrintDataCell;
static void
_print_data_header_cell_clear (gpointer cell_p)
{
PrintDataHeaderCell *cell = cell_p;
if (cell->title_to_free) {
g_free ((char *) cell->title);
cell->title_to_free = FALSE;
}
cell->title = NULL;
}
static void
_print_data_cell_clear_text (PrintDataCell *cell)
{
switch (cell->text_format) {
case PRINT_DATA_CELL_FORMAT_TYPE_PLAIN:
if (cell->text_to_free)
g_free ((char *) cell->text.plain);
cell->text.plain = NULL;
break;
case PRINT_DATA_CELL_FORMAT_TYPE_STRV:
if (cell->text_to_free)
g_strfreev ((char **) cell->text.strv);
cell->text.strv = NULL;
break;
};
cell->text_format = PRINT_DATA_CELL_FORMAT_TYPE_PLAIN;
cell->text_to_free = FALSE;
}
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,
gpointer targets_data,
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;
NMMetaAccessorGetFlags text_get_flags;
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;
const NMMetaAbstractInfo *info;
col = &cols[i_col];
if (!col->is_leaf)
continue;
info = col->selection_item->info;
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;
/* 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;
header_cell->title = nm_meta_abstract_info_get_name (info, TRUE);
if ( nmc_config->multiline_output
&& col->parent_col
&& NM_IN_SET (info->meta_type,
&nm_meta_type_property_info,
&nmc_meta_type_generic_info)) {
header_cell->title = g_strdup_printf ("%s.%s",
nm_meta_abstract_info_get_name (col->parent_col->selection_item->info, FALSE),
header_cell->title);
header_cell->title_to_free = TRUE;
}
}
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);
text_get_type = nmc_print_output_to_accessor_get_type (nmc_config->print_output);
text_get_flags = NM_META_ACCESSOR_GET_FLAGS_ACCEPT_STRV;
if (nmc_config->show_secrets)
text_get_flags |= NM_META_ACCESSOR_GET_FLAGS_SHOW_SECRETS;
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];
PrintDataHeaderCell *header_cell;
const NMMetaAbstractInfo *info;
NMMetaAccessorGetOutFlags text_out_flags, color_out_flags;
gconstpointer value;
gboolean is_default;
header_cell = &g_array_index (header_row, PrintDataHeaderCell, i_col);
info = header_cell->col->selection_item->info;
cell->row_idx = i_row;
cell->header_cell = header_cell;
value = nm_meta_abstract_info_get (info,
nmc_meta_environment,
(gpointer) nmc_meta_environment_arg,
target,
targets_data,
text_get_type,
text_get_flags,
&text_out_flags,
&is_default,
(gpointer *) &to_free);
nm_assert (!to_free || value == to_free);
if ( ( is_default
&& nmc_config->overview)
|| NM_FLAGS_HAS (text_out_flags, NM_META_ACCESSOR_GET_OUT_FLAGS_HIDE)) {
/* don't mark the entry for display. This is to shorten the output in case
* the property is the default value. But we only do that, if the user
* opts in to this behavior (-overview), or of the property marks itself
* eligible to be hidden.
*
* In general, only new API shall mark itself eligible to be hidden.
* Long established properties cannot, because it would be a change
* in behavior. */
} else
header_cell->to_print = TRUE;
if (NM_FLAGS_HAS (text_out_flags, NM_META_ACCESSOR_GET_OUT_FLAGS_STRV)) {
if (nmc_config->multiline_output) {
cell->text_format = PRINT_DATA_CELL_FORMAT_TYPE_STRV;
cell->text.strv = value;
cell->text_to_free = !!to_free;
} else {
if (value && ((const char *const*) value)[0]) {
cell->text.plain = g_strjoinv (" | ", (char **) value);
cell->text_to_free = TRUE;
}
if (to_free)
g_strfreev ((char **) to_free);
}
} else {
cell->text.plain = value;
cell->text_to_free = !!to_free;
}
cell->color = GPOINTER_TO_INT (nm_meta_abstract_info_get (info,
nmc_meta_environment,
(gpointer) nmc_meta_environment_arg,
target,
targets_data,
NM_META_ACCESSOR_GET_TYPE_COLOR,
NM_META_ACCESSOR_GET_FLAGS_NONE,
&color_out_flags,
NULL,
NULL));
if (cell->text_format == PRINT_DATA_CELL_FORMAT_TYPE_PLAIN) {
if ( NM_IN_SET (nmc_config->print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY)
&& ( !cell->text.plain
|| !cell->text.plain[0])) {
_print_data_cell_clear_text (cell);
cell->text.plain = "--";
} else if (!cell->text.plain)
cell->text.plain = "";
nm_assert (cell->text_format == PRINT_DATA_CELL_FORMAT_TYPE_PLAIN);
}
}
}
for (i_col = 0; i_col < header_row->len; i_col++) {
PrintDataHeaderCell *header_cell = &g_array_index (header_row, PrintDataHeaderCell, i_col);
header_cell->width = nmc_string_screen_width (header_cell->title, NULL);
for (i_row = 0; i_row < targets_len; i_row++) {
const PrintDataCell *cells_line = &g_array_index (cells, PrintDataCell, i_row * header_row->len);
const PrintDataCell *cell = &cells_line[i_col];
const char *const*i_strv;
switch (cell->text_format) {
case PRINT_DATA_CELL_FORMAT_TYPE_PLAIN:
header_cell->width = NM_MAX (header_cell->width,
nmc_string_screen_width (cell->text.plain, NULL));
break;
case PRINT_DATA_CELL_FORMAT_TYPE_STRV:
i_strv = cell->text.strv;
if (i_strv) {
for (; *i_strv; i_strv++) {
header_cell->width = NM_MAX (header_cell->width,
nmc_string_screen_width (*i_strv, NULL));
}
}
break;
}
}
header_cell->width += 1;
}
*out_header_row = header_row;
*out_cells = cells;
}
static gboolean
_print_skip_column (const NmcConfig *nmc_config,
const PrintDataHeaderCell *header_cell)
{
const NMMetaSelectionItem *selection_item;
const NMMetaAbstractInfo *info;
selection_item = header_cell->col->selection_item;
info = selection_item->info;
if (!header_cell->to_print)
return TRUE;
if (nmc_config->multiline_output) {
if (info->meta_type == &nm_meta_type_setting_info_editor) {
/* we skip the "name" entry for the setting in multiline output. */
return TRUE;
}
if ( info->meta_type == &nmc_meta_type_generic_info
&& ((const NmcMetaGenericInfo *) info)->nested) {
/* skip the "name" entry for parent generic-infos */
return TRUE;
}
} else {
if ( NM_IN_SET (info->meta_type,
&nm_meta_type_setting_info_editor,
&nmc_meta_type_generic_info)
&& selection_item->sub_selection) {
/* in tabular form, we skip the "name" entry for sections that have sub-selections.
* That is, for "ipv4.may-fail", but not for "ipv4". */
return TRUE;
}
}
return FALSE;
}
static void
_print_do (const NmcConfig *nmc_config,
const char *header_name_no_l10n,
guint col_len,
guint row_len,
const PrintDataHeaderCell *header_row,
const PrintDataCell *cells)
{
int width1, width2;
int table_width = 0;
guint i_row, i_col;
nm_auto_free_gstring GString *str = NULL;
g_assert (col_len);
/* Main header */
if ( nmc_config->print_output == NMC_PRINT_PRETTY
&& header_name_no_l10n) {
gs_free char *line = NULL;
int header_width;
const char *header_name = _(header_name_no_l10n);
header_width = nmc_string_screen_width (header_name, NULL) + 4;
if (nmc_config->multiline_output) {
table_width = NM_MAX (header_width, ML_HEADER_WIDTH);
line = g_strnfill (ML_HEADER_WIDTH, '=');
} else { /* tabular */
table_width = NM_MAX (table_width, header_width);
line = g_strnfill (table_width, '=');
}
width1 = strlen (header_name);
width2 = nmc_string_screen_width (header_name, NULL);
g_print ("%s\n", line);
g_print ("%*s\n", (table_width + width2)/2 + width1 - width2, header_name);
g_print ("%s\n", line);
}
str = !nmc_config->multiline_output
? g_string_sized_new (100)
: NULL;
/* print the header for the tabular form */
if ( NM_IN_SET (nmc_config->print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY)
&& !nmc_config->multiline_output) {
for (i_col = 0; i_col < col_len; i_col++) {
const PrintDataHeaderCell *header_cell = &header_row[i_col];
const char *title;
if (_print_skip_column (nmc_config, header_cell))
continue;
title = header_cell->title;
width1 = strlen (title);
width2 = nmc_string_screen_width (title, NULL); /* Width of the string (in screen columns) */
g_string_append_printf (str, "%-*s", (int) (header_cell->width + width1 - width2), title);
g_string_append_c (str, ' '); /* Column separator */
table_width += header_cell->width + width1 - width2 + 1;
}
if (str->len)
g_string_truncate (str, str->len-1); /* Chop off last column separator */
g_print ("%s\n", str->str);
g_string_truncate (str, 0);
/* Print horizontal separator */
if (nmc_config->print_output == NMC_PRINT_PRETTY) {
gs_free char *line = NULL;
g_print ("%s\n", (line = g_strnfill (table_width, '-')));
}
}
for (i_row = 0; i_row < row_len; i_row++) {
const PrintDataCell *current_line = &cells[i_row * col_len];
for (i_col = 0; i_col < col_len; i_col++) {
const PrintDataCell *cell = ¤t_line[i_col];
const char *const*lines = NULL;
guint i_lines, lines_len;
if (_print_skip_column (nmc_config, cell->header_cell))
continue;
lines_len = 0;
switch (cell->text_format) {
case PRINT_DATA_CELL_FORMAT_TYPE_PLAIN:
lines = &cell->text.plain;
lines_len = 1;
break;
case PRINT_DATA_CELL_FORMAT_TYPE_STRV:
nm_assert (nmc_config->multiline_output);
lines = cell->text.strv;
lines_len = NM_PTRARRAY_LEN (lines);
break;
}
for (i_lines = 0; i_lines < lines_len; i_lines++) {
gs_free char *text_to_free = NULL;
const char *text;
text = colorize_string (nmc_config, cell->color, lines[i_lines], &text_to_free);
if (nmc_config->multiline_output) {
gs_free char *prefix = NULL;
if (cell->text_format == PRINT_DATA_CELL_FORMAT_TYPE_STRV)
prefix = g_strdup_printf ("%s[%u]:", cell->header_cell->title, i_lines + 1);
else
prefix = g_strdup_printf ("%s:", cell->header_cell->title);
width1 = strlen (prefix);
width2 = nmc_string_screen_width (prefix, NULL);
g_print ("%-*s%s\n",
(int) ( nmc_config->print_output == NMC_PRINT_TERSE
? 0
: ML_VALUE_INDENT+width1-width2),
prefix,
text);
} else {
nm_assert (str);
if (nmc_config->print_output == NMC_PRINT_TERSE) {
if (nmc_config->escape_values) {
const char *p = text;
while (*p) {
if (*p == ':' || *p == '\\')
g_string_append_c (str, '\\'); /* Escaping by '\' */
g_string_append_c (str, *p);
p++;
}
}
else
g_string_append_printf (str, "%s", text);
g_string_append_c (str, ':'); /* Column separator */
} else {
const PrintDataHeaderCell *header_cell = &header_row[i_col];
width1 = strlen (text);
width2 = nmc_string_screen_width (text, NULL); /* Width of the string (in screen columns) */
g_string_append_printf (str, "%-*s", (int) (header_cell->width + width1 - width2), text);
g_string_append_c (str, ' '); /* Column separator */
table_width += header_cell->width + width1 - width2 + 1;
}
}
}
}
if (!nmc_config->multiline_output) {
if (str->len)
g_string_truncate (str, str->len-1); /* Chop off last column separator */
g_print ("%s\n", str->str);
g_string_truncate (str, 0);
}
if ( nmc_config->print_output == NMC_PRINT_PRETTY
&& nmc_config->multiline_output) {
gs_free char *line = NULL;
g_print ("%s\n", (line = g_strnfill (ML_HEADER_WIDTH, '-')));
}
}
}
gboolean
nmc_print (const NmcConfig *nmc_config,
gpointer const *targets,
gpointer targets_data,
const char *header_name_no_l10n,
const NMMetaAbstractInfo *const*fields,
const char *fields_str,
GError **error)
{
gs_unref_ptrarray GPtrArray *gfree_keeper = NULL;
gs_free PrintDataCol *cols_data = NULL;
guint cols_len;
gs_unref_array GArray *header_row = NULL;
gs_unref_array GArray *cells = NULL;
if (!_output_selection_parse (fields,
fields_str,
&cols_data,
&cols_len,
&gfree_keeper,
error))
return FALSE;
_print_fill (nmc_config,
targets,
targets_data,
cols_data,
cols_len,
&header_row,
&cells);
_print_do (nmc_config,
header_name_no_l10n,
header_row->len,
cells->len / header_row->len,
&g_array_index (header_row, PrintDataHeaderCell, 0),
&g_array_index (cells, PrintDataCell, 0));
return TRUE;
}
/*****************************************************************************/
static void
pager_fallback (void)
{
char buf[64];
int rb;
int errsv;
do {
rb = read (STDIN_FILENO, buf, sizeof (buf));
if (rb == -1) {
errsv = errno;
if (errsv == EINTR)
continue;
g_printerr (_("Error reading nmcli output: %s\n"), nm_strerror_native (errsv));
_exit(EXIT_FAILURE);
}
if (write (STDOUT_FILENO, buf, rb) == -1) {
errsv = errno;
g_printerr (_("Error writing nmcli output: %s\n"), nm_strerror_native (errsv));
_exit(EXIT_FAILURE);
}
} while (rb > 0);
_exit(EXIT_SUCCESS);
}
pid_t
nmc_terminal_spawn_pager (const NmcConfig *nmc_config)
{
const char *pager = getenv ("PAGER");
pid_t pager_pid;
pid_t parent_pid;
int fd[2];
int errsv;
if ( nmc_config->in_editor
|| nmc_config->print_output == NMC_PRINT_TERSE
|| !nmc_config->use_colors
|| g_strcmp0 (pager, "") == 0
|| getauxval (AT_SECURE))
return 0;
if (pipe (fd) == -1) {
errsv = errno;
g_printerr (_("Failed to create pager pipe: %s\n"), nm_strerror_native (errsv));
return 0;
}
parent_pid = getpid ();
pager_pid = fork ();
if (pager_pid == -1) {
errsv = errno;
g_printerr (_("Failed to fork pager: %s\n"), nm_strerror_native (errsv));
nm_close (fd[0]);
nm_close (fd[1]);
return 0;
}
/* In the child start the pager */
if (pager_pid == 0) {
dup2 (fd[0], STDIN_FILENO);
nm_close (fd[0]);
nm_close (fd[1]);
setenv ("LESS", "FRSXMK", 1);
setenv ("LESSCHARSET", "utf-8", 1);
/* Make sure the pager goes away when the parent dies */
if (prctl (PR_SET_PDEATHSIG, SIGTERM) < 0)
_exit (EXIT_FAILURE);
/* Check whether our parent died before we were able
* to set the death signal */
if (getppid () != parent_pid)
_exit (EXIT_SUCCESS);
if (pager) {
execlp (pager, pager, NULL);
execl ("/bin/sh", "sh", "-c", pager, NULL);
}
/* Debian's alternatives command for pagers is
* called 'pager'. Note that we do not call
* sensible-pagers here, since that is just a
* shell script that implements a logic that
* is similar to this one anyway, but is
* Debian-specific. */
execlp ("pager", "pager", NULL);
execlp ("less", "less", NULL);
execlp ("more", "more", NULL);
pager_fallback ();
/* not reached */
}
/* Return in the parent */
if (dup2 (fd[1], STDOUT_FILENO) < 0) {
errsv = errno;
g_printerr (_("Failed to duplicate pager pipe: %s\n"), nm_strerror_native (errsv));
}
if (dup2 (fd[1], STDERR_FILENO) < 0) {
errsv = errno;
g_printerr (_("Failed to duplicate pager pipe: %s\n"), nm_strerror_native (errsv));
}
nm_close (fd[0]);
nm_close (fd[1]);
return pager_pid;
}
/*****************************************************************************/
static const char *
get_value_to_print (const NmcConfig *nmc_config,
const NmcOutputField *field,
gboolean field_name,
const char *not_set_str,
char **out_to_free)
{
gboolean is_array = field->value_is_array;
const char *value;
const char *out;
gs_free char *free_value = NULL;
nm_assert (out_to_free && !*out_to_free);
if (field_name)
value = nm_meta_abstract_info_get_name (field->info, FALSE);
else {
value = field->value
? (is_array
? (free_value = g_strjoinv (" | ", (char **) field->value))
: (*((const char *) field->value))
? field->value
: not_set_str)
: not_set_str;
}
/* colorize the value */
out = colorize_string (nmc_config, field->color, value, out_to_free);
if (out && out == free_value) {
nm_assert (!*out_to_free);
*out_to_free = g_steal_pointer (&free_value);
}
return out;
}
/*
* Print both headers or values of 'field_values' array.
* Entries to print and their order are specified via indices in
* 'nmc->indices' array.
* Various flags influencing the output of fields are set up in the first item
* of 'field_values' array.
*/
void
print_required_fields (const NmcConfig *nmc_config,
NmcPagerData *pager_data,
NmcOfFlags of_flags,
const GArray *indices,
const char *header_name,
int indent,
const NmcOutputField *field_values)
{
nm_auto_free_gstring GString *str = NULL;
int width1, width2;
int table_width = 0;
const char *not_set_str;
int i;
gboolean main_header_add = of_flags & NMC_OF_FLAG_MAIN_HEADER_ADD;
gboolean main_header_only = of_flags & NMC_OF_FLAG_MAIN_HEADER_ONLY;
gboolean field_names = of_flags & NMC_OF_FLAG_FIELD_NAMES;
gboolean section_prefix = of_flags & NMC_OF_FLAG_SECTION_PREFIX;
nm_cli_spawn_pager (nmc_config, pager_data);
/* --- Main header --- */
if ( nmc_config->print_output == NMC_PRINT_PRETTY
&& ( main_header_add
|| main_header_only)) {
gs_free char *line = NULL;
int header_width;
header_width = nmc_string_screen_width (header_name, NULL) + 4;
if (nmc_config->multiline_output) {
table_width = NM_MAX (header_width, ML_HEADER_WIDTH);
line = g_strnfill (ML_HEADER_WIDTH, '=');
} else { /* tabular */
table_width = NM_MAX (table_width, header_width);
line = g_strnfill (table_width, '=');
}
width1 = strlen (header_name);
width2 = nmc_string_screen_width (header_name, NULL);
g_print ("%s\n", line);
g_print ("%*s\n", (table_width + width2)/2 + width1 - width2, header_name);
g_print ("%s\n", line);
}
if (main_header_only)
return;
/* No field headers are printed in terse mode nor for multiline output */
if ( ( nmc_config->print_output == NMC_PRINT_TERSE
|| nmc_config->multiline_output)
&& field_names)
return;
/* Don't replace empty strings in terse mode */
not_set_str = nmc_config->print_output == NMC_PRINT_TERSE ? "" : "--";
if (nmc_config->multiline_output) {
for (i = 0; i < indices->len; i++) {
int idx = g_array_index (indices, int, i);
gboolean is_array = field_values[idx].value_is_array;
/* section prefix can't be an array */
g_assert (!is_array || !section_prefix || idx != 0);
if (section_prefix && idx == 0) /* The first field is section prefix */
continue;
if (is_array) {
gs_free char *val_to_free = NULL;
const char **p, *val, *print_val;
int j;
/* value is a null-terminated string array */
for (p = (const char **) field_values[idx].value, j = 1; p && *p; p++, j++) {
gs_free char *tmp = NULL;
val = *p ?: not_set_str;
print_val = colorize_string (nmc_config, field_values[idx].color,
val, &val_to_free);
tmp = g_strdup_printf ("%s%s%s[%d]:",
section_prefix ? (const char*) field_values[0].value : "",
section_prefix ? "." : "",
nm_meta_abstract_info_get_name (field_values[idx].info, FALSE),
j);
width1 = strlen (tmp);
width2 = nmc_string_screen_width (tmp, NULL);
g_print ("%-*s%s\n",
(int) (nmc_config->print_output == NMC_PRINT_TERSE
? 0
: ML_VALUE_INDENT + width1 - width2),
tmp,
print_val);
}
} else {
gs_free char *val_to_free = NULL;
gs_free char *tmp = NULL;
const char *hdr_name = (const char*) field_values[0].value;
const char *val = (const char*) field_values[idx].value;
const char *print_val;
/* value is a string */
val = val && *val ? val : not_set_str;
print_val = colorize_string (nmc_config, field_values[idx].color,
val, &val_to_free);
tmp = g_strdup_printf ("%s%s%s:",
section_prefix ? hdr_name : "",
section_prefix ? "." : "",
nm_meta_abstract_info_get_name (field_values[idx].info, FALSE));
width1 = strlen (tmp);
width2 = nmc_string_screen_width (tmp, NULL);
g_print ("%-*s%s\n",
(int) ( nmc_config->print_output == NMC_PRINT_TERSE
? 0
: ML_VALUE_INDENT + width1 - width2),
tmp,
print_val);
}
}
if (nmc_config->print_output == NMC_PRINT_PRETTY) {
gs_free char *line = NULL;
g_print ("%s\n", (line = g_strnfill (ML_HEADER_WIDTH, '-')));
}
return;
}
/* --- Tabular mode: each line = one object --- */
str = g_string_new (NULL);
for (i = 0; i < indices->len; i++) {
gs_free char *val_to_free = NULL;
int idx;
const char *value;
idx = g_array_index (indices, int, i);
value = get_value_to_print (nmc_config, (NmcOutputField *) field_values+idx, field_names,
not_set_str, &val_to_free);
if (nmc_config->print_output == NMC_PRINT_TERSE) {
if (nmc_config->escape_values) {
const char *p = value;
while (*p) {
if (*p == ':' || *p == '\\')
g_string_append_c (str, '\\'); /* Escaping by '\' */
g_string_append_c (str, *p);
p++;
}
}
else
g_string_append_printf (str, "%s", value);
g_string_append_c (str, ':'); /* Column separator */
} else {
width1 = strlen (value);
width2 = nmc_string_screen_width (value, NULL); /* Width of the string (in screen columns) */
g_string_append_printf (str, "%-*s", field_values[idx].width + width1 - width2, strlen (value) > 0 ? value : not_set_str);
g_string_append_c (str, ' '); /* Column separator */
table_width += field_values[idx].width + width1 - width2 + 1;
}
}
/* Print actual values */
if (str->len > 0) {
g_string_truncate (str, str->len-1); /* Chop off last column separator */
if (indent > 0) {
gs_free char *indent_str = NULL;
g_string_prepend (str, (indent_str = g_strnfill (indent, ' ')));
}
g_print ("%s\n", str->str);
/* Print horizontal separator */
if ( nmc_config->print_output == NMC_PRINT_PRETTY
&& field_names) {
gs_free char *line = NULL;
g_print ("%s\n", (line = g_strnfill (table_width, '-')));
}
}
}
void
print_data_prepare_width (GPtrArray *output_data)
{
int i, j;
size_t len;
NmcOutputField *row;
int num_fields = 0;
if (!output_data || output_data->len < 1)
return;
/* How many fields? */
row = g_ptr_array_index (output_data, 0);
while (row->info) {
num_fields++;
row++;
}
/* Find out maximal string lengths */
for (i = 0; i < num_fields; i++) {
size_t max_width = 0;
for (j = 0; j < output_data->len; j++) {
gboolean field_names;
gs_free char * val_to_free = NULL;
const char *value;
row = g_ptr_array_index (output_data, j);
field_names = row[0].flags & NMC_OF_FLAG_FIELD_NAMES;
value = get_value_to_print (NULL, row+i, field_names, "--", &val_to_free);
len = nmc_string_screen_width (value, NULL);
max_width = len > max_width ? len : max_width;
}
for (j = 0; j < output_data->len; j++) {
row = g_ptr_array_index (output_data, j);
row[i].width = max_width + 1;
}
}
}
void
print_data (const NmcConfig *nmc_config,
NmcPagerData *pager_data,
const GArray *indices,
const char *header_name,
int indent,
const NmcOutputData *out)
{
guint i;
for (i = 0; i < out->output_data->len; i++) {
const NmcOutputField *field_values = g_ptr_array_index (out->output_data, i);
print_required_fields (nmc_config,
pager_data,
field_values[0].flags,
indices,
header_name,
indent,
field_values);
}
}