/* 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. * * (C) Copyright 2010 - 2012 Red Hat, Inc. */ /* Generated configuration file */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "utils.h" int matches (const char *cmd, const char *pattern) { int len = strlen (cmd); if (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; } /* * 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 * argv: command-line argument array size * 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 && 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 printable form. * If it is an UTF-8 string, enclose it in quotes and return it. * Otherwise convert it to a hex string representation. * Caller has to free the returned string using g_free() */ char * ssid_to_printable (const char *str, gsize len) { GString *printable; char *printable_str; int i; if (str == NULL || len == 0) return NULL; if (g_utf8_validate (str, len, NULL)) return g_strdup_printf ("'%.*s'", (int) len, str); 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; } /* * Converts IPv4 address from guint32 in network-byte order to text representation. * Returns: text form of the IP or NULL (then error is set) */ char * nmc_ip4_address_as_string (guint32 ip, GError **error) { struct in_addr tmp_addr; char buf[INET_ADDRSTRLEN]; g_return_val_if_fail (error == NULL || *error == NULL, NULL); memset (&buf, '\0', sizeof (buf)); tmp_addr.s_addr = ip; if (inet_ntop (AF_INET, &tmp_addr, buf, INET_ADDRSTRLEN)) { return g_strdup (buf); } else { g_set_error (error, NMCLI_ERROR, 0, _("Error converting IP4 address '0x%X' to text form"), ntohl (tmp_addr.s_addr)); return NULL; } } /* * Converts IPv6 address in in6_addr structure to text representation. * Returns: text form of the IP or NULL (then error is set) */ char * nmc_ip6_address_as_string (const struct in6_addr *ip, GError **error) { char buf[INET6_ADDRSTRLEN]; g_return_val_if_fail (error == NULL || *error == NULL, NULL); memset (&buf, '\0', sizeof (buf)); if (inet_ntop (AF_INET6, ip, buf, INET6_ADDRSTRLEN)) { return g_strdup (buf); } else { if (error) { int j; GString *ip6_str = g_string_new (NULL); g_string_append_printf (ip6_str, "%02X", ip->s6_addr[0]); for (j = 1; j < 16; j++) g_string_append_printf (ip6_str, " %02X", ip->s6_addr[j]); g_set_error (error, NMCLI_ERROR, 0, _("Error converting IP6 address '%s' to text form"), ip6_str->str); g_string_free (ip6_str, TRUE); } return NULL; } } /* * 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) { 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 (); printf ("%c %s", slashes[idx++], str ? str : ""); fflush (stdout); if (idx == 4) idx = 0; } /* * 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); } /* * 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; fprintf (stdout, "%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, char ***argv, int *argc) { int i = 0; char **arr; arr = g_strsplit_set (line ? line : "", delim ? delim : " \t", 0); while (arr && arr[i]) i++; *argc = i; *argv = 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); g_set_error (error, 1, 0, _("'%s' not among [%s]"), input ? input : "", valid_vals); g_free (valid_vals); } return ret; } /* * 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; if (end == NULL) end = start + strlen (start); while (start < end) { width += g_unichar_iswide (g_utf8_get_char (start)) ? 2 : g_unichar_iszerowidth (g_utf8_get_char (start)) ? 0 : 1; start = g_utf8_next_char (start); } return width; } void set_val_str (NmcOutputField fields_array[], guint32 idx, char *value) { fields_array[idx].flags = 0; fields_array[idx].value = value; } void set_val_arr (NmcOutputField fields_array[], guint32 idx, char **value) { fields_array[idx].flags = NMC_OF_FLAG_ARRAY; fields_array[idx].value = value; } /* * Free 'value' members in array of NmcOutputField */ void nmc_free_output_field_values (NmcOutputField fields_array[]) { int idx = 0; while (fields_array && fields_array[idx].value) { if (fields_array[idx].flags & NMC_OF_FLAG_ARRAY) g_strfreev (fields_array[idx].value); else g_free (fields_array[idx].value); idx++; } } /* * Parse comma separated fields in 'fields_str' according to 'fields_array'. * IN: 'field_str': comma-separated fields names * 'fields_array': array of allowed fields * RETURN: GArray with indices representing fields in 'fields_array'. * Caller is responsible to free it. */ GArray * parse_output_fields (const char *fields_str, const NmcOutputField fields_array[], GError **error) { char **fields, **iter; GArray *array; int i; g_return_val_if_fail (error == NULL || *error == NULL, NULL); array = g_array_new (FALSE, FALSE, sizeof (int)); /* Split supplied fields string */ fields = g_strsplit_set (fields_str, ",", -1); for (iter = fields; iter && *iter; iter++) { for (i = 0; fields_array[i].name; i++) { if (strcasecmp (*iter, fields_array[i].name) == 0) { g_array_append_val (array, i); break; } } if (fields_array[i].name == NULL) { if (!strcasecmp (*iter, "all") || !strcasecmp (*iter, "common")) g_set_error (error, NMCLI_ERROR, 0, _("field '%s' has to be alone"), *iter); else g_set_error (error, NMCLI_ERROR, 1, _("invalid field '%s'"), *iter); g_array_free (array, TRUE); array = NULL; goto done; } } done: if (fields) g_strfreev (fields); return array; } 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; } /* * Print both headers or values of 'field_values' array. * Entries to print and their order are specified via indices * in 'fields.indices' array. * 'fields.flags' specify various aspects influencing the output. */ void print_fields (const NmcPrintFields fields, 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; gboolean multiline = fields.flags & NMC_PF_FLAG_MULTILINE; gboolean terse = fields.flags & NMC_PF_FLAG_TERSE; gboolean pretty = fields.flags & NMC_PF_FLAG_PRETTY; gboolean main_header_add = fields.flags & NMC_PF_FLAG_MAIN_HEADER_ADD; gboolean main_header_only = fields.flags & NMC_PF_FLAG_MAIN_HEADER_ONLY; gboolean field_names = fields.flags & NMC_PF_FLAG_FIELD_NAMES; gboolean escape = fields.flags & NMC_PF_FLAG_ESCAPE; gboolean section_prefix = fields.flags & NMC_PF_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); printf ("%s\n", line); printf ("%*s\n", (table_width + width2)/2 + width1 - width2, fields.header_name); printf ("%s\n", line); g_free (line); } /* Print values */ if (!main_header_only && !field_names) { for (i = 0; i < fields.indices->len; i++) { char *tmp; int idx = g_array_index (fields.indices, int, i); guint32 value_is_array = field_values[idx].flags & NMC_OF_FLAG_ARRAY; /* section prefix can't be an array */ g_assert (!value_is_array || !section_prefix || idx != 0); if (section_prefix && idx == 0) /* The first field is section prefix */ continue; if (value_is_array) { /* value is a null-terminated string array */ const char **p; int j; for (p = (const char **) field_values[idx].value, j = 1; p && *p; p++, j++) { tmp = g_strdup_printf ("%s%s%s[%d]:", section_prefix ? (const char*) field_values[0].value : "", section_prefix ? "." : "", _(field_values[idx].name_l10n), j); printf ("%-*s%s\n", terse ? 0 : ML_VALUE_INDENT, tmp, *p ? *p : not_set_str); g_free (tmp); } } else { /* value is a string */ const char *hdr_name = (const char*) field_values[0].value; const char *val = (const char*) field_values[idx].value; tmp = g_strdup_printf ("%s%s%s:", section_prefix ? hdr_name : "", section_prefix ? "." : "", _(field_values[idx].name_l10n)); printf ("%-*s%s\n", terse ? 0 : ML_VALUE_INDENT, tmp, val ? val : not_set_str); g_free (tmp); } } if (pretty) { line = g_strnfill (ML_HEADER_WIDTH, '-'); printf ("%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); guint32 value_is_array = field_values[idx].flags & NMC_OF_FLAG_ARRAY; char *value; if (field_names) value = _(field_values[idx].name_l10n); else value = field_values[idx].value ? (value_is_array ? g_strjoinv (" | ", (char **) field_values[idx].value) : (char *) field_values[idx].value) : (char *) not_set_str; 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 (value_is_array && field_values[idx].value && !field_values) 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); printf ("%s\n", line); printf ("%*s\n", (table_width + width2)/2 + width1 - width2, fields.header_name); printf ("%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); } printf ("%s\n", str->str); } /* Print horizontal separator */ if (!main_header_only && field_names && pretty) { if (str->len > 0) { line = g_strnfill (table_width, '-'); printf ("%s\n", line); g_free (line); } } g_string_free (str, TRUE); } /* * Compare versions of nmcli and NM daemon. * Return: TRUE - the versions match (when only major and minor match, print a warning) * FALSE - versions mismatch */ gboolean nmc_versions_match (NmCli *nmc) { const char *nm_ver = NULL; const char *dot; gboolean match = FALSE; g_return_val_if_fail (nmc != NULL, FALSE); /* --nocheck option - don't compare the versions */ if (nmc->nocheck_ver) return TRUE; nmc->get_client (nmc); nm_ver = nm_client_get_version (nmc->client); if (nm_ver) { if (!strcmp (nm_ver, VERSION)) match = TRUE; else { dot = strchr (nm_ver, '.'); if (dot) { dot = strchr (dot + 1, '.'); if (dot && !strncmp (nm_ver, VERSION, dot-nm_ver)) { fprintf(stderr, _("Warning: nmcli (%s) and NetworkManager (%s) versions don't match. Use --nocheck to suppress the warning.\n"), VERSION, nm_ver); match = TRUE; } } } } if (!match) { g_string_printf (nmc->return_text, _("Error: nmcli (%s) and NetworkManager (%s) versions don't match. Force execution using --nocheck, but the results are unpredictable."), VERSION, nm_ver ? nm_ver : _("unknown")); nmc->return_value = NMC_RESULT_ERROR_VERSIONS_MISMATCH; } return match; }