/* nmcli - command-line tool to control NetworkManager * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright 2010 - 2015 Red Hat, Inc. */ #include "nm-default.h" #include #include #include #include #include #include #include #include "utils.h" int matches (const char *cmd, const char *pattern) { size_t len = strlen (cmd); if (!len || len > strlen (pattern)) return -1; return memcmp (pattern, cmd, len); } int next_arg (int *argc, char ***argv) { int arg_num = *argc; if (arg_num > 0) { (*argc)--; (*argv)++; } if (arg_num <= 1) return -1; return 0; } gboolean nmc_arg_is_help (const char *arg) { if (!arg) return FALSE; if ( matches (arg, "help") == 0 || (g_str_has_prefix (arg, "-") && matches (arg+1, "help") == 0) || (g_str_has_prefix (arg, "--") && matches (arg+2, "help") == 0)) { 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) == 0) : 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, char ***argv, GError **error) { nmc_arg_t *p; gboolean found; gboolean have_mandatory; g_return_val_if_fail (arg_arr != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); while (*argc > 0) { found = FALSE; for (p = arg_arr; p->name; p++) { if (strcmp (**argv, p->name) == 0) { if (p->found) { /* Don't allow repeated arguments, because the argument of the same * name could be used later on the line for another purpose. Assume * that's the case and return. */ return TRUE; } if (p->has_value) { if (next_arg (argc, argv) != 0) { 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 (argc, argv); } 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) { GString *printable; char *printable_str; int i; if (str == NULL || len == 0) return NULL; printable = g_string_new (NULL); for (i = 0; i < len; i++) { g_string_append_printf (printable, "%02X", (unsigned char) str[i]); } printable_str = g_string_free (printable, FALSE); return printable_str; } /* * Erase terminal line using ANSI escape sequences. * It prints [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 ? str : ""); fflush (stdout); if (idx == 4) idx = 0; } const char * nmc_term_color_sequence (NmcTermColor color) { switch (color) { case NMC_TERM_COLOR_BLACK: return "\33[30m"; break; case NMC_TERM_COLOR_RED: return "\33[31m"; break; case NMC_TERM_COLOR_GREEN: return "\33[32m"; break; case NMC_TERM_COLOR_YELLOW: return "\33[33m"; break; case NMC_TERM_COLOR_BLUE: return "\33[34m"; break; case NMC_TERM_COLOR_MAGENTA: return "\33[35m"; break; case NMC_TERM_COLOR_CYAN: return "\33[36m"; break; case NMC_TERM_COLOR_WHITE: return "\33[37m"; break; default: return ""; break; } } /* Parses @str for color as string or number */ NmcTermColor nmc_term_color_parse_string (const char *str, GError **error) { unsigned long color_int; static const char *colors[] = { "normal", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", NULL }; if (nmc_string_to_uint (str, TRUE, 0, 8, &color_int)) { return (NmcTermColor) color_int; } else { const char *color, **p; int i; color = nmc_string_is_valid (str, colors, error); for (p = colors, i = 0; *p != NULL; p++, i++) { if (*p == color) return (NmcTermColor) i; } return -1; } } const char * nmc_term_format_sequence (NmcTermFormat format) { switch (format) { case NMC_TERM_FORMAT_BOLD: return "\33[1m"; break; case NMC_TERM_FORMAT_DIM: return "\33[2m"; break; case NMC_TERM_FORMAT_UNDERLINE: return "\33[4m"; break; case NMC_TERM_FORMAT_BLINK: return "\33[5m"; break; case NMC_TERM_FORMAT_REVERSE: return "\33[7m"; break; case NMC_TERM_FORMAT_HIDDEN: return "\33[8m"; break; default: return ""; break; } } static gboolean use_colors (NmCli *nmc) { if (nmc == NULL) return FALSE; if (nmc->use_colors == NMC_USE_COLOR_AUTO) { if ( g_strcmp0 (g_getenv ("TERM"), "dumb") == 0 || !isatty (fileno (stdout))) nmc->use_colors = NMC_USE_COLOR_NO; else nmc->use_colors = NMC_USE_COLOR_YES; } return nmc->use_colors == NMC_USE_COLOR_YES; } char * nmc_colorize (NmCli *nmc, NmcTermColor color, NmcTermFormat format, const char *fmt, ...) { va_list args; char *str, *colored; const char *ansi_color, *color_end, *ansi_fmt, *format_end; static const char *end_seq = "\33[0m"; va_start (args, fmt); str = g_strdup_vprintf (fmt, args); va_end (args); if (!use_colors (nmc)) return str; ansi_color = nmc_term_color_sequence (color); ansi_fmt = nmc_term_format_sequence (format); color_end = *ansi_color ? end_seq : ""; format_end = *ansi_fmt ? end_seq : ""; colored = g_strdup_printf ("%s%s%s%s%s", ansi_fmt, ansi_color, str, color_end, format_end); g_free (str); return colored; } /* * Count characters belonging to terminal color escape sequences. * @start points to beginning of the string, @end points to the end, * or NULL if the string is nul-terminated. */ static int nmc_count_color_escape_chars (const char *start, const char *end) { int num = 0; gboolean inside = FALSE; if (end == NULL) end = start + strlen (start); while (start < end) { if (*start == '\33' && *(start+1) == '[') inside = TRUE; if (inside) num++; if (*start == 'm') inside = FALSE; start++; } return num; } /* Filter out possible ANSI color escape sequences */ /* It directly modifies the passed string @str. */ void nmc_filter_out_colors_inplace (char *str) { const char *p1; char *p2; gboolean copy_char = TRUE; if (!str) return; p1 = p2 = str; while (*p1) { if (*p1 == '\33' && *(p1+1) == '[') copy_char = FALSE; if (copy_char) *p2++ = *p1; if (!copy_char && *p1 == 'm') copy_char = TRUE; p1++; } *p2 = '\0'; } /* Filter out possible ANSI color escape sequences */ char * nmc_filter_out_colors (const char *str) { char *filtered; if (!str) return NULL; filtered = g_strdup (str); nmc_filter_out_colors_inplace (filtered); return filtered; } /* * Convert string to signed integer. * If required, the resulting number is checked to be in the range. */ gboolean nmc_string_to_int_base (const char *str, int base, gboolean range_check, long int min, long int max, long int *value) { char *end; long int tmp; errno = 0; tmp = strtol (str, &end, base); if (errno || *end != '\0' || (range_check && (tmp < min || tmp > max))) { return FALSE; } *value = tmp; return TRUE; } /* * Convert string to unsigned integer. * If required, the resulting number is checked to be in the range. */ gboolean nmc_string_to_uint_base (const char *str, int base, gboolean range_check, unsigned long int min, unsigned long int max, unsigned long int *value) { char *end; unsigned long int tmp; errno = 0; tmp = strtoul (str, &end, base); if (errno || *end != '\0' || (range_check && (tmp < min || tmp > max))) { return FALSE; } *value = tmp; return TRUE; } gboolean nmc_string_to_int (const char *str, gboolean range_check, long int min, long int max, long int *value) { return nmc_string_to_int_base (str, 10, range_check, min, max, value); } gboolean nmc_string_to_uint (const char *str, gboolean range_check, unsigned long int min, unsigned long int max, unsigned long int *value) { return nmc_string_to_uint_base (str, 10, range_check, min, max, value); } gboolean nmc_string_to_bool (const char *str, gboolean *val_bool, GError **error) { const char *s_true[] = { "true", "yes", "on", NULL }; const char *s_false[] = { "false", "no", "off", NULL }; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (g_strcmp0 (str, "o") == 0) { g_set_error (error, 1, 0, /* Translators: the first %s is the partial value entered by * the user, the second %s a list of compatible values. */ _("'%s' is ambiguous (%s)"), str, "on x off"); return FALSE; } if (nmc_string_is_valid (str, s_true, NULL)) *val_bool = TRUE; else if (nmc_string_is_valid (str, s_false, NULL)) *val_bool = FALSE; else { g_set_error (error, 1, 0, _("'%s' is not valid; use [%s] or [%s]"), str, "true, yes, on", "false, no, off"); return FALSE; } return TRUE; } gboolean nmc_string_to_tristate (const char *str, NMCTriStateValue *val, GError **error) { const char *s_true[] = { "true", "yes", "on", NULL }; const char *s_false[] = { "false", "no", "off", NULL }; const char *s_unknown[] = { "unknown", NULL }; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (g_strcmp0 (str, "o") == 0) { g_set_error (error, 1, 0, /* Translators: the first %s is the partial value entered by * the user, the second %s a list of compatible values. */ _("'%s' is ambiguous (%s)"), str, "on x off"); return FALSE; } if (nmc_string_is_valid (str, s_true, NULL)) *val = NMC_TRI_STATE_YES; else if (nmc_string_is_valid (str, s_false, NULL)) *val = NMC_TRI_STATE_NO; else if (nmc_string_is_valid (str, s_unknown, NULL)) *val = NMC_TRI_STATE_UNKNOWN; else { g_set_error (error, 1, 0, _("'%s' is not valid; use [%s], [%s] or [%s]"), str, "true, yes, on", "false, no, off", "unknown"); return FALSE; } return TRUE; } /* * 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) { char **arr; arr = nmc_strsplit_set (line ? line : "", delim ? delim : " \t", 0); if (unquote) { int i = 0; char *s; size_t l; const char *quotes = "\"'"; while (arr && 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; } /* * Check whether 'input' is contained in 'allowed' array. It performs case * insensitive comparison and supports shortcut strings if they are unique. * Returns: a pointer to found string in allowed array on success or NULL. * On failure: error->code : 0 - string not found; 1 - string is ambiguous */ const char * nmc_string_is_valid (const char *input, const char **allowed, GError **error) { const char **p; size_t input_ln, p_len; gboolean prev_match = FALSE; const char *ret = NULL; g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (!input || !*input) goto finish; input_ln = strlen (input); for (p = allowed; p && *p; p++) { p_len = strlen (*p); if (g_ascii_strncasecmp (input, *p, input_ln) == 0) { if (input_ln == p_len) { ret = *p; break; } if (!prev_match) ret = *p; else { g_set_error (error, 1, 1, _("'%s' is ambiguous (%s x %s)"), input, ret, *p); return NULL; } prev_match = TRUE; } } finish: if (ret == NULL) { char *valid_vals = g_strjoinv (", ", (char **) allowed); if (!input || !*input) g_set_error (error, 1, 0, _("missing name, try one of [%s]"), valid_vals); else g_set_error (error, 1, 0, _("'%s' not among [%s]"), input, valid_vals); g_free (valid_vals); } return ret; } /* * 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 **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); } /* * Wrapper function for g_strsplit_set() that removes empty strings * from the vector as they are not useful in most cases. */ char ** nmc_strsplit_set (const char *str, const char *delimiter, int max_tokens) { char **result; uint i; uint j; result = g_strsplit_set (str, delimiter, max_tokens); /* remove empty strings */ for (i = 0; result && result[i]; i++) { if (*(result[i]) == '\0') { g_free (result[i]); for (j = i; result[j]; j++) result[j] = result[j + 1]; i--; } } return result; } /* * 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[], NmcTermColor color) { int i; for (i = 0; fields_array[i].name; i++) { fields_array[i].color = color; } } void set_val_color_fmt_all (NmcOutputField fields_array[], NmcTermFormat format) { int i; for (i = 0; fields_array[i].name; i++) { fields_array[i].color_fmt = format; } } /* * Free 'value' members in array of NmcOutputField */ void nmc_free_output_field_values (NmcOutputField fields_array[]) { NmcOutputField *iter = fields_array; while (iter && iter->name) { if (iter->free_value) { if (iter->value_is_array) g_strfreev ((char **) iter->value); else g_free ((char *) iter->value); iter->value = NULL; } iter++; } } /** * 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 NmcOutputField fields_array[], gboolean parse_groups, GPtrArray **group_fields, GError **error) { char **fields, **iter; GArray *array; int i, j; g_return_val_if_fail (error == NULL || *error == NULL, NULL); g_return_val_if_fail (group_fields == NULL || *group_fields == NULL, NULL); array = g_array_new (FALSE, FALSE, sizeof (int)); if (parse_groups && group_fields) *group_fields = g_ptr_array_new_full (20, (GDestroyNotify) g_free); /* Split supplied fields string */ fields = g_strsplit_set (fields_str, ",", -1); for (iter = fields; iter && *iter; iter++) { int idx = -1; g_strstrip (*iter); if (parse_groups) { /* e.g. "general.device,general.driver,ip4,ip6" */ gboolean found = FALSE; char *left = *iter; char *right = strchr (*iter, '.'); if (right) *right++ = '\0'; for (i = 0; fields_array[i].name; i++) { if (strcasecmp (left, fields_array[i].name) == 0) { NmcOutputField *valid_names = fields_array[i].group; idx = i; if (!right && !valid_names) { found = TRUE; break; } for (j = 0; valid_names && valid_names[j].name; j++) { if (!right || strcasecmp (right, valid_names[j].name) == 0) { found = TRUE; break; } } if (found) break; } } if (found) { /* Add index to array, and field name (or NULL) to group_fields array */ g_array_append_val (array, idx); if (group_fields && *group_fields) g_ptr_array_add (*group_fields, g_strdup (right)); } if (right) *(right-1) = '.'; /* Restore the original string */ } else { /* e.g. "general,ip4,ip6" */ for (i = 0; fields_array[i].name; i++) { if (strcasecmp (*iter, fields_array[i].name) == 0) { g_array_append_val (array, i); break; } } } /* Field was not found - error case */ if (fields_array[i].name == NULL) { /* Set GError */ if (!strcasecmp (*iter, "all") || !strcasecmp (*iter, "common")) g_set_error (error, NMCLI_ERROR, 0, _("field '%s' has to be alone"), *iter); else { char *allowed_fields = nmc_get_allowed_fields (fields_array, idx); g_set_error (error, NMCLI_ERROR, 1, _("invalid field '%s'; allowed fields: %s"), *iter, allowed_fields); g_free (allowed_fields); } /* Free arrays on error */ g_array_free (array, TRUE); array = NULL; if (group_fields && *group_fields) { g_ptr_array_free (*group_fields, TRUE); *group_fields = NULL; } goto done; } } done: if (fields) g_strfreev (fields); return array; } /** * nmc_get_allowed_fields: * @fields_array: array of fields * @group_idx: index to the array (for second-level array in 'group' member), * or -1 * * Returns: string of allowed fields names. * Caller is responsible for freeing the array. */ char * nmc_get_allowed_fields (const NmcOutputField fields_array[], int group_idx) { GString *allowed_fields = g_string_sized_new (256); int i; if (group_idx != -1 && fields_array[group_idx].group) { NmcOutputField *second_level = fields_array[group_idx].group; for (i = 0; second_level[i].name; i++) g_string_append_printf (allowed_fields, "%s.%s,", fields_array[group_idx].name, second_level[i].name); } else { for (i = 0; fields_array[i].name; i++) g_string_append_printf (allowed_fields, "%s,", fields_array[i].name); } g_string_truncate (allowed_fields, allowed_fields->len - 1); return g_string_free (allowed_fields, FALSE); } gboolean nmc_terse_option_check (NMCPrintOutput print_output, const char *fields, GError **error) { g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (print_output == NMC_PRINT_TERSE) { if (!fields) { g_set_error_literal (error, NMCLI_ERROR, 0, _("Option '--terse' requires specifying '--fields'")); return FALSE; } else if ( !strcasecmp (fields, "all") || !strcasecmp (fields, "common")) { g_set_error (error, NMCLI_ERROR, 0, _("Option '--terse' requires specific '--fields' option values , not '%s'"), fields); return FALSE; } } return TRUE; } NmcOutputField * nmc_dup_fields_array (NmcOutputField fields[], size_t size, guint32 flags) { NmcOutputField *row; row = g_malloc0 (size); memcpy (row, fields, size); row[0].flags = flags; return row; } void nmc_empty_output_fields (NmCli *nmc) { guint i; /* Free values in field structure */ for (i = 0; i < nmc->output_data->len; i++) { NmcOutputField *fld_arr = g_ptr_array_index (nmc->output_data, i); nmc_free_output_field_values (fld_arr); } /* Empty output_data array */ if (nmc->output_data->len > 0) g_ptr_array_remove_range (nmc->output_data, 0, nmc->output_data->len); if (nmc->print_fields.indices) { g_array_free (nmc->print_fields.indices, TRUE); nmc->print_fields.indices = NULL; } } static char * colorize_string (NmCli *nmc, NmcTermColor color, NmcTermFormat color_fmt, const char *str, gboolean *dealloc) { char *out; if ( use_colors (nmc) && (color != NMC_TERM_COLOR_NORMAL || color_fmt != NMC_TERM_FORMAT_NORMAL)) { out = nmc_colorize (nmc, color, color_fmt, "%s", str); *dealloc = TRUE; } else { out = (char *) str; *dealloc = FALSE; } return out; } static char * get_value_to_print (NmCli *nmc, NmcOutputField *field, gboolean field_name, const char *not_set_str, gboolean *dealloc) { gboolean is_array = field->value_is_array; char *value, *out; gboolean free_value, free_out; if (field_name) value = _(field->name_l10n); else value = field->value ? (is_array ? g_strjoinv (" | ", (char **) field->value) : (char *) field->value) : (char *) not_set_str; free_value = field->value && is_array && !field_name; /* colorize the value */ out = colorize_string (nmc, field->color, field->color_fmt, value, &free_out); if (free_out) { if (free_value) g_free (value); *dealloc = TRUE; } else *dealloc = 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->print_fields.indices' array. * Various flags influencing the output of fields are set up in the first item * of 'field_values' array. */ void print_required_fields (NmCli *nmc, const NmcOutputField field_values[]) { GString *str; int width1, width2; int table_width = 0; char *line = NULL; char *indent_str; const char *not_set_str = "--"; int i; const NmcPrintFields fields = nmc->print_fields; gboolean multiline = nmc->multiline_output; gboolean terse = (nmc->print_output == NMC_PRINT_TERSE); gboolean pretty = (nmc->print_output == NMC_PRINT_PRETTY); gboolean escape = nmc->escape_values; gboolean main_header_add = field_values[0].flags & NMC_OF_FLAG_MAIN_HEADER_ADD; gboolean main_header_only = field_values[0].flags & NMC_OF_FLAG_MAIN_HEADER_ONLY; gboolean field_names = field_values[0].flags & NMC_OF_FLAG_FIELD_NAMES; gboolean section_prefix = field_values[0].flags & NMC_OF_FLAG_SECTION_PREFIX; gboolean main_header = main_header_add || main_header_only; /* No headers are printed in terse mode: * - neither main header nor field (column) names */ if ((main_header_only || field_names) && terse) return; if (multiline) { /* --- Multiline mode --- */ enum { ML_HEADER_WIDTH = 79 }; enum { ML_VALUE_INDENT = 40 }; if (main_header && pretty) { /* Print the main header */ int header_width = nmc_string_screen_width (fields.header_name, NULL) + 4; table_width = header_width < ML_HEADER_WIDTH ? ML_HEADER_WIDTH : header_width; line = g_strnfill (ML_HEADER_WIDTH, '='); width1 = strlen (fields.header_name); width2 = nmc_string_screen_width (fields.header_name, NULL); g_print ("%s\n", line); g_print ("%*s\n", (table_width + width2)/2 + width1 - width2, fields.header_name); g_print ("%s\n", line); g_free (line); } /* Print values */ if (!main_header_only && !field_names) { for (i = 0; i < fields.indices->len; i++) { char *tmp; gboolean free_print_val; int idx = g_array_index (fields.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) { /* value is a null-terminated string array */ const char **p, *val; char *print_val; int j; for (p = (const char **) field_values[idx].value, j = 1; p && *p; p++, j++) { val = *p ? *p : not_set_str; print_val = colorize_string (nmc, field_values[idx].color, field_values[idx].color_fmt, val, &free_print_val); tmp = g_strdup_printf ("%s%s%s[%d]:", section_prefix ? (const char*) field_values[0].value : "", section_prefix ? "." : "", _(field_values[idx].name_l10n), j); width1 = strlen (tmp); width2 = nmc_string_screen_width (tmp, NULL); g_print ("%-*s%s\n", terse ? 0 : ML_VALUE_INDENT+width1-width2, tmp, print_val); g_free (tmp); if (free_print_val) g_free (print_val); } } else { /* value is a string */ const char *hdr_name = (const char*) field_values[0].value; const char *val = (const char*) field_values[idx].value; char *print_val; val = val ? val : not_set_str; print_val = colorize_string (nmc, field_values[idx].color, field_values[idx].color_fmt, val, &free_print_val); tmp = g_strdup_printf ("%s%s%s:", section_prefix ? hdr_name : "", section_prefix ? "." : "", _(field_values[idx].name_l10n)); width1 = strlen (tmp); width2 = nmc_string_screen_width (tmp, NULL); g_print ("%-*s%s\n", terse ? 0 : ML_VALUE_INDENT+width1-width2, tmp, print_val); g_free (tmp); if (free_print_val) g_free (print_val); } } if (pretty) { line = g_strnfill (ML_HEADER_WIDTH, '-'); g_print ("%s\n", line); g_free (line); } } return; } /* --- Tabular mode: each line = one object --- */ str = g_string_new (NULL); for (i = 0; i < fields.indices->len; i++) { int idx = g_array_index (fields.indices, int, i); gboolean dealloc; char *value = get_value_to_print (nmc, (NmcOutputField *) field_values+idx, field_names, not_set_str, &dealloc); if (terse) { if (escape) { 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 colums) */ g_string_append_printf (str, "%-*s", field_values[idx].width + width1 - width2, strlen (value) > 0 ? value : "--"); g_string_append_c (str, ' '); /* Column separator */ table_width += field_values[idx].width + width1 - width2 + 1; } if (dealloc) g_free (value); } /* Print the main table header */ if (main_header && pretty) { int header_width = nmc_string_screen_width (fields.header_name, NULL) + 4; table_width = table_width < header_width ? header_width : table_width; line = g_strnfill (table_width, '='); width1 = strlen (fields.header_name); width2 = nmc_string_screen_width (fields.header_name, NULL); g_print ("%s\n", line); g_print ("%*s\n", (table_width + width2)/2 + width1 - width2, fields.header_name); g_print ("%s\n", line); g_free (line); } /* Print actual values */ if (!main_header_only && str->len > 0) { g_string_truncate (str, str->len-1); /* Chop off last column separator */ if (fields.indent > 0) { indent_str = g_strnfill (fields.indent, ' '); g_string_prepend (str, indent_str); g_free (indent_str); } g_print ("%s\n", str->str); } /* Print horizontal separator */ if (!main_header_only && field_names && pretty) { if (str->len > 0) { line = g_strnfill (table_width, '-'); g_print ("%s\n", line); g_free (line); } } g_string_free (str, TRUE); } /* * Print nmc->output_data * * It first finds out maximal string length in columns and fill the value to * 'width' member of NmcOutputField, so that columns in tabular output are * properly aligned. Then each object (row in tabular) is printed using * print_required_fields() function. */ void print_data (NmCli *nmc) { int i, j; size_t len; NmcOutputField *row; int num_fields = 0; if (!nmc->output_data || nmc->output_data->len < 1) return; /* How many fields? */ row = g_ptr_array_index (nmc->output_data, 0); while (row->name) { num_fields++; row++; } /* Find out maximal string lengths */ for (i = 0; i < num_fields; i++) { size_t max_width = 0; for (j = 0; j < nmc->output_data->len; j++) { gboolean field_names, dealloc; char *value; row = g_ptr_array_index (nmc->output_data, j); field_names = row[0].flags & NMC_OF_FLAG_FIELD_NAMES; value = get_value_to_print (NULL, row+i, field_names, "--", &dealloc); len = nmc_string_screen_width (value, NULL); max_width = len > max_width ? len : max_width; if (dealloc) g_free (value); } for (j = 0; j < nmc->output_data->len; j++) { row = g_ptr_array_index (nmc->output_data, j); row[i].width = max_width + 1; } } /* Now we can print the data. */ for (i = 0; i < nmc->output_data->len; i++) { row = g_ptr_array_index (nmc->output_data, i); print_required_fields (nmc, row); } }