1822 lines
60 KiB
C
1822 lines
60 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Copyright (C) 2010 Lennart Poettering
|
|
* Copyright (C) 2010 - 2018 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "libnm-client-aux-extern/nm-default-client.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 "libnmc-base/nm-client-utils.h"
|
|
#include "libnmc-setting/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;
|
|
gs_free char *str = NULL;
|
|
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.ansi_seq[color];
|
|
|
|
if (!ansi_seq)
|
|
return g_steal_pointer(&str);
|
|
|
|
return g_strdup_printf("\33[%sm%s\33[0m", ansi_seq, str);
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
}
|