summary refs log tree commit diff
path: root/clients/cli/utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'clients/cli/utils.c')
-rw-r--r--clients/cli/utils.c1821
1 files changed, 0 insertions, 1821 deletions
diff --git a/clients/cli/utils.c b/clients/cli/utils.c
deleted file mode 100644
index 0a62c35c..00000000
--- a/clients/cli/utils.c
+++ /dev/null
@@ -1,1821 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2010 Lennart Poettering
- * Copyright (C) 2010 - 2018 Red Hat, Inc.
- */
-
-#include "libnm/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 "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;
-    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  = &current_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);
-    }
-}