diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /clients/cli/connections.c | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'clients/cli/connections.c')
| -rw-r--r-- | clients/cli/connections.c | 9645 |
1 files changed, 0 insertions, 9645 deletions
diff --git a/clients/cli/connections.c b/clients/cli/connections.c deleted file mode 100644 index fe4f8855..00000000 --- a/clients/cli/connections.c +++ /dev/null @@ -1,9645 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2010 - 2018 Red Hat, Inc. - */ - -#include "libnm/nm-default-client.h" - -#include "connections.h" - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <signal.h> -#include <readline/readline.h> -#include <readline/history.h> -#include <fcntl.h> - -#include "nm-client-utils.h" -#include "nm-vpn-helpers.h" -#include "nm-meta-setting-access.h" -#include "nm-secret-agent-simple.h" -#include "nm-glib-aux/nm-dbus-aux.h" - -#include "utils.h" -#include "common.h" -#include "settings.h" -#include "devices.h" -#include "polkit-agent.h" - -/*****************************************************************************/ - -typedef enum { - PROPERTY_INF_FLAG_NONE = 0x0, - PROPERTY_INF_FLAG_DISABLED = 0x1, /* Don't ask due to runtime decision. */ - PROPERTY_INF_FLAG_ENABLED = - 0x2, /* Override NM_META_PROPERTY_INF_FLAG_DONT_ASK due to runtime decision. */ - PROPERTY_INF_FLAG_ALL = 0x3, -} PropertyInfFlags; - -typedef char *(*CompEntryFunc)(const char *, int); - -typedef struct _OptionInfo { - const NMMetaSettingInfoEditor *setting_info; - const char * property; - const char * option; - gboolean (*check_and_set)(NmCli * nmc, - NMConnection * connection, - const struct _OptionInfo *option, - const char * value, - GError ** error); - CompEntryFunc generator_func; -} OptionInfo; - -/* define some prompts for connection editor */ -#define EDITOR_PROMPT_SETTING _("Setting name? ") -#define EDITOR_PROMPT_PROPERTY _("Property name? ") -#define EDITOR_PROMPT_CON_TYPE _("Enter connection type: ") - -/* define some other prompts */ - -#define PROMPT_CONNECTION _("Connection (name, UUID, or path): ") -#define PROMPT_VPN_CONNECTION _("VPN connection (name, UUID, or path): ") -#define PROMPT_CONNECTIONS _("Connection(s) (name, UUID, or path): ") -#define PROMPT_ACTIVE_CONNECTIONS _("Connection(s) (name, UUID, path or apath): ") - -#define BASE_PROMPT "nmcli> " - -/*****************************************************************************/ - -static NM_UTILS_LOOKUP_STR_DEFINE( - active_connection_state_to_string, - NMActiveConnectionState, - NM_UTILS_LOOKUP_DEFAULT(N_("unknown")), - NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_ACTIVATING, N_("activating")), - NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_ACTIVATED, N_("activated")), - NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, N_("deactivating")), - NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, N_("deactivated")), - NM_UTILS_LOOKUP_ITEM_IGNORE(NM_ACTIVE_CONNECTION_STATE_UNKNOWN), ); - -static NM_UTILS_LOOKUP_STR_DEFINE( - vpn_connection_state_to_string, - NMVpnConnectionState, - NM_UTILS_LOOKUP_DEFAULT(N_("unknown")), - NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_PREPARE, N_("VPN connecting (prepare)")), - NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_NEED_AUTH, - N_("VPN connecting (need authentication)")), - NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_CONNECT, N_("VPN connecting")), - NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_IP_CONFIG_GET, - N_("VPN connecting (getting IP configuration)")), - NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_ACTIVATED, N_("VPN connected")), - NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_FAILED, N_("VPN connection failed")), - NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_DISCONNECTED, N_("VPN disconnected")), - NM_UTILS_LOOKUP_ITEM_IGNORE(NM_VPN_CONNECTION_STATE_UNKNOWN), ); - -/*****************************************************************************/ - -typedef struct { - NmCli *nmc; - char * orig_id; - char * orig_uuid; - char * new_id; -} AddConnectionInfo; - -static AddConnectionInfo * -_add_connection_info_new(NmCli *nmc, NMConnection *orig_connection, NMConnection *new_connection) -{ - AddConnectionInfo *info; - - info = g_slice_new(AddConnectionInfo); - *info = (AddConnectionInfo){ - .nmc = nmc, - .orig_id = orig_connection ? g_strdup(nm_connection_get_id(orig_connection)) : NULL, - .orig_uuid = orig_connection ? g_strdup(nm_connection_get_uuid(orig_connection)) : NULL, - .new_id = g_strdup(nm_connection_get_id(new_connection)), - }; - return info; -} - -static void -_add_connection_info_free(AddConnectionInfo *info) -{ - g_free(info->orig_id); - g_free(info->orig_uuid); - g_free(info->new_id); - nm_g_slice_free(info); -} - -NM_AUTO_DEFINE_FCN(AddConnectionInfo *, - _nm_auto_free_add_connection_info, - _add_connection_info_free); - -#define nm_auto_free_add_connection_info nm_auto(_nm_auto_free_add_connection_info) - -/*****************************************************************************/ - -/* Essentially a version of nm_setting_connection_get_connection_type() that - * prefers an alias instead of the settings name when in pretty print mode. - * That is so that we print "wifi" instead of "802-11-wireless" in "nmcli c". */ -static const char * -connection_type_to_display(const char *type, NMMetaAccessorGetType get_type) -{ - const NMMetaSettingInfoEditor *editor; - int i; - - nm_assert( - NM_IN_SET(get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE)); - - if (!type) - return NULL; - - if (get_type != NM_META_ACCESSOR_GET_TYPE_PRETTY) - return type; - - for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { - editor = &nm_meta_setting_infos_editor[i]; - if (nm_streq(type, editor->general->setting_name)) - return editor->alias ?: type; - } - return type; -} - -static int -active_connection_get_state_ord(NMActiveConnection *active) -{ - /* returns an integer related to @active's state, that can be used for sorting - * active connections based on their activation state. */ - if (!active) - return -2; - - switch (nm_active_connection_get_state(active)) { - case NM_ACTIVE_CONNECTION_STATE_UNKNOWN: - return 0; - case NM_ACTIVE_CONNECTION_STATE_DEACTIVATED: - return 1; - case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING: - return 2; - case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: - return 3; - case NM_ACTIVE_CONNECTION_STATE_ACTIVATED: - return 4; - } - return -1; -} - -int -nmc_active_connection_cmp(NMActiveConnection *ac_a, NMActiveConnection *ac_b) -{ - NMSettingIPConfig * s_ip; - NMRemoteConnection *conn; - NMIPConfig * da_ip; - NMIPConfig * db_ip; - int da_num_addrs; - int db_num_addrs; - int cmp = 0; - - /* Non-active sort last. */ - NM_CMP_SELF(ac_a, ac_b); - NM_CMP_DIRECT(active_connection_get_state_ord(ac_b), active_connection_get_state_ord(ac_a)); - - /* Shared connections (likely hotspots) go on the top if possible */ - conn = nm_active_connection_get_connection(ac_a); - s_ip = conn ? nm_connection_get_setting_ip6_config(NM_CONNECTION(conn)) : NULL; - if (s_ip - && strcmp(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) - cmp++; - conn = nm_active_connection_get_connection(ac_b); - s_ip = conn ? nm_connection_get_setting_ip6_config(NM_CONNECTION(conn)) : NULL; - if (s_ip - && strcmp(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) - cmp--; - NM_CMP_RETURN(cmp); - - conn = nm_active_connection_get_connection(ac_a); - s_ip = conn ? nm_connection_get_setting_ip4_config(NM_CONNECTION(conn)) : NULL; - if (s_ip - && strcmp(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) - cmp++; - conn = nm_active_connection_get_connection(ac_b); - s_ip = conn ? nm_connection_get_setting_ip4_config(NM_CONNECTION(conn)) : NULL; - if (s_ip - && strcmp(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) - cmp--; - NM_CMP_RETURN(cmp); - - /* VPNs go next */ - NM_CMP_DIRECT(!!nm_active_connection_get_vpn(ac_a), !!nm_active_connection_get_vpn(ac_b)); - - /* Default devices are prioritized */ - NM_CMP_DIRECT(nm_active_connection_get_default(ac_a), nm_active_connection_get_default(ac_b)); - - /* Default IPv6 devices are prioritized */ - NM_CMP_DIRECT(nm_active_connection_get_default6(ac_a), nm_active_connection_get_default6(ac_b)); - - /* Sort by number of addresses. */ - da_ip = nm_active_connection_get_ip4_config(ac_a); - da_num_addrs = da_ip ? nm_ip_config_get_addresses(da_ip)->len : 0; - db_ip = nm_active_connection_get_ip4_config(ac_b); - db_num_addrs = db_ip ? nm_ip_config_get_addresses(db_ip)->len : 0; - - da_ip = nm_active_connection_get_ip6_config(ac_a); - da_num_addrs += da_ip ? nm_ip_config_get_addresses(da_ip)->len : 0; - db_ip = nm_active_connection_get_ip6_config(ac_b); - db_num_addrs += db_ip ? nm_ip_config_get_addresses(db_ip)->len : 0; - - NM_CMP_DIRECT(da_num_addrs, db_num_addrs); - - return 0; -} - -static char * -get_ac_device_string(NMActiveConnection *active) -{ - GString * dev_str; - const GPtrArray *devices; - guint i; - - if (!active) - return NULL; - - /* Get devices of the active connection */ - dev_str = g_string_new(NULL); - devices = nm_active_connection_get_devices(active); - for (i = 0; i < devices->len; i++) { - NMDevice * device = g_ptr_array_index(devices, i); - const char *dev_iface = nm_device_get_iface(device); - - if (dev_iface) { - g_string_append(dev_str, dev_iface); - g_string_append_c(dev_str, ','); - } - } - if (dev_str->len > 0) - g_string_truncate(dev_str, dev_str->len - 1); /* Cut off last ',' */ - - return g_string_free(dev_str, FALSE); -} - -/*****************************************************************************/ - -/* FIXME: The same or similar code for VPN info appears also in nm-applet (applet-dialogs.c), - * and in gnome-control-center as well. It could probably be shared somehow. */ - -static const char * -get_vpn_connection_type(NMConnection *connection) -{ - NMSettingVpn *s_vpn; - const char * type, *p; - - s_vpn = nm_connection_get_setting_vpn(connection); - if (!s_vpn) - return NULL; - - /* The service type is in form of "org.freedesktop.NetworkManager.vpnc". - * Extract end part after last dot, e.g. "vpnc" - */ - type = nm_setting_vpn_get_service_type(nm_connection_get_setting_vpn(connection)); - if (!type) - return NULL; - p = strrchr(type, '.'); - return p ? p + 1 : type; -} - -/* VPN parameters can be found at: - * http://git.gnome.org/browse/network-manager-openvpn/tree/src/nm-openvpn-service.h - * http://git.gnome.org/browse/network-manager-vpnc/tree/src/nm-vpnc-service.h - * http://git.gnome.org/browse/network-manager-pptp/tree/src/nm-pptp-service.h - * http://git.gnome.org/browse/network-manager-openconnect/tree/src/nm-openconnect-service.h - * http://git.gnome.org/browse/network-manager-openswan/tree/src/nm-openswan-service.h - * See also 'properties' directory in these plugins. - */ -static const char * -find_vpn_gateway_key(const char *vpn_type) -{ - if (vpn_type) { - if (nm_streq(vpn_type, "openvpn")) - return "remote"; - if (nm_streq(vpn_type, "vpnc")) - return "IPSec gateway"; - if (nm_streq(vpn_type, "pptp")) - return "gateway"; - if (nm_streq(vpn_type, "openconnect")) - return "gateway"; - if (nm_streq(vpn_type, "openswan")) - return "right"; - if (nm_streq(vpn_type, "libreswan")) - return "right"; - if (nm_streq(vpn_type, "ssh")) - return "remote"; - if (nm_streq(vpn_type, "l2tp")) - return "gateway"; - } - return NULL; -} - -static const char * -find_vpn_username_key(const char *vpn_type) -{ - if (vpn_type) { - if (nm_streq(vpn_type, "openvpn")) - return "username"; - if (nm_streq(vpn_type, "vpnc")) - return "Xauth username"; - if (nm_streq(vpn_type, "pptp")) - return "user"; - if (nm_streq(vpn_type, "openconnect")) - return "username"; - if (nm_streq(vpn_type, "openswan")) - return "leftxauthusername"; - if (nm_streq(vpn_type, "libreswan")) - return "leftxauthusername"; - if (nm_streq(vpn_type, "l2tp")) - return "user"; - } - return NULL; -} - -enum VpnDataItem { VPN_DATA_ITEM_GATEWAY, VPN_DATA_ITEM_USERNAME }; - -static const char * -get_vpn_data_item(NMConnection *connection, enum VpnDataItem vpn_data_item) -{ - const char *type; - const char *key = NULL; - - type = get_vpn_connection_type(connection); - - switch (vpn_data_item) { - case VPN_DATA_ITEM_GATEWAY: - key = find_vpn_gateway_key(type); - break; - case VPN_DATA_ITEM_USERNAME: - key = find_vpn_username_key(type); - break; - default: - break; - } - - if (!key) - return NULL; - return nm_setting_vpn_get_data_item(nm_connection_get_setting_vpn(connection), key); -} - -/*****************************************************************************/ - -typedef struct { - NMConnection * connection; - NMActiveConnection *primary_active; - GPtrArray * all_active; - bool show_active_fields; -} MetagenConShowRowData; - -static MetagenConShowRowData * -_metagen_con_show_row_data_new_for_connection(NMRemoteConnection *connection, - gboolean show_active_fields) -{ - MetagenConShowRowData *row_data; - - row_data = g_slice_new0(MetagenConShowRowData); - row_data->connection = g_object_ref(NM_CONNECTION(connection)); - row_data->show_active_fields = show_active_fields; - return row_data; -} - -static MetagenConShowRowData * -_metagen_con_show_row_data_new_for_active_connection(NMRemoteConnection *connection, - NMActiveConnection *active, - gboolean show_active_fields) -{ - MetagenConShowRowData *row_data; - - row_data = g_slice_new0(MetagenConShowRowData); - if (connection) - row_data->connection = g_object_ref(NM_CONNECTION(connection)); - row_data->primary_active = g_object_ref(active); - row_data->show_active_fields = show_active_fields; - return row_data; -} - -static void -_metagen_con_show_row_data_add_active_connection(MetagenConShowRowData *row_data, - NMActiveConnection * active) -{ - if (!row_data->primary_active) { - row_data->primary_active = g_object_ref(active); - return; - } - if (!row_data->all_active) { - row_data->all_active = g_ptr_array_new_with_free_func(g_object_unref); - g_ptr_array_add(row_data->all_active, g_object_ref(row_data->primary_active)); - } - g_ptr_array_add(row_data->all_active, g_object_ref(active)); -} - -static void -_metagen_con_show_row_data_init_primary_active(MetagenConShowRowData *row_data) -{ - NMActiveConnection *ac, *best_ac; - guint i; - - if (!row_data->all_active) - return; - - best_ac = row_data->all_active->pdata[0]; - for (i = 1; i < row_data->all_active->len; i++) { - ac = row_data->all_active->pdata[i]; - - if (active_connection_get_state_ord(ac) > active_connection_get_state_ord(best_ac)) - best_ac = ac; - } - - if (row_data->primary_active != best_ac) { - g_object_unref(row_data->primary_active); - row_data->primary_active = g_object_ref(best_ac); - } - nm_clear_pointer(&row_data->all_active, g_ptr_array_unref); -} - -static void -_metagen_con_show_row_data_destroy(gpointer data) -{ - MetagenConShowRowData *row_data = data; - - if (!row_data) - return; - - g_clear_object(&row_data->connection); - g_clear_object(&row_data->primary_active); - nm_clear_pointer(&row_data->all_active, g_ptr_array_unref); - g_slice_free(MetagenConShowRowData, row_data); -} - -static const char * -_con_show_fcn_get_id(NMConnection *c, NMActiveConnection *ac) -{ - NMSettingConnection *s_con = NULL; - const char * s; - - if (c) - s_con = nm_connection_get_setting_connection(c); - - s = s_con ? nm_setting_connection_get_id(s_con) : NULL; - if (!s && ac) { - /* note that if we have no s_con, that usually means that the user has no permissions - * to see the connection. We still fall to get the ID from the active-connection, - * which exposes it despite the user having no permissions. - * - * That might be unexpected, because the user is shown an ID, which he later - * is unable to resolve in other operations. */ - s = nm_active_connection_get_id(ac); - } - return s; -} - -static const char * -_con_show_fcn_get_type(NMConnection *c, NMActiveConnection *ac, NMMetaAccessorGetType get_type) -{ - NMSettingConnection *s_con = NULL; - const char * s; - - if (c) - s_con = nm_connection_get_setting_connection(c); - - s = s_con ? nm_setting_connection_get_connection_type(s_con) : NULL; - if (!s && ac) { - /* see _con_show_fcn_get_id() for why we fallback to get the value - * from @ac. */ - s = nm_active_connection_get_connection_type(ac); - } - return connection_type_to_display(s, get_type); -} - -static gconstpointer _metagen_con_show_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS) -{ - const MetagenConShowRowData *row_data = target; - NMConnection * c = row_data->connection; - NMActiveConnection * ac = row_data->primary_active; - NMSettingConnection * s_con = NULL; - const char * s; - char * s_mut; - - NMC_HANDLE_COLOR(nmc_active_connection_state_to_color(ac)); - - if (c) - s_con = nm_connection_get_setting_connection(c); - - if (!row_data->show_active_fields) { - /* we are not supposed to show any fields of the active connection. - * We only tracked the primary_active to get the coloring right. - * From now on, there is no active connection. */ - ac = NULL; - - /* in this mode, we expect that we are called only with connections that - * have a [connection] setting and a UUID. Otherwise, the connection is - * effectively invisible to the user, and should be hidden. - * - * But in that case, we expect that the caller pre-filtered this row out. - * So assert(). */ - nm_assert(s_con); - nm_assert(nm_setting_connection_get_uuid(s_con)); - } - - nm_assert( - NM_IN_SET(get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE)); - - switch (info->info_type) { - case NMC_GENERIC_INFO_TYPE_CON_SHOW_NAME: - return _con_show_fcn_get_id(c, ac); - case NMC_GENERIC_INFO_TYPE_CON_SHOW_UUID: - s = s_con ? nm_setting_connection_get_uuid(s_con) : NULL; - if (!s && ac) { - /* see _con_show_fcn_get_id() for why we fallback to get the value - * from @ac. */ - s = nm_active_connection_get_uuid(ac); - } - return s; - case NMC_GENERIC_INFO_TYPE_CON_SHOW_TYPE: - return _con_show_fcn_get_type(c, ac, get_type); - case NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP: - case NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP_REAL: - if (!s_con) - return NULL; - { - guint64 timestamp; - time_t timestamp_real; - - timestamp = nm_setting_connection_get_timestamp(s_con); - - if (info->info_type == NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP) - return (*out_to_free = g_strdup_printf("%" G_GUINT64_FORMAT, timestamp)); - else { - struct tm localtime_result; - - if (!timestamp) { - if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) - return _("never"); - return "never"; - } - timestamp_real = timestamp; - s_mut = g_malloc0(128); - strftime(s_mut, 127, "%c", localtime_r(×tamp_real, &localtime_result)); - return (*out_to_free = s_mut); - } - } - case NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT: - if (!s_con) - return NULL; - return nmc_meta_generic_get_bool(nm_setting_connection_get_autoconnect(s_con), get_type); - case NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT_PRIORITY: - if (!s_con) - return NULL; - return (*out_to_free = - g_strdup_printf("%d", nm_setting_connection_get_autoconnect_priority(s_con))); - case NMC_GENERIC_INFO_TYPE_CON_SHOW_READONLY: - if (!s_con) - return NULL; - return nmc_meta_generic_get_bool(nm_setting_connection_get_read_only(s_con), get_type); - case NMC_GENERIC_INFO_TYPE_CON_SHOW_DBUS_PATH: - if (!c) - return NULL; - return nm_connection_get_path(c); - case NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE: - return nmc_meta_generic_get_bool(!!ac, get_type); - case NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE: - if (ac) - return (*out_to_free = get_ac_device_string(ac)); - return NULL; - case NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE: - return nmc_meta_generic_get_str_i18n( - ac ? active_connection_state_to_string(nm_active_connection_get_state(ac)) : NULL, - get_type); - case NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH: - if (ac) - return nm_object_get_path(NM_OBJECT(ac)); - return NULL; - case NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE: - if (!s_con) - return NULL; - return nm_setting_connection_get_slave_type(s_con); - case NMC_GENERIC_INFO_TYPE_CON_SHOW_FILENAME: - if (!NM_IS_REMOTE_CONNECTION(c)) - return NULL; - return nm_remote_connection_get_filename(NM_REMOTE_CONNECTION(c)); - default: - break; - } - - g_return_val_if_reached(NULL); -} - -const NmcMetaGenericInfo *const metagen_con_show[_NMC_GENERIC_INFO_TYPE_CON_SHOW_NUM + 1] = { -#define _METAGEN_CON_SHOW(type, name) \ - [type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_con_show_get_fcn) - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_NAME, "NAME"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_UUID, "UUID"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_TYPE, "TYPE"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP, "TIMESTAMP"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP_REAL, "TIMESTAMP-REAL"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT, "AUTOCONNECT"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT_PRIORITY, "AUTOCONNECT-PRIORITY"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_READONLY, "READONLY"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_DBUS_PATH, "DBUS-PATH"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE, "ACTIVE"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE, "DEVICE"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE, "STATE"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH, "ACTIVE-PATH"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE, "SLAVE"), - _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_FILENAME, "FILENAME"), -}; -#define NMC_FIELDS_CON_SHOW_COMMON "NAME,UUID,TYPE,DEVICE" - -/*****************************************************************************/ - -static gconstpointer _metagen_con_active_general_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS) -{ - NMActiveConnection * ac = target; - NMConnection * c; - NMSettingConnection *s_con = NULL; - NMDevice * dev; - guint i; - const char * s; - - NMC_HANDLE_COLOR(NM_META_COLOR_NONE); - - nm_assert( - NM_IN_SET(get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE)); - - c = NM_CONNECTION(nm_active_connection_get_connection(ac)); - if (c) - s_con = nm_connection_get_setting_connection(c); - - switch (info->info_type) { - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NAME: - return nm_active_connection_get_id(ac); - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_UUID: - return nm_active_connection_get_uuid(ac); - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEVICES: - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_IP_IFACE: - { - GString * str = NULL; - const GPtrArray *devices; - - s = NULL; - devices = nm_active_connection_get_devices(ac); - if (devices) { - for (i = 0; i < devices->len; i++) { - NMDevice * device = g_ptr_array_index(devices, i); - const char *iface; - - if (info->info_type == NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEVICES) { - iface = nm_device_get_iface(device); - } else { - iface = nm_device_get_ip_iface(device); - } - - if (!iface) - continue; - if (!s) { - s = iface; - continue; - } - if (!str) - str = g_string_new(s); - g_string_append_c(str, ','); - g_string_append(str, iface); - } - } - if (str) - return (*out_to_free = g_string_free(str, FALSE)); - return s; - } - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_STATE: - return nmc_meta_generic_get_str_i18n( - active_connection_state_to_string(nm_active_connection_get_state(ac)), - get_type); - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT: - return nmc_meta_generic_get_bool(nm_active_connection_get_default(ac), get_type); - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT6: - return nmc_meta_generic_get_bool(nm_active_connection_get_default6(ac), get_type); - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_SPEC_OBJECT: - return nm_active_connection_get_specific_object_path(ac); - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_VPN: - return nmc_meta_generic_get_bool(NM_IS_VPN_CONNECTION(ac), get_type); - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DBUS_PATH: - return nm_object_get_path(NM_OBJECT(ac)); - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CON_PATH: - return c ? nm_connection_get_path(c) : NULL; - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_ZONE: - /* this is really ugly, because the zone is not a property of the active-connection, - * but the settings-connection profile. There is no guarantee, that they agree. */ - return s_con ? nm_setting_connection_get_zone(s_con) : NULL; - case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH: - dev = nm_active_connection_get_master(ac); - return dev ? nm_object_get_path(NM_OBJECT(dev)) : NULL; - default: - break; - } - - g_return_val_if_reached(NULL); -} - -const NmcMetaGenericInfo - *const metagen_con_active_general[_NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NUM + 1] = { -#define _METAGEN_CON_ACTIVE_GENERAL(type, name) \ - [type] = \ - NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_con_active_general_get_fcn) - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NAME, "NAME"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_UUID, "UUID"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEVICES, "DEVICES"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_IP_IFACE, "IP-IFACE"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_STATE, "STATE"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT, "DEFAULT"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT6, "DEFAULT6"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_SPEC_OBJECT, - "SPEC-OBJECT"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_VPN, "VPN"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DBUS_PATH, - "DBUS-PATH"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CON_PATH, "CON-PATH"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_ZONE, "ZONE"), - _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH, - "MASTER-PATH"), -}; - -/*****************************************************************************/ - -static gconstpointer _metagen_con_active_vpn_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS) -{ - NMActiveConnection * ac = target; - NMConnection * c; - NMSettingVpn * s_vpn = NULL; - NMVpnConnectionState vpn_state; - guint i; - const char * s; - char ** arr = NULL; - - nm_assert(NM_IS_VPN_CONNECTION(ac)); - - NMC_HANDLE_COLOR(NM_META_COLOR_NONE); - - nm_assert( - NM_IN_SET(get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE)); - - c = NM_CONNECTION(nm_active_connection_get_connection(ac)); - if (c) - s_vpn = nm_connection_get_setting_vpn(c); - - switch (info->info_type) { - case NMC_GENERIC_INFO_TYPE_CON_VPN_TYPE: - return c ? get_vpn_connection_type(c) : NULL; - case NMC_GENERIC_INFO_TYPE_CON_VPN_USERNAME: - if (s_vpn && (s = nm_setting_vpn_get_user_name(s_vpn))) - return s; - return c ? get_vpn_data_item(c, VPN_DATA_ITEM_USERNAME) : NULL; - case NMC_GENERIC_INFO_TYPE_CON_VPN_GATEWAY: - return c ? get_vpn_data_item(c, VPN_DATA_ITEM_GATEWAY) : NULL; - case NMC_GENERIC_INFO_TYPE_CON_VPN_BANNER: - s = nm_vpn_connection_get_banner(NM_VPN_CONNECTION(ac)); - if (s) - return (*out_to_free = g_strescape(s, "")); - return NULL; - case NMC_GENERIC_INFO_TYPE_CON_VPN_VPN_STATE: - vpn_state = nm_vpn_connection_get_vpn_state(NM_VPN_CONNECTION(ac)); - return (*out_to_free = - nmc_meta_generic_get_enum_with_detail(NMC_META_GENERIC_GET_ENUM_TYPE_DASH, - vpn_state, - vpn_connection_state_to_string(vpn_state), - get_type)); - case NMC_GENERIC_INFO_TYPE_CON_VPN_CFG: - if (!NM_FLAGS_HAS(get_flags, NM_META_ACCESSOR_GET_FLAGS_ACCEPT_STRV)) - return NULL; - if (s_vpn) { - gs_free char **arr2 = NULL; - guint n; - - arr2 = (char **) nm_setting_vpn_get_data_keys(s_vpn, &n); - if (!n) - goto arr_out; - - nm_assert(arr2 && !arr2[n]); - for (i = 0; i < n; i++) { - const char *k = arr2[i]; - const char *v; - - nm_assert(k); - v = nm_setting_vpn_get_data_item(s_vpn, k); - /* update the arr array in-place. Previously it contained - * the constant keys, now it contains the strdup'ed output text. */ - arr2[i] = g_strdup_printf("%s = %s", k, v); - } - - arr = g_steal_pointer(&arr2); - } - goto arr_out; - default: - break; - } - - g_return_val_if_reached(NULL); - -arr_out: - NM_SET_OUT(out_is_default, !arr || !arr[0]); - *out_flags |= NM_META_ACCESSOR_GET_OUT_FLAGS_STRV; - *out_to_free = arr; - return arr; -} - -const NmcMetaGenericInfo - *const metagen_con_active_vpn[_NMC_GENERIC_INFO_TYPE_CON_ACTIVE_VPN_NUM + 1] = { -#define _METAGEN_CON_ACTIVE_VPN(type, name) \ - [type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_con_active_vpn_get_fcn) - _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_TYPE, "TYPE"), - _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_USERNAME, "USERNAME"), - _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_GATEWAY, "GATEWAY"), - _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_BANNER, "BANNER"), - _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_VPN_STATE, "VPN-STATE"), - _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_CFG, "CFG"), -}; - -/*****************************************************************************/ - -#define NMC_FIELDS_SETTINGS_NAMES_ALL \ - NM_SETTING_CONNECTION_SETTING_NAME \ - "," NM_SETTING_MATCH_SETTING_NAME "," NM_SETTING_WIRED_SETTING_NAME \ - "," NM_SETTING_VETH_SETTING_NAME "," NM_SETTING_802_1X_SETTING_NAME \ - "," NM_SETTING_WIRELESS_SETTING_NAME "," NM_SETTING_WIRELESS_SECURITY_SETTING_NAME \ - "," NM_SETTING_IP4_CONFIG_SETTING_NAME "," NM_SETTING_IP6_CONFIG_SETTING_NAME \ - "," NM_SETTING_SERIAL_SETTING_NAME "," NM_SETTING_WIFI_P2P_SETTING_NAME \ - "," NM_SETTING_PPP_SETTING_NAME "," NM_SETTING_PPPOE_SETTING_NAME \ - "," NM_SETTING_ADSL_SETTING_NAME "," NM_SETTING_GSM_SETTING_NAME \ - "," NM_SETTING_CDMA_SETTING_NAME "," NM_SETTING_BLUETOOTH_SETTING_NAME \ - "," NM_SETTING_OLPC_MESH_SETTING_NAME "," NM_SETTING_VPN_SETTING_NAME \ - "," NM_SETTING_INFINIBAND_SETTING_NAME "," NM_SETTING_BOND_SETTING_NAME \ - "," NM_SETTING_VLAN_SETTING_NAME "," NM_SETTING_BRIDGE_SETTING_NAME \ - "," NM_SETTING_BRIDGE_PORT_SETTING_NAME "," NM_SETTING_TEAM_SETTING_NAME \ - "," NM_SETTING_TEAM_PORT_SETTING_NAME "," NM_SETTING_OVS_BRIDGE_SETTING_NAME \ - "," NM_SETTING_OVS_INTERFACE_SETTING_NAME "," NM_SETTING_OVS_PATCH_SETTING_NAME \ - "," NM_SETTING_OVS_PORT_SETTING_NAME "," NM_SETTING_DCB_SETTING_NAME \ - "," NM_SETTING_TUN_SETTING_NAME "," NM_SETTING_IP_TUNNEL_SETTING_NAME \ - "," NM_SETTING_MACSEC_SETTING_NAME "," NM_SETTING_MACVLAN_SETTING_NAME \ - "," NM_SETTING_VXLAN_SETTING_NAME "," NM_SETTING_VRF_SETTING_NAME \ - "," NM_SETTING_WPAN_SETTING_NAME "," NM_SETTING_6LOWPAN_SETTING_NAME \ - "," NM_SETTING_WIREGUARD_SETTING_NAME "," NM_SETTING_PROXY_SETTING_NAME \ - "," NM_SETTING_TC_CONFIG_SETTING_NAME "," NM_SETTING_SRIOV_SETTING_NAME \ - "," NM_SETTING_ETHTOOL_SETTING_NAME "," NM_SETTING_OVS_DPDK_SETTING_NAME \ - "," NM_SETTING_HOSTNAME_SETTING_NAME /* NM_SETTING_DUMMY_SETTING_NAME NM_SETTING_WIMAX_SETTING_NAME */ - -const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[] = { - NMC_META_GENERIC_WITH_NESTED("GENERAL", metagen_con_active_general), /* 0 */ - NMC_META_GENERIC_WITH_NESTED("IP4", metagen_ip4_config), /* 1 */ - NMC_META_GENERIC_WITH_NESTED("DHCP4", metagen_dhcp_config), /* 2 */ - NMC_META_GENERIC_WITH_NESTED("IP6", metagen_ip6_config), /* 3 */ - NMC_META_GENERIC_WITH_NESTED("DHCP6", metagen_dhcp_config), /* 4 */ - NMC_META_GENERIC_WITH_NESTED("VPN", metagen_con_active_vpn), /* 5 */ - NULL, -}; - -/* Pseudo group names for 'connection show <con>' */ -/* e.g.: nmcli -f profile con show my-eth0 */ -/* e.g.: nmcli -f active con show my-eth0 */ -#define CON_SHOW_DETAIL_GROUP_PROFILE "profile" -#define CON_SHOW_DETAIL_GROUP_ACTIVE "active" - -static guint progress_id = 0; /* ID of event source for displaying progress */ - -/* for readline TAB completion in editor */ -typedef struct { - NmCli * nmc; - char * con_type; - NMConnection *connection; - NMSetting * setting; - const char * property; - char ** words; -} TabCompletionInfo; - -static TabCompletionInfo nmc_tab_completion; - -/*****************************************************************************/ - -static void -usage(void) -{ - g_printerr( - _("Usage: nmcli connection { COMMAND | help }\n\n" - "COMMAND := { show | up | down | add | modify | clone | edit | delete | monitor | reload " - "| load | import | export }\n\n" - " show [--active] [--order <order spec>]\n" - " show [--active] [id | uuid | path | apath] <ID> ...\n\n" - " up [[id | uuid | path] <ID>] [ifname <ifname>] [ap <BSSID>] [passwd-file <file with " - "passwords>]\n\n" - " down [id | uuid | path | apath] <ID> ...\n\n" - " add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS SLAVE_OPTIONS IP_OPTIONS [-- " - "([+|-]<setting>.<property> <value>)+]\n\n" - " modify [--temporary] [id | uuid | path] <ID> ([+|-]<setting>.<property> <value>)+\n\n" - " clone [--temporary] [id | uuid | path ] <ID> <new name>\n\n" - " edit [id | uuid | path] <ID>\n" - " edit [type <new_con_type>] [con-name <new_con_name>]\n\n" - " delete [id | uuid | path] <ID>\n\n" - " monitor [id | uuid | path] <ID> ...\n\n" - " reload\n\n" - " load <filename> [ <filename>... ]\n\n" - " import [--temporary] type <type> file <file to import>\n\n" - " export [id | uuid | path] <ID> [<output file>]\n\n")); -} - -static void -usage_connection_show(void) -{ - g_printerr( - _("Usage: nmcli connection show { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [--active] [--order <order spec>]\n" - "\n" - "List in-memory and on-disk connection profiles, some of which may also be\n" - "active if a device is using that connection profile. Without a parameter, all\n" - "profiles are listed. When --active option is specified, only the active\n" - "profiles are shown. --order allows custom connection ordering (see manual page).\n" - "\n" - "ARGUMENTS := [--active] [id | uuid | path | apath] <ID> ...\n" - "\n" - "Show details for specified connections. By default, both static configuration\n" - "and active connection data are displayed. It is possible to filter the output\n" - "using global '--fields' option. Refer to the manual page for more information.\n" - "When --active option is specified, only the active profiles are taken into\n" - "account. Use global --show-secrets option to reveal associated secrets as well.\n")); -} - -static void -usage_connection_up(void) -{ - g_printerr(_("Usage: nmcli connection up { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [id | uuid | path] <ID> [ifname <ifname>] [ap <BSSID>] [nsp <name>] " - "[passwd-file <file with passwords>]\n" - "\n" - "Activate a connection on a device. The profile to activate is identified by its\n" - "name, UUID or D-Bus path.\n" - "\n" - "ARGUMENTS := ifname <ifname> [ap <BSSID>] [nsp <name>] [passwd-file <file with " - "passwords>]\n" - "\n" - "Activate a device with a connection. The connection profile is selected\n" - "automatically by NetworkManager.\n" - "\n" - "ifname - specifies the device to active the connection on\n" - "ap - specifies AP to connect to (only valid for Wi-Fi)\n" - "nsp - specifies NSP to connect to (only valid for WiMAX)\n" - "passwd-file - file with password(s) required to activate the connection\n\n")); -} - -static void -usage_connection_down(void) -{ - g_printerr(_("Usage: nmcli connection down { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [id | uuid | path | apath] <ID> ...\n" - "\n" - "Deactivate a connection from a device (without preventing the device from\n" - "further auto-activation). The profile to deactivate is identified by its name,\n" - "UUID or D-Bus path.\n\n")); -} - -static void -usage_connection_add(void) -{ - g_printerr(_("Usage: nmcli connection add { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS SLAVE_OPTIONS IP_OPTIONS [-- " - "([+|-]<setting>.<property> <value>)+]\n\n" - " COMMON_OPTIONS:\n" - " type <type>\n" - " ifname <interface name> | \"*\"\n" - " [con-name <connection name>]\n" - " [autoconnect yes|no]\n" - " [save yes|no]\n" - " [master <master (ifname, or connection UUID or name)>]\n" - " [slave-type <master connection type>]\n\n" - " TYPE_SPECIFIC_OPTIONS:\n" - " ethernet: [mac <MAC address>]\n" - " [cloned-mac <cloned MAC address>]\n" - " [mtu <MTU>]\n\n" - " wifi: ssid <SSID>\n" - " [mac <MAC address>]\n" - " [cloned-mac <cloned MAC address>]\n" - " [mtu <MTU>]\n" - " [mode infrastructure|ap|adhoc]\n\n" - " wimax: [mac <MAC address>]\n" - " [nsp <NSP>]\n\n" - " pppoe: username <PPPoE username>\n" - " [password <PPPoE password>]\n" - " [service <PPPoE service name>]\n" - " [mtu <MTU>]\n" - " [mac <MAC address>]\n\n" - " gsm: apn <APN>\n" - " [user <username>]\n" - " [password <password>]\n\n" - " cdma: [user <username>]\n" - " [password <password>]\n\n" - " infiniband: [mac <MAC address>]\n" - " [mtu <MTU>]\n" - " [transport-mode datagram | connected]\n" - " [parent <ifname>]\n" - " [p-key <IPoIB P_Key>]\n\n" - " bluetooth: [addr <bluetooth address>]\n" - " [bt-type panu|nap|dun-gsm|dun-cdma]\n\n" - " vlan: dev <parent device (connection UUID, ifname, or MAC)>\n" - " id <VLAN ID>\n" - " [flags <VLAN flags>]\n" - " [ingress <ingress priority mapping>]\n" - " [egress <egress priority mapping>]\n" - " [mtu <MTU>]\n\n" - " bond: [mode balance-rr (0) | active-backup (1) | balance-xor (2) | " - "broadcast (3) |\n" - " 802.3ad (4) | balance-tlb (5) | balance-alb (6)]\n" - " [primary <ifname>]\n" - " [miimon <num>]\n" - " [downdelay <num>]\n" - " [updelay <num>]\n" - " [arp-interval <num>]\n" - " [arp-ip-target <num>]\n" - " [lacp-rate slow (0) | fast (1)]\n\n" - " bond-slave: master <master (ifname, or connection UUID or name)>\n\n" - " team: [config <file>|<raw JSON data>]\n\n" - " team-slave: master <master (ifname, or connection UUID or name)>\n" - " [config <file>|<raw JSON data>]\n\n" - " bridge: [stp yes|no]\n" - " [priority <num>]\n" - " [forward-delay <2-30>]\n" - " [hello-time <1-10>]\n" - " [max-age <6-40>]\n" - " [ageing-time <0-1000000>]\n" - " [multicast-snooping yes|no]\n" - " [mac <MAC address>]\n\n" - " bridge-slave: master <master (ifname, or connection UUID or name)>\n" - " [priority <0-63>]\n" - " [path-cost <1-65535>]\n" - " [hairpin yes|no]\n\n" - " vpn: vpn-type " - "vpnc|openvpn|pptp|openconnect|openswan|libreswan|ssh|l2tp|iodine|...\n" - " [user <username>]\n\n" - " olpc-mesh: ssid <SSID>\n" - " [channel <1-13>]\n" - " [dhcp-anycast <MAC address>]\n\n" - " adsl: username <username>\n" - " protocol pppoa|pppoe|ipoatm\n" - " [password <password>]\n" - " [encapsulation vcmux|llc]\n\n" - " tun: mode tun|tap\n" - " [owner <UID>]\n" - " [group <GID>]\n" - " [pi yes|no]\n" - " [vnet-hdr yes|no]\n" - " [multi-queue yes|no]\n\n" - " ip-tunnel: mode ipip|gre|sit|isatap|vti|ip6ip6|ipip6|ip6gre|vti6\n" - " remote <remote endpoint IP>\n" - " [local <local endpoint IP>]\n" - " [dev <parent device (ifname or connection UUID)>]\n\n" - " macsec: dev <parent device (connection UUID, ifname, or MAC)>\n" - " mode <psk|eap>\n" - " [cak <key> ckn <key>]\n" - " [encrypt yes|no]\n" - " [port 1-65534]\n\n\n" - " macvlan: dev <parent device (connection UUID, ifname, or MAC)>\n" - " mode vepa|bridge|private|passthru|source\n" - " [tap yes|no]\n\n" - " vxlan: id <VXLAN ID>\n" - " [remote <IP of multicast group or remote address>]\n" - " [local <source IP>]\n" - " [dev <parent device (ifname or connection UUID)>]\n" - " [source-port-min <0-65535>]\n" - " [source-port-max <0-65535>]\n" - " [destination-port <0-65535>]\n\n" - " wpan: [short-addr <0x0000-0xffff>]\n" - " [pan-id <0x0000-0xffff>]\n" - " [page <default|0-31>]\n" - " [channel <default|0-26>]\n" - " [mac <MAC address>]\n\n" - " 6lowpan: dev <parent device (connection UUID, ifname, or MAC)>\n" - " dummy:\n\n" - " SLAVE_OPTIONS:\n" - " bridge: [priority <0-63>]\n" - " [path-cost <1-65535>]\n" - " [hairpin yes|no]\n\n" - " team: [config <file>|<raw JSON data>]\n\n" - " IP_OPTIONS:\n" - " [ip4 <IPv4 address>] [gw4 <IPv4 gateway>]\n" - " [ip6 <IPv6 address>] [gw6 <IPv6 gateway>]\n\n")); -} - -static void -usage_connection_modify(void) -{ - g_printerr( - _("Usage: nmcli connection modify { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [id | uuid | path] <ID> ([+|-]<setting>.<property> <value>)+\n" - "\n" - "Modify one or more properties of the connection profile.\n" - "The profile is identified by its name, UUID or D-Bus path. For multi-valued\n" - "properties you can use optional '+' or '-' prefix to the property name.\n" - "The '+' sign allows appending items instead of overwriting the whole value.\n" - "The '-' sign allows removing selected items instead of the whole value.\n" - "\n" - "ARGUMENTS := remove <setting>\n" - "\n" - "Remove a setting from the connection profile.\n" - "\n" - "Examples:\n" - "nmcli con mod home-wifi wifi.ssid rakosnicek\n" - "nmcli con mod em1-1 ipv4.method manual ipv4.addr \"192.168.1.2/24, 10.10.1.5/8\"\n" - "nmcli con mod em1-1 +ipv4.dns 8.8.4.4\n" - "nmcli con mod em1-1 -ipv4.dns 1\n" - "nmcli con mod em1-1 -ipv6.addr \"abbe::cafe/56\"\n" - "nmcli con mod bond0 +bond.options mii=500\n" - "nmcli con mod bond0 -bond.options downdelay\n" - "nmcli con mod em1-1 remove sriov\n\n")); -} - -static void -usage_connection_clone(void) -{ - g_printerr(_("Usage: nmcli connection clone { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [--temporary] [id | uuid | path] <ID> <new name>\n" - "\n" - "Clone an existing connection profile. The newly created connection will be\n" - "the exact copy of the <ID>, except the uuid property (will be generated) and\n" - "id (provided as <new name> argument).\n\n")); -} - -static void -usage_connection_edit(void) -{ - g_printerr(_("Usage: nmcli connection edit { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [id | uuid | path] <ID>\n" - "\n" - "Edit an existing connection profile in an interactive editor.\n" - "The profile is identified by its name, UUID or D-Bus path\n" - "\n" - "ARGUMENTS := [type <new connection type>] [con-name <new connection name>]\n" - "\n" - "Add a new connection profile in an interactive editor.\n\n")); -} - -static void -usage_connection_delete(void) -{ - g_printerr(_("Usage: nmcli connection delete { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [id | uuid | path] <ID>\n" - "\n" - "Delete a connection profile.\n" - "The profile is identified by its name, UUID or D-Bus path.\n\n")); -} - -static void -usage_connection_monitor(void) -{ - g_printerr(_("Usage: nmcli connection monitor { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [id | uuid | path] <ID> ...\n" - "\n" - "Monitor connection profile activity.\n" - "This command prints a line whenever the specified connection changes.\n" - "Monitors all connection profiles in case none is specified.\n\n")); -} - -static void -usage_connection_reload(void) -{ - g_printerr(_("Usage: nmcli connection reload { help }\n" - "\n" - "Reload all connection files from disk.\n\n")); -} - -static void -usage_connection_load(void) -{ - g_printerr(_("Usage: nmcli connection load { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := <filename> [<filename>...]\n" - "\n" - "Load/reload one or more connection files from disk. Use this after manually\n" - "editing a connection file to ensure that NetworkManager is aware of its latest\n" - "state.\n\n")); -} - -static void -usage_connection_import(void) -{ - g_printerr( - _("Usage: nmcli connection import { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [--temporary] type <type> file <file to import>\n" - "\n" - "Import an external/foreign configuration as a NetworkManager connection profile.\n" - "The type of the input file is specified by type option.\n" - "Only VPN configurations are supported at the moment. The configuration\n" - "is imported by NetworkManager VPN plugins.\n\n")); -} - -static void -usage_connection_export(void) -{ - g_printerr(_("Usage: nmcli connection export { ARGUMENTS | help }\n" - "\n" - "ARGUMENTS := [id | uuid | path] <ID> [<output file>]\n" - "\n" - "Export a connection. Only VPN connections are supported at the moment.\n" - "The data are directed to standard output or to a file if a name is given.\n\n")); -} - -static void -quit(void) -{ - if (nm_clear_g_source(&progress_id)) - nmc_terminal_erase_line(); - g_main_loop_quit(loop); -} - -static char * -construct_header_name(const char *base, const char *spec) -{ - if (spec == NULL) - return g_strdup(base); - - return g_strdup_printf("%s (%s)", base, spec); -} - -static int -get_ac_for_connection_cmp(gconstpointer pa, gconstpointer pb) -{ - NMActiveConnection *ac_a = *((NMActiveConnection *const *) pa); - NMActiveConnection *ac_b = *((NMActiveConnection *const *) pb); - - NM_CMP_RETURN(nmc_active_connection_cmp(ac_a, ac_b)); - NM_CMP_DIRECT_STRCMP0(nm_active_connection_get_id(ac_a), nm_active_connection_get_id(ac_b)); - NM_CMP_DIRECT_STRCMP0(nm_active_connection_get_connection_type(ac_a), - nm_active_connection_get_connection_type(ac_b)); - NM_CMP_DIRECT_STRCMP0(nm_object_get_path(NM_OBJECT(ac_a)), nm_object_get_path(NM_OBJECT(ac_b))); - - g_return_val_if_reached(0); -} - -static NMActiveConnection * -get_ac_for_connection(const GPtrArray *active_cons, - NMConnection * connection, - GPtrArray ** out_result) -{ - guint i; - NMActiveConnection *best_candidate = NULL; - GPtrArray * result = out_result ? *out_result : NULL; - - for (i = 0; i < active_cons->len; i++) { - NMActiveConnection *candidate = g_ptr_array_index(active_cons, i); - NMRemoteConnection *con; - - con = nm_active_connection_get_connection(candidate); - if (NM_CONNECTION(con) != connection) - continue; - - if (!out_result) - return candidate; - if (!result) - result = g_ptr_array_new_with_free_func(g_object_unref); - g_ptr_array_add(result, g_object_ref(candidate)); - } - - if (result) { - g_ptr_array_sort(result, get_ac_for_connection_cmp); - best_candidate = result->pdata[0]; - } - - NM_SET_OUT(out_result, result); - return best_candidate; -} - -typedef struct { - GMainLoop * loop; - NMConnection *local; - const char * setting_name; -} GetSecretsData; - -static void -got_secrets(GObject *source_object, GAsyncResult *res, gpointer user_data) -{ - NMRemoteConnection *remote = NM_REMOTE_CONNECTION(source_object); - GetSecretsData * data = user_data; - gs_unref_variant GVariant *secrets = NULL; - - secrets = nm_remote_connection_get_secrets_finish(remote, res, NULL); - if (secrets) { - gs_free_error GError *error = NULL; - - if (!nm_connection_update_secrets(data->local, NULL, secrets, &error) && error) { - g_printerr(_("Error updating secrets for %s: %s\n"), - data->setting_name, - error->message); - } - } - - g_main_loop_quit(data->loop); -} - -/* Put secrets into local connection. */ -static void -update_secrets_in_connection(NMRemoteConnection *remote, NMConnection *local) -{ - GetSecretsData data = { - 0, - }; - GType setting_type; - int i; - - data.local = local; - data.loop = g_main_loop_new(NULL, FALSE); - - for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { - setting_type = nm_meta_setting_infos[i].get_setting_gtype(); - if (!nm_connection_get_setting(NM_CONNECTION(remote), setting_type)) - continue; - if (!nm_meta_setting_info_editor_has_secrets( - nm_meta_setting_info_editor_find_by_gtype(setting_type))) - continue; - data.setting_name = nm_meta_setting_infos[i].setting_name; - nm_remote_connection_get_secrets_async(remote, - nm_meta_setting_infos[i].setting_name, - NULL, - got_secrets, - &data); - g_main_loop_run(data.loop); - } - - g_main_loop_unref(data.loop); -} - -static gboolean -nmc_connection_profile_details(NMConnection *connection, NmCli *nmc) -{ - GError * error = NULL; - GArray * print_settings_array; - GPtrArray * prop_array = NULL; - guint i; - char * fields_str; - char * fields_all = NMC_FIELDS_SETTINGS_NAMES_ALL; - char * fields_common = NMC_FIELDS_SETTINGS_NAMES_ALL; - const char *base_hdr = _("Connection profile details"); - gboolean was_output = FALSE; - - if (!nmc->required_fields || g_ascii_strcasecmp(nmc->required_fields, "common") == 0) - fields_str = fields_common; - else if (!nmc->required_fields || g_ascii_strcasecmp(nmc->required_fields, "all") == 0) - fields_str = fields_all; - else - fields_str = nmc->required_fields; - - print_settings_array = - parse_output_fields(fields_str, - (const NMMetaAbstractInfo *const *) nm_meta_setting_infos_editor_p(), - TRUE, - &prop_array, - &error); - if (error) { - g_string_printf(nmc->return_text, _("Error: 'connection show': %s"), error->message); - g_error_free(error); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return FALSE; - } - g_assert(print_settings_array); - - /* Main header */ - { - gs_free char *header_name = NULL; - gs_free NmcOutputField *row = NULL; - gs_unref_array GArray *out_indices = NULL; - - header_name = construct_header_name(base_hdr, nm_connection_get_id(connection)); - out_indices = parse_output_fields( - NMC_FIELDS_SETTINGS_NAMES_ALL, - (const NMMetaAbstractInfo *const *) nm_meta_setting_infos_editor_p(), - FALSE, - NULL, - NULL); - - row = g_new0(NmcOutputField, _NM_META_SETTING_TYPE_NUM + 1); - for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) - row[i].info = (const NMMetaAbstractInfo *) &nm_meta_setting_infos_editor[i]; - - print_required_fields(&nmc->nmc_config, - &nmc->pager_data, - NMC_OF_FLAG_MAIN_HEADER_ONLY, - out_indices, - header_name, - 0, - row); - } - - /* Loop through the required settings and print them. */ - for (i = 0; i < print_settings_array->len; i++) { - NMSetting * setting; - int section_idx = g_array_index(print_settings_array, int, i); - const char *prop_name = (const char *) g_ptr_array_index(prop_array, i); - - if (NM_IN_SET(nmc->nmc_config.print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY) - && !nmc->nmc_config.multiline_output && was_output) - g_print("\n"); /* Empty line */ - - was_output = FALSE; - - setting = nm_connection_get_setting_by_name( - connection, - nm_meta_setting_infos_editor[section_idx].general->setting_name); - if (setting) { - setting_details(&nmc->nmc_config, setting, prop_name); - was_output = TRUE; - } - } - - g_array_free(print_settings_array, TRUE); - if (prop_array) - g_ptr_array_free(prop_array, TRUE); - - return TRUE; -} - -NMMetaColor -nmc_active_connection_state_to_color(NMActiveConnection *ac) -{ - NMActiveConnectionState state; - - if (!ac) - return NM_META_COLOR_CONNECTION_UNKNOWN; - - if (NM_FLAGS_HAS(nm_active_connection_get_state_flags(ac), NM_ACTIVATION_STATE_FLAG_EXTERNAL)) - return NM_META_COLOR_CONNECTION_EXTERNAL; - - state = nm_active_connection_get_state(ac); - - if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING) - return NM_META_COLOR_CONNECTION_ACTIVATING; - else if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) - return NM_META_COLOR_CONNECTION_ACTIVATED; - else if (state > NM_ACTIVE_CONNECTION_STATE_ACTIVATED) - return NM_META_COLOR_CONNECTION_DISCONNECTING; - else - return NM_META_COLOR_CONNECTION_UNKNOWN; -} - -static gboolean -nmc_active_connection_details(NMActiveConnection *acon, NmCli *nmc) -{ - GError * error = NULL; - GArray * print_groups; - GPtrArray * group_fields = NULL; - int i; - const char *fields_str = NULL; - const char *base_hdr = _("Activate connection details"); - gboolean was_output = FALSE; - - if (!nmc->required_fields || g_ascii_strcasecmp(nmc->required_fields, "common") == 0) { - /* pass */ - } else if (!nmc->required_fields || g_ascii_strcasecmp(nmc->required_fields, "all") == 0) { - /* pass */ - } else - fields_str = nmc->required_fields; - - print_groups = parse_output_fields( - fields_str, - (const NMMetaAbstractInfo *const *) nmc_fields_con_active_details_groups, - TRUE, - &group_fields, - &error); - if (error) { - g_string_printf(nmc->return_text, _("Error: 'connection show': %s"), error->message); - g_error_free(error); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return FALSE; - } - g_assert(print_groups); - - /* Main header */ - { - gs_free char *header_name = NULL; - gs_free NmcOutputField *row = NULL; - gs_unref_array GArray *out_indices = NULL; - - header_name = construct_header_name(base_hdr, nm_active_connection_get_uuid(acon)); - out_indices = parse_output_fields( - NULL, - (const NMMetaAbstractInfo *const *) nmc_fields_con_active_details_groups, - FALSE, - NULL, - NULL); - - row = g_new0(NmcOutputField, G_N_ELEMENTS(nmc_fields_con_active_details_groups) + 1); - for (i = 0; nmc_fields_con_active_details_groups[i]; i++) - row[i].info = (const NMMetaAbstractInfo *) nmc_fields_con_active_details_groups[i]; - - print_required_fields(&nmc->nmc_config, - &nmc->pager_data, - NMC_OF_FLAG_MAIN_HEADER_ONLY, - out_indices, - header_name, - 0, - row); - } - - /* Loop through the groups and print them. */ - for (i = 0; i < print_groups->len; i++) { - int group_idx = g_array_index(print_groups, int, i); - char *group_fld = (char *) g_ptr_array_index(group_fields, i); - - if (NM_IN_SET(nmc->nmc_config.print_output, NMC_PRINT_NORMAL, NMC_PRINT_PRETTY) - && !nmc->nmc_config.multiline_output && was_output) - g_print("\n"); - - was_output = FALSE; - - if (nmc_fields_con_active_details_groups[group_idx]->nested == metagen_con_active_general) { - gs_free char *f = NULL; - - if (group_fld) - f = g_strdup_printf("GENERAL.%s", group_fld); - - nmc_print(&nmc->nmc_config, - (gpointer[]){acon, NULL}, - NULL, - NULL, - NMC_META_GENERIC_GROUP("GENERAL", metagen_con_active_general, N_("GROUP")), - f, - NULL); - was_output = TRUE; - continue; - } - - /* IP4 */ - if (g_ascii_strcasecmp(nmc_fields_con_active_details_groups[group_idx]->name, - nmc_fields_con_active_details_groups[1]->name) - == 0) { - gboolean b1 = FALSE; - NMIPConfig *cfg4 = nm_active_connection_get_ip4_config(acon); - - b1 = print_ip_config(cfg4, AF_INET, &nmc->nmc_config, group_fld); - was_output = was_output || b1; - } - - /* DHCP4 */ - if (g_ascii_strcasecmp(nmc_fields_con_active_details_groups[group_idx]->name, - nmc_fields_con_active_details_groups[2]->name) - == 0) { - gboolean b1 = FALSE; - NMDhcpConfig *dhcp4 = nm_active_connection_get_dhcp4_config(acon); - - b1 = print_dhcp_config(dhcp4, AF_INET, &nmc->nmc_config, group_fld); - was_output = was_output || b1; - } - - /* IP6 */ - if (g_ascii_strcasecmp(nmc_fields_con_active_details_groups[group_idx]->name, - nmc_fields_con_active_details_groups[3]->name) - == 0) { - gboolean b1 = FALSE; - NMIPConfig *cfg6 = nm_active_connection_get_ip6_config(acon); - - b1 = print_ip_config(cfg6, AF_INET6, &nmc->nmc_config, group_fld); - was_output = was_output || b1; - } - - /* DHCP6 */ - if (g_ascii_strcasecmp(nmc_fields_con_active_details_groups[group_idx]->name, - nmc_fields_con_active_details_groups[4]->name) - == 0) { - gboolean b1 = FALSE; - NMDhcpConfig *dhcp6 = nm_active_connection_get_dhcp6_config(acon); - - b1 = print_dhcp_config(dhcp6, AF_INET6, &nmc->nmc_config, group_fld); - was_output = was_output || b1; - } - - if (nmc_fields_con_active_details_groups[group_idx]->nested == metagen_con_active_vpn) { - if (NM_IS_VPN_CONNECTION(acon)) { - nmc_print(&nmc->nmc_config, - (gpointer[]){acon, NULL}, - NULL, - NULL, - NMC_META_GENERIC_GROUP("VPN", metagen_con_active_vpn, N_("NAME")), - group_fld, - NULL); - was_output = TRUE; - } - continue; - } - } - - g_array_free(print_groups, TRUE); - if (group_fields) - g_ptr_array_free(group_fields, TRUE); - - return TRUE; -} - -static gboolean -split_required_fields_for_con_show(const char *input, - char ** profile_flds, - char ** active_flds, - GError ** error) -{ - gs_free const char **fields = NULL; - const char *const * iter; - nm_auto_free_gstring GString *str1 = NULL; - nm_auto_free_gstring GString *str2 = NULL; - gboolean group_profile = FALSE; - gboolean group_active = FALSE; - gboolean do_free; - - if (!input) { - *profile_flds = NULL; - *active_flds = NULL; - return TRUE; - } - - str1 = g_string_new(NULL); - str2 = g_string_new(NULL); - - fields = nm_utils_strsplit_set_with_empty(input, ","); - for (iter = fields; iter && *iter; iter++) { - char * s_mutable = (char *) (*iter); - char * dot; - gboolean is_all; - gboolean is_common; - gboolean found; - int i; - - g_strstrip(s_mutable); - dot = strchr(s_mutable, '.'); - if (dot) - *dot = '\0'; - - is_all = !dot && g_ascii_strcasecmp(s_mutable, "all") == 0; - is_common = !dot && g_ascii_strcasecmp(s_mutable, "common") == 0; - - found = FALSE; - for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { - if (is_all || is_common - || !g_ascii_strcasecmp(s_mutable, nm_meta_setting_infos[i].setting_name)) { - if (dot) - *dot = '.'; - g_string_append(str1, s_mutable); - g_string_append_c(str1, ','); - found = TRUE; - break; - } - } - if (found) - continue; - - for (i = 0; nmc_fields_con_active_details_groups[i]; i++) { - if (is_all || is_common - || !g_ascii_strcasecmp(s_mutable, nmc_fields_con_active_details_groups[i]->name)) { - if (dot) - *dot = '.'; - g_string_append(str2, s_mutable); - g_string_append_c(str2, ','); - found = TRUE; - break; - } - } - if (!found) { - if (dot) - *dot = '.'; - if (!g_ascii_strcasecmp(s_mutable, CON_SHOW_DETAIL_GROUP_PROFILE)) - group_profile = TRUE; - else if (!g_ascii_strcasecmp(s_mutable, CON_SHOW_DETAIL_GROUP_ACTIVE)) - group_active = TRUE; - else { - gs_free char *allowed1 = nm_meta_abstract_infos_get_names_str( - (const NMMetaAbstractInfo *const *) nm_meta_setting_infos_editor_p(), - NULL); - gs_free char *allowed2 = nm_meta_abstract_infos_get_names_str( - (const NMMetaAbstractInfo *const *) nmc_fields_con_active_details_groups, - NULL); - - g_set_error(error, - NMCLI_ERROR, - 0, - _("invalid field '%s'; allowed fields: %s and %s, or %s,%s"), - s_mutable, - allowed1, - allowed2, - CON_SHOW_DETAIL_GROUP_PROFILE, - CON_SHOW_DETAIL_GROUP_ACTIVE); - return FALSE; - } - } - } - - /* Handle pseudo groups: profile, active */ - if (group_profile) { - if (str1->len > 0) { - g_set_error(error, - NMCLI_ERROR, - 0, - _("'%s' has to be alone"), - CON_SHOW_DETAIL_GROUP_PROFILE); - return FALSE; - } - g_string_assign(str1, "all,"); - } - if (group_active) { - if (str2->len > 0) { - g_set_error(error, - NMCLI_ERROR, - 0, - _("'%s' has to be alone"), - CON_SHOW_DETAIL_GROUP_ACTIVE); - return FALSE; - } - g_string_assign(str2, "all,"); - } - - if (str1->len > 0) - g_string_truncate(str1, str1->len - 1); - if (str2->len > 0) - g_string_truncate(str2, str2->len - 1); - - do_free = (str1->len == 0); - *profile_flds = g_string_free(g_steal_pointer(&str1), do_free); - do_free = (str2->len == 0); - *active_flds = g_string_free(g_steal_pointer(&str2), do_free); - return TRUE; -} - -typedef enum { - NMC_SORT_ACTIVE = 1, - NMC_SORT_ACTIVE_INV = -1, - NMC_SORT_NAME = 2, - NMC_SORT_NAME_INV = -2, - NMC_SORT_TYPE = 3, - NMC_SORT_TYPE_INV = -3, - NMC_SORT_PATH = 4, - NMC_SORT_PATH_INV = -4, -} NmcSortOrder; - -typedef struct { - NmCli * nmc; - const GArray *order; - gboolean show_active_fields; -} ConShowSortInfo; - -static int -con_show_get_items_cmp(gconstpointer pa, gconstpointer pb, gpointer user_data) -{ - const ConShowSortInfo * sort_info = user_data; - const MetagenConShowRowData *row_data_a = *((const MetagenConShowRowData *const *) pa); - const MetagenConShowRowData *row_data_b = *((const MetagenConShowRowData *const *) pb); - NMConnection * c_a = row_data_a->connection; - NMConnection * c_b = row_data_b->connection; - NMActiveConnection * ac_a = row_data_a->primary_active; - NMActiveConnection * ac_b = row_data_b->primary_active; - NMActiveConnection * ac_a_effective = sort_info->show_active_fields ? ac_a : NULL; - NMActiveConnection * ac_b_effective = sort_info->show_active_fields ? ac_b : NULL; - - /* first sort active-connections which are invisible, i.e. that have no connection */ - if (!c_a && c_b) - return -1; - if (!c_b && c_a) - return 1; - - /* we have two connections... */ - if (c_a && c_b && c_a != c_b) { - const NmcSortOrder * order_arr; - guint i, order_len; - NMMetaAccessorGetType get_type = - nmc_print_output_to_accessor_get_type(sort_info->nmc->nmc_config.print_output); - - if (sort_info->order) { - order_arr = &g_array_index(sort_info->order, NmcSortOrder, 0); - order_len = sort_info->order->len; - } else { - static const NmcSortOrder def[] = {NMC_SORT_ACTIVE, NMC_SORT_NAME, NMC_SORT_PATH}; - - /* Note: the default order does not consider whether a column is shown. - * That means, the selection of the output fields, does not affect the - * order (although there could be an argument that it should). */ - order_arr = def; - order_len = G_N_ELEMENTS(def); - } - - for (i = 0; i < order_len; i++) { - NmcSortOrder item = order_arr[i]; - - switch (item) { - case NMC_SORT_ACTIVE: - NM_CMP_RETURN(nmc_active_connection_cmp(ac_b, ac_a)); - break; - case NMC_SORT_ACTIVE_INV: - NM_CMP_RETURN(nmc_active_connection_cmp(ac_a, ac_b)); - break; - - case NMC_SORT_TYPE: - NM_CMP_DIRECT_STRCMP0(_con_show_fcn_get_type(c_a, ac_a_effective, get_type), - _con_show_fcn_get_type(c_b, ac_b_effective, get_type)); - break; - case NMC_SORT_TYPE_INV: - NM_CMP_DIRECT_STRCMP0(_con_show_fcn_get_type(c_b, ac_b_effective, get_type), - _con_show_fcn_get_type(c_a, ac_a_effective, get_type)); - break; - - case NMC_SORT_NAME: - NM_CMP_RETURN(nm_utf8_collate0(_con_show_fcn_get_id(c_a, ac_a_effective), - _con_show_fcn_get_id(c_b, ac_b_effective))); - break; - case NMC_SORT_NAME_INV: - NM_CMP_RETURN(nm_utf8_collate0(_con_show_fcn_get_id(c_b, ac_b_effective), - _con_show_fcn_get_id(c_a, ac_a_effective))); - break; - - case NMC_SORT_PATH: - NM_CMP_RETURN(nm_utils_dbus_path_cmp(nm_connection_get_path(c_a), - nm_connection_get_path(c_b))); - break; - - case NMC_SORT_PATH_INV: - NM_CMP_RETURN(nm_utils_dbus_path_cmp(nm_connection_get_path(c_b), - nm_connection_get_path(c_a))); - break; - - default: - nm_assert_not_reached(); - break; - } - } - - NM_CMP_DIRECT_STRCMP0(nm_connection_get_uuid(c_a), nm_connection_get_uuid(c_b)); - NM_CMP_DIRECT_STRCMP0(nm_connection_get_path(c_a), nm_connection_get_path(c_b)); - } - - NM_CMP_DIRECT_STRCMP0(nm_object_get_path(NM_OBJECT(ac_a)), nm_object_get_path(NM_OBJECT(ac_b))); - - g_return_val_if_reached(0); -} - -static GPtrArray * -con_show_get_items(NmCli *nmc, gboolean active_only, gboolean show_active_fields, GArray *order) -{ - gs_unref_hashtable GHashTable *row_hash = NULL; - GHashTableIter hiter; - GPtrArray * result; - const GPtrArray * arr; - NMRemoteConnection * c; - MetagenConShowRowData * row_data; - guint i; - const ConShowSortInfo sort_info = { - .nmc = nmc, - .order = order, - .show_active_fields = show_active_fields, - }; - - row_hash = g_hash_table_new(nm_direct_hash, NULL); - - arr = nm_client_get_connections(nmc->client); - for (i = 0; i < arr->len; i++) { - /* Note: libnm will not expose connection that are invisible - * to the user but currently inactive. - * - * That differs from get-active-connection(). If an invisible connection - * is active, we can get its NMActiveConnection. We can even obtain - * the corresponding NMRemoteConnection (although, of course it has - * no visible settings). - * - * I think this inconsistency is a bug in libnm. Anyway, the result is, - * that we print invisible connections if they are active, but otherwise - * we exclude them. */ - c = arr->pdata[i]; - g_hash_table_insert(row_hash, - c, - _metagen_con_show_row_data_new_for_connection(c, show_active_fields)); - } - - arr = nm_client_get_active_connections(nmc->client); - for (i = 0; i < arr->len; i++) { - NMActiveConnection *ac = arr->pdata[i]; - - c = nm_active_connection_get_connection(ac); - if (!show_active_fields && !c) { - /* the active connection has no connection, and we don't show - * any active fields. Skip this row. */ - continue; - } - - row_data = c ? g_hash_table_lookup(row_hash, c) : NULL; - - if (show_active_fields || !c) { - /* the active connection either has no connection (in which we create a - * connection-less row), or we are interested in showing each active - * connection in its own row. Add a row. */ - if (row_data) { - /* we create a rowdata for this connection earlier. We drop it, because this - * connection is tracked via the rowdata of the active connection. */ - g_hash_table_remove(row_hash, c); - _metagen_con_show_row_data_destroy(row_data); - } - row_data = - _metagen_con_show_row_data_new_for_active_connection(c, ac, show_active_fields); - g_hash_table_insert(row_hash, ac, row_data); - continue; - } - - /* we add the active connection to the row for the referenced - * connection. We need to group them this way, to print the proper - * color (activated or not) based on primary_active. */ - if (!row_data) { - /* this is unexpected. The active connection references a connection that - * seemingly no longer exists. It's a bug in libnm. Add a row nonetheless. */ - row_data = _metagen_con_show_row_data_new_for_connection(c, show_active_fields); - g_hash_table_insert(row_hash, c, row_data); - } - _metagen_con_show_row_data_add_active_connection(row_data, ac); - } - - result = g_ptr_array_new_with_free_func(_metagen_con_show_row_data_destroy); - - g_hash_table_iter_init(&hiter, row_hash); - while (g_hash_table_iter_next(&hiter, NULL, (gpointer *) &row_data)) { - if (active_only && !row_data->primary_active) { - /* We only print connections that are active. Skip this row. */ - _metagen_con_show_row_data_destroy(row_data); - continue; - } - if (!show_active_fields) { - NMSettingConnection *s_con; - - nm_assert(NM_IS_REMOTE_CONNECTION(row_data->connection)); - s_con = nm_connection_get_setting_connection(row_data->connection); - if (!s_con || !nm_setting_connection_get_uuid(s_con)) { - /* we are in a mode, where we only print rows for connection. - * For that we require that all rows are visible to the user, - * meaning: the have a [connection] setting and a UUID. - * - * Otherwise, this connection is likely invisible to the user. - * Skip it. */ - _metagen_con_show_row_data_destroy(row_data); - continue; - } - _metagen_con_show_row_data_init_primary_active(row_data); - } else - nm_assert(!row_data->all_active); - g_ptr_array_add(result, row_data); - } - - g_ptr_array_sort_with_data(result, con_show_get_items_cmp, (gpointer) &sort_info); - return result; -} - -static GArray * -parse_preferred_connection_order(const char *order, GError **error) -{ - gs_free const char **strv = NULL; - const char *const * iter; - const char * str; - GArray * order_arr; - NmcSortOrder val; - gboolean inverse, unique; - guint i; - - strv = nm_utils_strsplit_set(order, ":"); - if (!strv) { - g_set_error(error, NMCLI_ERROR, 0, _("incorrect string '%s' of '--order' option"), order); - return NULL; - } - - order_arr = g_array_sized_new(FALSE, FALSE, sizeof(NmcSortOrder), 4); - for (iter = strv; iter && *iter; iter++) { - str = *iter; - inverse = FALSE; - if (str[0] == '-') - inverse = TRUE; - if (str[0] == '+' || str[0] == '-') - str++; - - if (matches(str, "active")) - val = inverse ? NMC_SORT_ACTIVE_INV : NMC_SORT_ACTIVE; - else if (matches(str, "name")) - val = inverse ? NMC_SORT_NAME_INV : NMC_SORT_NAME; - else if (matches(str, "type")) - val = inverse ? NMC_SORT_TYPE_INV : NMC_SORT_TYPE; - else if (matches(str, "path")) - val = inverse ? NMC_SORT_PATH_INV : NMC_SORT_PATH; - else { - g_array_unref(order_arr); - order_arr = NULL; - g_set_error(error, NMCLI_ERROR, 0, _("incorrect item '%s' in '--order' option"), *iter); - break; - } - /* Check for duplicates and ignore them. */ - unique = TRUE; - for (i = 0; i < order_arr->len; i++) { - if (abs(g_array_index(order_arr, NmcSortOrder, i)) - abs(val) == 0) { - unique = FALSE; - break; - } - } - - /* Value is ok and unique, add it to the array */ - if (unique) - g_array_append_val(order_arr, val); - } - - return order_arr; -} - -static NMConnection * -get_connection(NmCli * nmc, - int * argc, - const char *const **argv, - const char ** out_selector, - const char ** out_value, - GPtrArray ** out_result, - GError ** error) -{ - const GPtrArray *connections; - NMConnection * connection = NULL; - const char * selector = NULL; - - NM_SET_OUT(out_selector, NULL); - NM_SET_OUT(out_value, NULL); - - if (*argc == 0) { - g_set_error_literal(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("No connection specified")); - return NULL; - } - - if (*argc == 1 && nmc->complete) - nmc_complete_strings(**argv, "id", "uuid", "path", "filename"); - - if (NM_IN_STRSET(**argv, "id", "uuid", "path", "filename")) { - if (*argc == 1) { - if (!nmc->complete) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("%s argument is missing"), - selector); - return NULL; - } - } else { - selector = **argv; - (*argv)++; - (*argc)--; - } - } - - NM_SET_OUT(out_selector, selector); - NM_SET_OUT(out_value, **argv); - - connections = nm_client_get_connections(nmc->client); - connection = - nmc_find_connection(connections, selector, **argv, out_result, *argc == 1 && nmc->complete); - if (!connection) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_NOT_FOUND, - _("unknown connection '%s'"), - **argv); - } - - next_arg(nmc, argc, argv, NULL); - return connection; -} - -static void -do_connections_show(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - gs_free_error GError *err = NULL; - gs_free char * profile_flds = NULL; - gs_free char * active_flds = NULL; - gboolean active_only = FALSE; - gs_unref_array GArray *order = NULL; - guint i; - int option; - - /* check connection show options [--active] [--order <order spec>] */ - while ((option = next_arg(nmc, &argc, &argv, "--active", "--order", NULL)) > 0) { - switch (option) { - case 1: /* --active */ - active_only = TRUE; - break; - case 2: /* --order */ - argc--; - argv++; - if (!argc) { - g_set_error_literal(&err, NMCLI_ERROR, 0, _("'--order' argument is missing")); - goto finish; - } - order = parse_preferred_connection_order(*argv, &err); - if (err) - goto finish; - break; - default: - g_assert_not_reached(); - break; - } - } - - if (argc == 0) { - const char * fields_str = NULL; - gs_unref_ptrarray GPtrArray *items = NULL; - gs_free NMMetaSelectionResultList *selection = NULL; - gboolean show_active_fields = TRUE; - - if (nmc->complete) - goto finish; - - if (!nmc->required_fields || g_ascii_strcasecmp(nmc->required_fields, "common") == 0) - fields_str = NMC_FIELDS_CON_SHOW_COMMON; - else if (!nmc->required_fields || g_ascii_strcasecmp(nmc->required_fields, "all") == 0) { - /* pass */ - } else - fields_str = nmc->required_fields; - - /* determine whether the user wants to see any fields that are related to active-connections - * (e.g. the apath, the current state, or the device where the profile is active). - * - * If that's the case, then we will show one line for each active connection. In case - * a profile has multiple active connections, it will be listed multiple times. - * If that's not the case, we filter out these duplicate lines. */ - selection = nm_meta_selection_create_parse_list( - (const NMMetaAbstractInfo *const *) metagen_con_show, - fields_str, - FALSE, - NULL); - if (selection && selection->num > 0) { - show_active_fields = FALSE; - for (i = 0; i < selection->num; i++) { - const NmcMetaGenericInfo *info = - (const NmcMetaGenericInfo *) selection->items[i].info; - - if (NM_IN_SET(info->info_type, - NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE, - NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE, - NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE, - NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH)) { - show_active_fields = TRUE; - break; - } - } - } - - nm_cli_spawn_pager(&nmc->nmc_config, &nmc->pager_data); - - items = con_show_get_items(nmc, active_only, show_active_fields, order); - g_ptr_array_add(items, NULL); - if (!nmc_print(&nmc->nmc_config, - items->pdata, - NULL, - active_only ? _("NetworkManager active profiles") - : _("NetworkManager connection profiles"), - (const NMMetaAbstractInfo *const *) metagen_con_show, - fields_str, - &err)) - goto finish; - } else { - gboolean new_line = FALSE; - gboolean without_fields = (nmc->required_fields == NULL); - const GPtrArray *active_cons = nm_client_get_active_connections(nmc->client); - - /* multiline mode is default for 'connection show <ID>' */ - if (!nmc->mode_specified) - nmc->nmc_config_mutable.multiline_output = TRUE; - - /* Split required fields into the settings and active ones. */ - if (!split_required_fields_for_con_show(nmc->required_fields, - &profile_flds, - &active_flds, - &err)) - goto finish; - - nm_clear_g_free(&nmc->required_fields); - - /* Before printing the connections check if we have a "--show-secret" - * option after the connection ids */ - if (!nmc->nmc_config.show_secrets && !nmc->complete) { - int argc_cp = argc; - const char *const *argv_cp = argv; - - do { - if (NM_IN_STRSET(*argv_cp, "id", "uuid", "path", "filename", "apath")) { - argc_cp--; - argv_cp++; - } - } while (next_arg(nmc, &argc_cp, &argv_cp, NULL) != -1); - } - - while (argc > 0) { - const GPtrArray *connections; - gboolean res; - NMConnection * con; - gs_unref_object NMActiveConnection *explicit_acon = NULL; - const char * selector = NULL; - gs_unref_ptrarray GPtrArray *found_cons = NULL; - gboolean explicit_acon_handled = FALSE; - guint i_found_cons; - - if (argc == 1 && nmc->complete) - nmc_complete_strings(*argv, "id", "uuid", "path", "filename", "apath"); - - if (NM_IN_STRSET(*argv, "id", "uuid", "path", "filename", "apath")) { - selector = *argv; - argc--; - argv++; - if (!argc) { - g_string_printf(nmc->return_text, - _("Error: %s argument is missing."), - *(argv - 1)); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - } - - /* Try to find connection by id, uuid or path first */ - connections = nm_client_get_connections(nmc->client); - con = nmc_find_connection(connections, - selector, - *argv, - &found_cons, - argc == 1 && nmc->complete); - if (!con && NM_IN_STRSET(selector, NULL, "apath")) { - /* Try apath too */ - explicit_acon = nmc_find_active_connection(active_cons, - "apath", - *argv, - NULL, - argc == 1 && nmc->complete); - if (explicit_acon) { - if (!selector - && !nm_streq0(*argv, nm_object_get_path(NM_OBJECT(explicit_acon)))) { - /* we matched the apath based on the last component alone (note the full D-Bus path). - * That is how nmc_find_active_connection() works, if you pass in a selector. - * Reject it. */ - explicit_acon = NULL; - } - nm_g_object_ref(explicit_acon); - } - } - - if (!con && !explicit_acon) { - g_string_printf(nmc->return_text, - _("Error: %s - no such connection profile."), - *argv); - nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; - goto finish; - } - - /* Print connection details: - * Usually we have both static and active connection. - * But when a connection is private to a user, another user - * may see only the active connection. - */ - - if (nmc->complete) { - next_arg(nmc, &argc, &argv, NULL); - continue; - } - - explicit_acon_handled = FALSE; - i_found_cons = 0; - for (;;) { - gs_unref_ptrarray GPtrArray *found_acons = NULL; - - if (explicit_acon) { - if (explicit_acon_handled) - break; - explicit_acon_handled = TRUE; - /* the user referenced an "apath". In this case, we can only have at most one connection - * and one apath. */ - con = NM_CONNECTION(nm_active_connection_get_connection(explicit_acon)); - } else { - if (i_found_cons >= found_cons->len) - break; - con = found_cons->pdata[i_found_cons++]; - get_ac_for_connection(active_cons, con, &found_acons); - } - - if (active_only && !explicit_acon && !found_acons) { - /* this connection is not interesting, we only print active ones. */ - continue; - } - - nm_assert(explicit_acon || con); - - if (new_line) - g_print("\n"); - new_line = TRUE; - - if (without_fields || profile_flds) { - if (con) { - nmc->required_fields = profile_flds; - if (nmc->nmc_config.show_secrets) - update_secrets_in_connection(NM_REMOTE_CONNECTION(con), con); - res = nmc_connection_profile_details(con, nmc); - nmc->required_fields = NULL; - if (!res) - goto finish; - } - } - - if (without_fields || active_flds) { - guint l = explicit_acon ? 1 : (found_acons ? found_acons->len : 0); - - for (i = 0; i < l; i++) { - NMActiveConnection *acon; - - if (i > 0) { - /* if there are multiple active connections, separate them with newline. - * that is a bit odd, because we already separate connections with newlines, - * and commonly don't separate the connection from the first active connection. */ - g_print("\n"); - } - - if (explicit_acon) - acon = explicit_acon; - else - acon = found_acons->pdata[i]; - - nmc->required_fields = active_flds; - res = nmc_active_connection_details(acon, nmc); - nmc->required_fields = NULL; - if (!res) - goto finish; - } - } - } - - next_arg(nmc, &argc, &argv, NULL); - } - } - -finish: - if (err) { - g_string_printf(nmc->return_text, _("Error: %s."), err->message); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - } -} - -static NMActiveConnection * -get_default_active_connection(NmCli *nmc, NMDevice **device) -{ - NMActiveConnection *default_ac = NULL; - NMDevice * non_default_device = NULL; - NMActiveConnection *non_default_ac = NULL; - const GPtrArray * connections; - guint i; - - g_return_val_if_fail(nmc, NULL); - g_return_val_if_fail(device, NULL); - g_return_val_if_fail(*device == NULL, NULL); - - connections = nm_client_get_active_connections(nmc->client); - for (i = 0; i < connections->len; i++) { - NMActiveConnection *candidate = g_ptr_array_index(connections, i); - const GPtrArray * devices; - - devices = nm_active_connection_get_devices(candidate); - if (!devices->len) - continue; - - if (nm_active_connection_get_default(candidate)) { - if (!default_ac) { - *device = g_ptr_array_index(devices, 0); - default_ac = candidate; - } - } else { - if (!non_default_ac) { - non_default_device = g_ptr_array_index(devices, 0); - non_default_ac = candidate; - } - } - } - - /* Prefer the default connection if one exists, otherwise return the first - * non-default connection. - */ - if (!default_ac && non_default_ac) { - default_ac = non_default_ac; - *device = non_default_device; - } - return default_ac; -} - -/* Find a device to activate the connection on. - * IN: connection: connection to activate - * iface: device interface name to use (optional) - * ap: access point to use (optional; valid just for 802-11-wireless) - * nsp: Network Service Provider to use (option; valid only for wimax) - * OUT: device: found device - * spec_object: specific_object path of NMAccessPoint - * RETURNS: TRUE when a device is found, FALSE otherwise. - */ -static gboolean -find_device_for_connection(NmCli * nmc, - NMConnection *connection, - const char * iface, - const char * ap, - const char * nsp, - NMDevice ** device, - const char ** spec_object, - GError ** error) -{ - NMSettingConnection *s_con; - const char * con_type; - guint i, j; - - g_return_val_if_fail(nmc, FALSE); - g_return_val_if_fail(iface || ap || nsp, FALSE); - g_return_val_if_fail(device && *device == NULL, FALSE); - g_return_val_if_fail(spec_object && *spec_object == NULL, FALSE); - g_return_val_if_fail(error == NULL || *error == NULL, FALSE); - - s_con = nm_connection_get_setting_connection(connection); - g_assert(s_con); - con_type = nm_setting_connection_get_connection_type(s_con); - - if (strcmp(con_type, NM_SETTING_VPN_SETTING_NAME) == 0) { - /* VPN connections */ - NMActiveConnection *active = NULL; - if (iface) { - *device = nm_client_get_device_by_iface(nmc->client, iface); - if (*device) - active = nm_device_get_active_connection(*device); - - if (!active) { - g_set_error(error, NMCLI_ERROR, 0, _("no active connection on device '%s'"), iface); - return FALSE; - } - *spec_object = nm_object_get_path(NM_OBJECT(active)); - return TRUE; - } else { - active = get_default_active_connection(nmc, device); - if (!active) { - g_set_error_literal(error, NMCLI_ERROR, 0, _("no active connection or device")); - return FALSE; - } - *spec_object = nm_object_get_path(NM_OBJECT(active)); - return TRUE; - } - } else { - /* Other connections */ - NMDevice * found_device = NULL; - const GPtrArray *devices = nm_client_get_devices(nmc->client); - - for (i = 0; i < devices->len && !found_device; i++) { - NMDevice *dev = g_ptr_array_index(devices, i); - - if (iface) { - const char *dev_iface = nm_device_get_iface(dev); - if (!nm_streq0(dev_iface, iface)) - continue; - - if (!nm_device_connection_compatible(dev, connection, error)) { - g_prefix_error(error, - _("device '%s' not compatible with connection '%s': "), - iface, - nm_setting_connection_get_id(s_con)); - return FALSE; - } - - } else { - if (!nm_device_connection_compatible(dev, connection, NULL)) - continue; - } - - found_device = dev; - if (ap && nm_streq(con_type, NM_SETTING_WIRELESS_SETTING_NAME) - && NM_IS_DEVICE_WIFI(dev)) { - gs_free char * bssid_up = g_ascii_strup(ap, -1); - const GPtrArray *aps = nm_device_wifi_get_access_points(NM_DEVICE_WIFI(dev)); - found_device = - NULL; /* Mark as not found; set to the device again later, only if AP matches */ - - for (j = 0; j < aps->len; j++) { - NMAccessPoint *candidate_ap = g_ptr_array_index(aps, j); - const char * candidate_bssid = nm_access_point_get_bssid(candidate_ap); - - if (nm_streq0(bssid_up, candidate_bssid)) { - found_device = dev; - *spec_object = nm_object_get_path(NM_OBJECT(candidate_ap)); - break; - } - } - } - } - - if (!found_device) { - if (iface) { - g_set_error(error, - NMCLI_ERROR, - 0, - _("device '%s' not compatible with connection '%s'"), - iface, - nm_setting_connection_get_id(s_con)); - } else { - g_set_error(error, - NMCLI_ERROR, - 0, - _("no device found for connection '%s'"), - nm_setting_connection_get_id(s_con)); - } - return FALSE; - } - - *device = found_device; - return TRUE; - } -} - -typedef struct { - NmCli * nmc; - NMDevice * device; - NMActiveConnection *active; -} ActivateConnectionInfo; - -static void -active_connection_hint(GString *return_text, NMActiveConnection *active, NMDevice *device) -{ - NMRemoteConnection * connection; - nm_auto_free_gstring GString *hint = NULL; - const GPtrArray * devices; - guint i; - - if (!active) - return; - - if (!nm_streq(NM_CONFIG_DEFAULT_LOGGING_BACKEND, "journal")) - return; - - connection = nm_active_connection_get_connection(active); - g_return_if_fail(connection); - - hint = g_string_new("journalctl -xe "); - g_string_append_printf(hint, - "NM_CONNECTION=%s", - nm_connection_get_uuid(NM_CONNECTION(connection))); - - if (device) - g_string_append_printf(hint, " + NM_DEVICE=%s", nm_device_get_iface(device)); - else { - devices = nm_active_connection_get_devices(active); - for (i = 0; i < devices->len; i++) { - g_string_append_printf(hint, - " + NM_DEVICE=%s", - nm_device_get_iface(NM_DEVICE(g_ptr_array_index(devices, i)))); - } - } - - g_string_append(return_text, "\n"); - g_string_append_printf(return_text, _("Hint: use '%s' to get more details."), hint->str); -} - -static void activate_connection_info_finish(ActivateConnectionInfo *info); - -static void -check_activated(ActivateConnectionInfo *info) -{ - NMActiveConnectionState ac_state; - NmCli * nmc = info->nmc; - const char * reason = NULL; - - ac_state = nmc_activation_get_effective_state(info->active, info->device, &reason); - switch (ac_state) { - case NM_ACTIVE_CONNECTION_STATE_ACTIVATED: - if (nmc->nmc_config.print_output == NMC_PRINT_PRETTY) - nmc_terminal_erase_line(); - if (reason) { - g_print(_("Connection successfully activated (%s) (D-Bus active path: %s)\n"), - reason, - nm_object_get_path(NM_OBJECT(info->active))); - } else { - g_print(_("Connection successfully activated (D-Bus active path: %s)\n"), - nm_object_get_path(NM_OBJECT(info->active))); - } - activate_connection_info_finish(info); - break; - case NM_ACTIVE_CONNECTION_STATE_DEACTIVATED: - nm_assert(reason); - g_string_printf(nmc->return_text, _("Error: Connection activation failed: %s"), reason); - active_connection_hint(nmc->return_text, info->active, info->device); - nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; - activate_connection_info_finish(info); - break; - case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: - if (nmc->secret_agent) { - NMRemoteConnection *connection = nm_active_connection_get_connection(info->active); - - nm_secret_agent_simple_enable(nmc->secret_agent, - nm_connection_get_path(NM_CONNECTION(connection))); - } - break; - default: - break; - } -} - -static void -device_state_cb(NMDevice *device, GParamSpec *pspec, ActivateConnectionInfo *info) -{ - check_activated(info); -} - -static void -active_connection_state_cb(NMActiveConnection * active, - NMActiveConnectionState state, - NMActiveConnectionStateReason reason, - ActivateConnectionInfo * info) -{ - check_activated(info); -} - -static void -set_nmc_error_timeout(NmCli *nmc) -{ - g_string_printf(nmc->return_text, _("Error: Timeout expired (%d seconds)"), nmc->timeout); - nmc->return_value = NMC_RESULT_ERROR_TIMEOUT_EXPIRED; -} - -static gboolean -activate_connection_timeout_cb(gpointer user_data) -{ - ActivateConnectionInfo *info = user_data; - - /* Time expired -> exit nmcli */ - set_nmc_error_timeout(info->nmc); - activate_connection_info_finish(info); - return FALSE; -} - -static gboolean -progress_cb(gpointer user_data) -{ - const char *str = (const char *) user_data; - - nmc_terminal_show_progress(str); - - return TRUE; -} - -static gboolean -progress_active_connection_cb(gpointer user_data) -{ - NMActiveConnection * active = user_data; - const char * str; - NMDevice * device; - NMActiveConnectionState ac_state; - const GPtrArray * ac_devs; - - ac_state = nm_active_connection_get_state(active); - - if (ac_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING) { - /* If the connection is activating, the device state - * is more interesting. */ - ac_devs = nm_active_connection_get_devices(active); - device = ac_devs->len > 0 ? g_ptr_array_index(ac_devs, 0) : NULL; - } else { - device = NULL; - } - - str = device ? gettext(nmc_device_state_to_string_with_external(device)) - : active_connection_state_to_string(ac_state); - - nmc_terminal_show_progress(str); - - return TRUE; -} - -static void -activate_connection_info_finish(ActivateConnectionInfo *info) -{ - if (info->device) { - g_signal_handlers_disconnect_by_func(info->device, G_CALLBACK(device_state_cb), info); - g_object_unref(info->device); - } - - if (info->active) { - g_signal_handlers_disconnect_by_func(info->active, - G_CALLBACK(active_connection_state_cb), - info); - g_object_unref(info->active); - } - - g_free(info); - quit(); -} - -static void -activate_connection_cb(GObject *client, GAsyncResult *result, gpointer user_data) -{ - ActivateConnectionInfo *info = (ActivateConnectionInfo *) user_data; - NmCli * nmc = info->nmc; - NMDevice * device = info->device; - NMActiveConnection * active; - NMActiveConnectionState state; - const GPtrArray * ac_devs; - GError * error = NULL; - - info->active = active = nm_client_activate_connection_finish(NM_CLIENT(client), result, &error); - - if (error) { - g_string_printf(nmc->return_text, - _("Error: Connection activation failed: %s"), - error->message); - g_error_free(error); - active_connection_hint(nmc->return_text, info->active, info->device); - nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; - activate_connection_info_finish(info); - } else { - state = nm_active_connection_get_state(active); - if (!device && !nm_active_connection_get_vpn(active)) { - /* device could be NULL for virtual devices. Fill it here. */ - ac_devs = nm_active_connection_get_devices(active); - device = ac_devs->len > 0 ? g_ptr_array_index(ac_devs, 0) : NULL; - if (device) - info->device = g_object_ref(device); - } - - if (nmc->nowait_flag || state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { - /* User doesn't want to wait or already activated */ - if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { - if (nmc->nmc_config.print_output == NMC_PRINT_PRETTY) - nmc_terminal_erase_line(); - g_print(_("Connection successfully activated (D-Bus active path: %s)\n"), - nm_object_get_path(NM_OBJECT(active))); - } - activate_connection_info_finish(info); - } else { - /* Monitor the active connection and device (if available) states */ - g_signal_connect(active, "state-changed", G_CALLBACK(active_connection_state_cb), info); - if (device) - g_signal_connect(device, - "notify::" NM_DEVICE_STATE, - G_CALLBACK(device_state_cb), - info); - /* Both active_connection_state_cb () and device_state_cb () will just - * call check_activated (info). So, just call it once directly after - * connecting on both the signals of the objects and skip the call to - * the callbacks. - */ - check_activated(info); - - /* Start progress indication showing VPN states */ - if (nmc->nmc_config.print_output == NMC_PRINT_PRETTY) { - if (progress_id) - g_source_remove(progress_id); - progress_id = g_timeout_add(120, progress_active_connection_cb, active); - } - - /* Start timer not to loop forever when signals are not emitted */ - g_timeout_add_seconds(nmc->timeout, activate_connection_timeout_cb, info); - } - } -} - -static gboolean -nmc_activate_connection(NmCli * nmc, - NMConnection * connection, - const char * ifname, - const char * ap, - const char * nsp, - const char * pwds, - GAsyncReadyCallback callback, - GError ** error) -{ - ActivateConnectionInfo *info; - - GHashTable *pwds_hash; - NMDevice * device = NULL; - const char *spec_object = NULL; - gboolean device_found; - - g_return_val_if_fail(nmc, FALSE); - g_return_val_if_fail(error == NULL || *error == NULL, FALSE); - - if (connection && (ifname || ap || nsp)) { - gs_free_error GError *local = NULL; - - device_found = find_device_for_connection(nmc, - connection, - ifname, - ap, - nsp, - &device, - &spec_object, - &local); - - /* Virtual connection may not have their interfaces created yet */ - if (!device_found && !nm_connection_is_virtual(connection)) { - g_set_error(error, NMCLI_ERROR, NMC_RESULT_ERROR_CON_ACTIVATION, "%s", local->message); - return FALSE; - } - } else if (ifname) { - device = nm_client_get_device_by_iface(nmc->client, ifname); - if (!device) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_NOT_FOUND, - _("unknown device '%s'."), - ifname); - return FALSE; - } - } else if (!connection) { - g_set_error_literal(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_NOT_FOUND, - _("neither a valid connection nor device given")); - return FALSE; - } - - /* Parse passwords given in passwords file */ - { - gs_free_error GError *local = NULL; - gssize error_line; - - pwds_hash = nmc_utils_read_passwd_file(pwds, &error_line, &local); - if (!pwds_hash) { - if (error_line >= 0) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("invalid passwd-file '%s' at line %zd: %s"), - pwds, - error_line, - local->message); - } else { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("invalid passwd-file '%s': %s"), - pwds, - local->message); - } - return FALSE; - } - } - - if (nmc->pwds_hash) - g_hash_table_destroy(nmc->pwds_hash); - nmc->pwds_hash = pwds_hash; - - nmc->secret_agent = nm_secret_agent_simple_new("nmcli-connect"); - if (nmc->secret_agent) { - g_signal_connect(nmc->secret_agent, - NM_SECRET_AGENT_SIMPLE_REQUEST_SECRETS, - G_CALLBACK(nmc_secrets_requested), - nmc); - } - - info = g_malloc0(sizeof(ActivateConnectionInfo)); - info->nmc = nmc; - if (device) - info->device = g_object_ref(device); - - nm_client_activate_connection_async(nmc->client, - connection, - device, - spec_object, - NULL, - callback, - info); - return TRUE; -} - -static void -do_connection_up(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - NMConnection *connection = NULL; - const char * ifname = NULL; - const char * ap = NULL; - const char * nsp = NULL; - const char * pwds = NULL; - gs_free_error GError *error = NULL; - gs_strfreev char ** arg_arr = NULL; - int arg_num; - const char *const ** argv_ptr; - int * argc_ptr; - - /* - * Set default timeout for connection activation. - * Activation can take quite a long time, use 90 seconds. - */ - if (nmc->timeout == -1) - nmc->timeout = 90; - - next_arg(nmc, &argc, &argv, NULL); - argv_ptr = &argv; - argc_ptr = &argc; - - if (argc == 0 && nmc->ask) { - gs_free char *line = NULL; - - /* nmc_do_cmd() should not call this with argc=0. */ - g_assert(!nmc->complete); - - line = nmc_readline(&nmc->nmc_config, PROMPT_CONNECTION); - nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num); - argv_ptr = (const char *const **) &arg_arr; - argc_ptr = &arg_num; - } - - if (argc > 0 && strcmp(*argv, "ifname") != 0) { - connection = get_connection(nmc, argc_ptr, argv_ptr, NULL, NULL, NULL, &error); - if (!connection) { - g_string_printf(nmc->return_text, _("Error: %s."), error->message); - nmc->return_value = error->code; - return; - } - } - - while (argc > 0) { - if (argc == 1 && nmc->complete) - nmc_complete_strings(*argv, "ifname", "ap", "passwd-file"); - - if (strcmp(*argv, "ifname") == 0) { - argc--; - argv++; - if (!argc) { - g_string_printf(nmc->return_text, _("Error: %s argument is missing."), *(argv - 1)); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - ifname = *argv; - if (argc == 1 && nmc->complete) - nmc_complete_device(nmc->client, ifname, ap != NULL); - } else if (strcmp(*argv, "ap") == 0) { - argc--; - argv++; - if (!argc) { - g_string_printf(nmc->return_text, _("Error: %s argument is missing."), *(argv - 1)); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - ap = *argv; - if (argc == 1 && nmc->complete) - nmc_complete_bssid(nmc->client, ifname, ap); - } else if (strcmp(*argv, "passwd-file") == 0) { - argc--; - argv++; - if (!argc) { - g_string_printf(nmc->return_text, _("Error: %s argument is missing."), *(argv - 1)); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - if (argc == 1 && nmc->complete) - nmc->return_value = NMC_RESULT_COMPLETE_FILE; - - pwds = *argv; - } else if (!nmc->complete) { - g_string_printf(nmc->return_text, _("Error: invalid extra argument '%s'."), *argv); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - next_arg(nmc, &argc, &argv, NULL); - } - - if (nmc->complete) - return; - - /* Use nowait_flag instead of should_wait because exiting has to be postponed till - * active_connection_state_cb() is called. That gives NM time to check our permissions - * and we can follow activation progress. - */ - nmc->nowait_flag = (nmc->timeout == 0); - nmc->should_wait++; - - if (!nmc_activate_connection(nmc, - connection, - ifname, - ap, - nsp, - pwds, - activate_connection_cb, - &error)) { - g_string_printf(nmc->return_text, _("Error: %s."), error->message); - nmc->should_wait--; - nmc->return_value = error->code; - return; - } - - /* Start progress indication */ - if (nmc->nmc_config.print_output == NMC_PRINT_PRETTY) - progress_id = g_timeout_add(120, progress_cb, _("preparing")); -} - -/*****************************************************************************/ - -typedef struct { - NmCli *nmc; - /* a list of object that is relevant for the callback. The object - * type differs, and depends on the type of callback. */ - GPtrArray * obj_list; - guint timeout_id; - GCancellable *cancellable; -} ConnectionCbInfo; - -static void -connection_removed_cb(NMClient *client, NMConnection *connection, ConnectionCbInfo *info); - -static void down_active_connection_state_cb(NMActiveConnection *active, - GParamSpec * pspec, - ConnectionCbInfo * info); - -static void -connection_cb_info_obj_list_destroy(ConnectionCbInfo *info, gpointer obj) -{ - nm_assert(info); - nm_assert(info->obj_list); - nm_assert(G_IS_OBJECT(obj)); - - g_signal_handlers_disconnect_by_func(obj, down_active_connection_state_cb, info); - g_object_unref(obj); -} - -static gssize -connection_cb_info_obj_list_idx(ConnectionCbInfo *info, gpointer obj) -{ - guint i; - - nm_assert(info); - nm_assert(info->obj_list); - nm_assert(G_IS_OBJECT(obj)); - - for (i = 0; i < info->obj_list->len; i++) { - if (info->obj_list->pdata[i] == obj) - return i; - } - return -1; -} - -static gpointer -connection_cb_info_obj_list_has(ConnectionCbInfo *info, gpointer obj) -{ - gssize idx; - - idx = connection_cb_info_obj_list_idx(info, obj); - if (idx >= 0) - return info->obj_list->pdata[idx]; - return NULL; -} - -static gpointer -connection_cb_info_obj_list_steal(ConnectionCbInfo *info, gpointer obj) -{ - gssize idx; - - idx = connection_cb_info_obj_list_idx(info, obj); - if (idx >= 0) { - g_ptr_array_remove_index(info->obj_list, idx); - return obj; - } - return NULL; -} - -static void -connection_cb_info_finish(ConnectionCbInfo *info, gpointer obj) -{ - if (obj) { - obj = connection_cb_info_obj_list_steal(info, obj); - if (obj) - connection_cb_info_obj_list_destroy(info, obj); - } else { - while (info->obj_list->len > 0) { - obj = info->obj_list->pdata[info->obj_list->len - 1]; - g_ptr_array_remove_index(info->obj_list, info->obj_list->len - 1); - connection_cb_info_obj_list_destroy(info, obj); - } - } - - if (info->obj_list->len > 0) - return; - - nm_clear_g_source(&info->timeout_id); - nm_clear_g_cancellable(&info->cancellable); - g_ptr_array_free(info->obj_list, TRUE); - - g_signal_handlers_disconnect_by_func(info->nmc->client, connection_removed_cb, info); - - g_slice_free(ConnectionCbInfo, info); - - quit(); -} - -/*****************************************************************************/ - -static void -connection_removed_cb(NMClient *client, NMConnection *connection, ConnectionCbInfo *info) -{ - if (!connection_cb_info_obj_list_has(info, connection)) - return; - g_print(_("Connection '%s' (%s) successfully deleted.\n"), - nm_connection_get_id(connection), - nm_connection_get_uuid(connection)); - connection_cb_info_finish(info, connection); -} - -static void -down_active_connection_state_cb(NMActiveConnection *active, - GParamSpec * pspec, - ConnectionCbInfo * info) -{ - if (nm_active_connection_get_state(active) < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) - return; - - if (info->nmc->nmc_config.print_output == NMC_PRINT_PRETTY) - nmc_terminal_erase_line(); - g_print(_("Connection '%s' successfully deactivated (D-Bus active path: %s)\n"), - nm_active_connection_get_id(active), - nm_object_get_path(NM_OBJECT(active))); - - g_signal_handlers_disconnect_by_func(G_OBJECT(active), down_active_connection_state_cb, info); - connection_cb_info_finish(info, active); -} - -static gboolean -connection_op_timeout_cb(gpointer user_data) -{ - ConnectionCbInfo *info = user_data; - - set_nmc_error_timeout(info->nmc); - connection_cb_info_finish(info, NULL); - return G_SOURCE_REMOVE; -} - -static void -do_connection_down(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - NMActiveConnection *active; - ConnectionCbInfo * info = NULL; - const GPtrArray * active_cons; - gs_strfreev char ** arg_arr = NULL; - const char *const * arg_ptr; - int arg_num; - guint i; - gs_unref_ptrarray GPtrArray *found_active_cons = NULL; - - if (nmc->timeout == -1) - nmc->timeout = 10; - - next_arg(nmc, &argc, &argv, NULL); - arg_ptr = argv; - arg_num = argc; - - if (argc == 0) { - /* nmc_do_cmd() should not call this with argc=0. */ - g_assert(!nmc->complete); - - if (nmc->ask) { - gs_free char *line = NULL; - - line = nmc_readline(&nmc->nmc_config, PROMPT_ACTIVE_CONNECTIONS); - nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num); - arg_ptr = (const char *const *) arg_arr; - } - if (arg_num == 0) { - g_string_printf(nmc->return_text, _("Error: No connection specified.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - } - - /* Get active connections */ - active_cons = nm_client_get_active_connections(nmc->client); - while (arg_num > 0) { - const char *selector = NULL; - - if (arg_num == 1 && nmc->complete) - nmc_complete_strings(*arg_ptr, "id", "uuid", "path", "filename", "apath"); - - if (NM_IN_STRSET(*arg_ptr, "id", "uuid", "path", "filename", "apath")) { - selector = *arg_ptr; - arg_num--; - arg_ptr++; - if (!arg_num) { - g_string_printf(nmc->return_text, _("Error: %s argument is missing."), selector); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - } - - active = nmc_find_active_connection(active_cons, - selector, - *arg_ptr, - &found_active_cons, - arg_num == 1 && nmc->complete); - if (!active) { - if (!nmc->complete) - g_printerr(_("Error: '%s' is not an active connection.\n"), *arg_ptr); - g_string_printf(nmc->return_text, _("Error: not all active connections found.")); - nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; - } - - next_arg(nmc->ask ? NULL : nmc, &arg_num, &arg_ptr, NULL); - } - - if (!found_active_cons) { - g_string_printf(nmc->return_text, _("Error: no active connection provided.")); - nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; - return; - } - nm_assert(found_active_cons->len > 0); - - if (nmc->complete) - return; - - if (nmc->timeout > 0) { - nmc->should_wait++; - - info = g_slice_new0(ConnectionCbInfo); - info->nmc = nmc; - info->obj_list = g_ptr_array_sized_new(found_active_cons->len); - for (i = 0; i < found_active_cons->len; i++) { - active = found_active_cons->pdata[i]; - g_ptr_array_add(info->obj_list, g_object_ref(active)); - g_signal_connect(active, - "notify::" NM_ACTIVE_CONNECTION_STATE, - G_CALLBACK(down_active_connection_state_cb), - info); - } - info->timeout_id = g_timeout_add_seconds(nmc->timeout, connection_op_timeout_cb, info); - } - - for (i = 0; i < found_active_cons->len; i++) { - GError *error = NULL; - - active = found_active_cons->pdata[i]; - - if (!nm_client_deactivate_connection(nmc->client, active, NULL, &error)) { - g_print(_("Connection '%s' deactivation failed: %s\n"), - nm_active_connection_get_id(active), - error->message); - g_clear_error(&error); - - if (info) { - g_signal_handlers_disconnect_by_func(active, down_active_connection_state_cb, info); - connection_cb_info_finish(info, active); - } - } - } -} - -/*****************************************************************************/ - -/* - * Return the most appropriate name for the connection of a type 'name' possibly with given 'slave_type' - * if exists, else return the 'name'. The returned string must not be freed. - */ -static const char * -get_name_alias_toplevel(const char *name, const char *slave_type) -{ - const NMMetaSettingInfoEditor *setting_info; - - if (slave_type) { - const char *slave_name; - - if (nm_meta_setting_info_valid_parts_for_slave_type(slave_type, &slave_name)) - return slave_name ?: name; - return name; - } - - setting_info = nm_meta_setting_info_editor_find_by_name(name, FALSE); - if (setting_info) - return setting_info->alias ?: setting_info->general->setting_name; - - return name; -} - -/* - * Construct a string with names and aliases from the arrays formatted as: - * "name (alias), name, name (alias), name, name" - * - * Returns: string; the caller is responsible for freeing it. - */ -static char * -get_valid_options_string(const NMMetaSettingValidPartItem *const *array, - const NMMetaSettingValidPartItem *const *array_slv) -{ - const NMMetaSettingValidPartItem *const *iter = array; - GString * str; - int i; - - str = g_string_sized_new(150); - - for (i = 0; i < 2; i++, iter = array_slv) { - for (; iter && *iter; iter++) { - const NMMetaSettingInfoEditor *setting_info = (*iter)->setting_info; - - if (str->len) - g_string_append(str, ", "); - if (setting_info->alias) - g_string_append_printf(str, - "%s (%s)", - setting_info->general->setting_name, - setting_info->alias); - else - g_string_append(str, setting_info->general->setting_name); - } - } - return g_string_free(str, FALSE); -} - -static char * -get_valid_options_string_toplevel(void) -{ - GString *str; - int i; - - str = g_string_sized_new(150); - for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { - const NMMetaSettingInfoEditor *setting_info = &nm_meta_setting_infos_editor[i]; - - if (!setting_info->valid_parts) - continue; - - if (str->len) - g_string_append(str, ", "); - if (setting_info->alias) - g_string_append_printf(str, - "%s (%s)", - setting_info->general->setting_name, - setting_info->alias); - else - g_string_append(str, setting_info->general->setting_name); - } - - if (str->len) - g_string_append(str, ", "); - g_string_append(str, "bond-slave, bridge-slave, team-slave"); - - return g_string_free(str, FALSE); -} - -static const NMMetaSettingValidPartItem *const * -get_valid_settings_array(const char *con_type) -{ - const NMMetaSettingInfoEditor *setting_info; - - /* No connection type yet? Return settings for a generic connection - * (just the "connection" setting), which always makes sense. */ - if (!con_type) - return nm_meta_setting_info_valid_parts_default; - - setting_info = nm_meta_setting_info_editor_find_by_name(con_type, FALSE); - if (setting_info) - return setting_info->valid_parts ?: NM_PTRARRAY_EMPTY(const NMMetaSettingValidPartItem *); - return NULL; -} - -static char * -_construct_property_name(const char * setting_name, - const char * property_name, - NMMetaAccessorModifier modifier) -{ - return g_strdup_printf("%s%s.%s\n", - (modifier == NM_META_ACCESSOR_MODIFIER_ADD - ? "+" - : (modifier == NM_META_ACCESSOR_MODIFIER_DEL ? "-" : "")), - setting_name, - property_name); -} - -/* get_valid_properties_string: - * @array: base properties for the current connection type - * @array_slv: slave properties (or ipv4/ipv6 ones) for the current connection type - * @modifier: to prepend to each element of the returned list - * @prefix: only properties matching the prefix will be returned - * @postfix: required prefix on the property args; if a empty string is passed, is - * assumed that the @prefix is a shortcut, so it should not be completed - * but left as is (and an additional check for shortcut ambiguity is performed) - * - * Returns a list of properties compatible with the current connection type - * for the shell autocompletion functionality. - * - * Returns: list of property.arg elements - */ -static char * -get_valid_properties_string(const NMMetaSettingValidPartItem *const *array, - const NMMetaSettingValidPartItem *const *array_slv, - NMMetaAccessorModifier modifier, - const char * prefix, - const char * postfix) -{ - const NMMetaSettingValidPartItem *const *iter = array; - const char * prop_name = NULL; - GString * str; - guint i, j; - gboolean full_match = FALSE; - - g_return_val_if_fail(prefix, NULL); - - str = g_string_sized_new(1024); - - for (i = 0; i < 2; i++, iter = array_slv) { - for (; !full_match && iter && *iter; iter++) { - const NMMetaSettingInfoEditor *setting_info = (*iter)->setting_info; - - if (!(g_str_has_prefix(setting_info->general->setting_name, prefix)) - && (!setting_info->alias || !g_str_has_prefix(setting_info->alias, prefix))) { - continue; - } - - /* If postix (so prefix is terminated by a dot), check - * that prefix is not ambiguous */ - if (postfix) { - /* If we have a perfect match, no need to look for others - * prefix and no check on ambiguity should be performed. - * Moreover, erase previous matches from output string */ - if (nm_streq(prefix, setting_info->general->setting_name) - || nm_streq0(prefix, setting_info->alias)) { - g_string_erase(str, 0, -1); - full_match = TRUE; - } else if (prop_name) - return g_string_free(str, TRUE); - prop_name = prefix; - } else - prop_name = setting_info->general->setting_name; - - /* Search the array with the arguments of the current property */ - for (j = 0; j < setting_info->properties_num; j++) { - gs_free char *ss1 = NULL; - const char * arg_name; - - arg_name = setting_info->properties[j]->property_name; - - /* If required, expand the alias too */ - if (!postfix && setting_info->alias) { - gs_free char *ss2 = NULL; - - ss2 = _construct_property_name(setting_info->alias, arg_name, modifier); - g_string_append(str, ss2); - } - - if (postfix && !g_str_has_prefix(arg_name, postfix)) - continue; - - ss1 = _construct_property_name(prop_name, arg_name, modifier); - g_string_append(str, ss1); - } - } - } - return g_string_free(str, FALSE); -} - -/* - * Check if 'val' is valid string in either array->name or array->alias for - * both array parameters (array & array_slv). - * It accepts shorter string provided they are not ambiguous. - * 'val' == NULL doesn't hurt. - * - * Returns: pointer to array->name string or NULL on failure. - * The returned string must not be freed. - */ -static const char * -check_valid_name(const char * val, - const NMMetaSettingValidPartItem *const *array, - const NMMetaSettingValidPartItem *const *array_slv, - GError ** error) -{ - const NMMetaSettingValidPartItem *const *iter; - gs_unref_ptrarray GPtrArray *tmp_arr = NULL; - const char * str; - GError * tmp_err = NULL; - int i; - - g_return_val_if_fail(array, NULL); - - /* Create a temporary array that can be used in nmc_string_is_valid() */ - tmp_arr = g_ptr_array_sized_new(32); - iter = array; - for (i = 0; i < 2; i++, iter = array_slv) { - for (; iter && *iter; iter++) { - const NMMetaSettingInfoEditor *setting_info = (*iter)->setting_info; - - g_ptr_array_add(tmp_arr, (gpointer) setting_info->general->setting_name); - if (setting_info->alias) - g_ptr_array_add(tmp_arr, (gpointer) setting_info->alias); - } - } - g_ptr_array_add(tmp_arr, (gpointer) NULL); - - /* Check string validity */ - str = nmc_string_is_valid(val, (const char **) tmp_arr->pdata, &tmp_err); - if (!str) { - if (tmp_err->code == 1) - g_propagate_error(error, tmp_err); - else { - /* We want to handle aliases, so construct own error message */ - gs_free char *err_str = NULL; - - err_str = get_valid_options_string(array, array_slv); - g_set_error(error, 1, 0, _("'%s' not among [%s]"), val, err_str); - g_clear_error(&tmp_err); - } - return NULL; - } - - /* Return a pointer to the found string in passed 'array' */ - iter = array; - for (i = 0; i < 2; i++, iter = array_slv) { - for (; iter && *iter; iter++) { - const NMMetaSettingInfoEditor *setting_info = (*iter)->setting_info; - - if (nm_streq(setting_info->general->setting_name, str) - || nm_streq0(setting_info->alias, str)) { - return setting_info->general->setting_name; - } - } - } - - /* We should not really come here */ - g_set_error(error, 1, 0, _("Unknown error")); - return NULL; -} - -static const char * -check_valid_name_toplevel(const char *val, const char **slave_type, GError **error) -{ - gs_unref_ptrarray GPtrArray * tmp_arr = NULL; - const NMMetaSettingInfoEditor *setting_info; - gs_free_error GError *tmp_err = NULL; - const char * str; - int i; - - NM_SET_OUT(slave_type, NULL); - - /* Create a temporary array that can be used in nmc_string_is_valid() */ - tmp_arr = g_ptr_array_sized_new(32); - for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { - setting_info = &nm_meta_setting_infos_editor[i]; - g_ptr_array_add(tmp_arr, (gpointer) setting_info->general->setting_name); - if (setting_info->alias) - g_ptr_array_add(tmp_arr, (gpointer) setting_info->alias); - } - g_ptr_array_add(tmp_arr, "bond-slave"); - g_ptr_array_add(tmp_arr, "bridge-slave"); - g_ptr_array_add(tmp_arr, "team-slave"); - g_ptr_array_add(tmp_arr, (gpointer) NULL); - - /* Check string validity */ - str = nmc_string_is_valid(val, (const char **) tmp_arr->pdata, &tmp_err); - if (!str) { - if (tmp_err->code == 1) - g_propagate_error(error, g_steal_pointer(&tmp_err)); - else { - /* We want to handle aliases, so construct own error message */ - gs_free char *err_str = NULL; - - err_str = get_valid_options_string_toplevel(); - g_set_error(error, 1, 0, _("'%s' not among [%s]"), val, err_str); - } - return NULL; - } - - if (nm_streq(str, "bond-slave")) { - NM_SET_OUT(slave_type, NM_SETTING_BOND_SETTING_NAME); - return NM_SETTING_WIRED_SETTING_NAME; - } else if (nm_streq(str, "bridge-slave")) { - NM_SET_OUT(slave_type, NM_SETTING_BRIDGE_SETTING_NAME); - return NM_SETTING_WIRED_SETTING_NAME; - } else if (nm_streq(str, "team-slave")) { - NM_SET_OUT(slave_type, NM_SETTING_TEAM_SETTING_NAME); - return NM_SETTING_WIRED_SETTING_NAME; - } - - setting_info = nm_meta_setting_info_editor_find_by_name(str, TRUE); - if (setting_info) - return setting_info->general->setting_name; - - /* We should not really come here */ - g_set_error(error, 1, 0, _("Unknown error")); - return NULL; -} - -static gboolean -is_setting_mandatory(NMConnection *connection, NMSetting *setting) -{ - NMSettingConnection * s_con; - const char * c_type; - const NMMetaSettingValidPartItem *const *item; - const char * name; - const char * s_type; - guint i; - - s_con = nm_connection_get_setting_connection(connection); - g_assert(s_con); - c_type = nm_setting_connection_get_connection_type(s_con); - s_type = nm_setting_connection_get_slave_type(s_con); - - name = nm_setting_get_name(setting); - - for (i = 0; i < 2; i++) { - if (i == 0) - item = get_valid_settings_array(c_type); - else - item = nm_meta_setting_info_valid_parts_for_slave_type(s_type, NULL); - for (; item && *item; item++) { - if (!strcmp(name, (*item)->setting_info->general->setting_name)) - return (*item)->mandatory; - } - } - - return FALSE; -} - -/*****************************************************************************/ - -static const char * -_strip_master_prefix(const char *master, const char *(**func)(NMConnection *) ) -{ - if (!master) - return NULL; - - if (g_str_has_prefix(master, "ifname/")) { - master = master + strlen("ifname/"); - if (func) - *func = nm_connection_get_interface_name; - } else if (g_str_has_prefix(master, "uuid/")) { - master = master + strlen("uuid/"); - if (func) - *func = nm_connection_get_uuid; - } else if (g_str_has_prefix(master, "id/")) { - master = master + strlen("id/"); - if (func) - *func = nm_connection_get_id; - } - return master; -} - -/* normalized_master_for_slave: - * @connections: list af all connections - * @master: UUID, ifname or ID of the master connection - * @type: virtual connection type (bond, team, bridge, ...) or %NULL - * @out_type: type of the connection that matched - * - * Check whether master is a valid interface name, UUID or ID of some connection, - * possibly of a specified @type. - * First UUID and ifname are checked. If they don't match, ID is checked - * and replaced by UUID on a match. - * - * Returns: identifier of master connection if found, %NULL otherwise - */ -static const char * -normalized_master_for_slave(const GPtrArray *connections, - const char * master, - const char * type, - const char ** out_type) -{ - NMConnection * connection; - NMSettingConnection *s_con; - const char * con_type = NULL, *id, *uuid, *ifname; - guint i; - const char * found_by_id = NULL; - const char * out_type_by_id = NULL; - const char * out_master = NULL; - const char *(*func)(NMConnection *) = NULL; - - if (!master) - return NULL; - - master = _strip_master_prefix(master, &func); - for (i = 0; i < connections->len; i++) { - connection = NM_CONNECTION(connections->pdata[i]); - s_con = nm_connection_get_setting_connection(connection); - g_assert(s_con); - con_type = nm_setting_connection_get_connection_type(s_con); - if (type && g_strcmp0(con_type, type) != 0) - continue; - if (func) { - /* There was a prefix; only compare to that type. */ - if (g_strcmp0(master, func(connection)) == 0) { - if (out_type) - *out_type = con_type; - if (func == nm_connection_get_id) - out_master = nm_connection_get_uuid(connection); - else - out_master = master; - break; - } - } else { - id = nm_connection_get_id(connection); - uuid = nm_connection_get_uuid(connection); - ifname = nm_connection_get_interface_name(connection); - if (g_strcmp0(master, uuid) == 0 || g_strcmp0(master, ifname) == 0) { - out_master = master; - if (out_type) - *out_type = con_type; - break; - } - if (!found_by_id && g_strcmp0(master, id) == 0) { - out_type_by_id = con_type; - found_by_id = uuid; - } - } - } - - if (!out_master) { - out_master = found_by_id; - if (out_type) - *out_type = out_type_by_id; - } - - if (!out_master) { - g_print(_("Warning: master='%s' doesn't refer to any existing profile.\n"), master); - out_master = master; - if (out_type) - *out_type = type; - } - - return out_master; -} - -#define WORD_YES "yes" -#define WORD_NO "no" -static const char * -prompt_yes_no(gboolean default_yes, char *delim) -{ - static char prompt[128] = {0}; - - if (!delim) - delim = ""; - - snprintf(prompt, - sizeof(prompt), - "(%s/%s) [%s]%s ", - WORD_YES, - WORD_NO, - default_yes ? WORD_YES : WORD_NO, - delim); - - return prompt; -} - -static NMSetting * -is_setting_valid(NMConnection * connection, - const NMMetaSettingValidPartItem *const *valid_settings_main, - const NMMetaSettingValidPartItem *const *valid_settings_slave, - const char * setting) -{ - const char *setting_name; - - if (!(setting_name = - check_valid_name(setting, valid_settings_main, valid_settings_slave, NULL))) - return NULL; - return nm_connection_get_setting_by_name(connection, setting_name); -} - -static char * -is_property_valid(NMSetting *setting, const char *property, GError **error) -{ - gs_strfreev char **valid_props = NULL; - const char * prop_name; - - valid_props = nmc_setting_get_valid_properties(setting); - prop_name = nmc_string_is_valid(property, (const char **) valid_props, error); - return g_strdup(prop_name); -} - -static char * -unique_master_iface_ifname(const GPtrArray *connections, const char *try_name) -{ - char *new_name; - guint num = 0; - guint i; - - new_name = g_strdup(try_name); - -again: - for (i = 0; i < connections->len; i++) { - NMConnection *connection = connections->pdata[i]; - - if (nm_streq0(new_name, nm_connection_get_interface_name(connection))) { - num++; - g_free(new_name); - new_name = g_strdup_printf("%s%u", try_name, num); - goto again; - } - } - return new_name; -} - -static void -set_default_interface_name(NmCli *nmc, NMSettingConnection *s_con) -{ - const char *default_name; - const char *con_type; - - if (nm_setting_connection_get_interface_name(s_con)) - return; - - con_type = nm_setting_connection_get_connection_type(s_con); - - /* Set a sensible bond/team/bridge interface name by default */ - if (nm_streq0(con_type, NM_SETTING_BOND_SETTING_NAME)) - default_name = "nm-bond"; - else if (nm_streq0(con_type, NM_SETTING_TEAM_SETTING_NAME)) - default_name = "nm-team"; - else if (nm_streq0(con_type, NM_SETTING_BRIDGE_SETTING_NAME)) - default_name = "nm-bridge"; - else - default_name = NULL; - - if (default_name) { - const GPtrArray *connections; - gs_free char * ifname = NULL; - - connections = nm_client_get_connections(nmc->client); - ifname = unique_master_iface_ifname(connections, default_name); - g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL); - } -} - -/*****************************************************************************/ - -static PropertyInfFlags -_dynamic_options_set(const NMMetaAbstractInfo *abstract_info, - PropertyInfFlags mask, - PropertyInfFlags set) -{ - static GHashTable *cache = NULL; - gpointer p; - PropertyInfFlags v, v2; - - if (G_UNLIKELY(!cache)) - cache = g_hash_table_new(nm_direct_hash, NULL); - - if (g_hash_table_lookup_extended(cache, (gpointer) abstract_info, NULL, &p)) - v = GPOINTER_TO_UINT(p); - else - v = 0; - - v2 = (v & ~mask) | (mask & set); - if (v != v2) - g_hash_table_insert(cache, (gpointer) abstract_info, GUINT_TO_POINTER(v2)); - - return v2; -} - -static PropertyInfFlags -_dynamic_options_get(const NMMetaAbstractInfo *abstract_info) -{ - return _dynamic_options_set(abstract_info, 0, 0); -} - -/*****************************************************************************/ - -static gboolean -_meta_property_needs_bond_hack(const NMMetaPropertyInfo *property_info) -{ - /* hack: the bond property data is handled special and not generically. - * Eventually, get rid of explicitly checking whether we handle a bond. */ - if (!property_info) - g_return_val_if_reached(FALSE); - return property_info->property_typ_data - && property_info->property_typ_data->nested == &nm_meta_property_typ_data_bond; -} - -static char ** -_meta_abstract_complete(const NMMetaAbstractInfo *abstract_info, const char *text) -{ - const char *const * values; - char ** values_to_free = NULL; - const NMMetaOperationContext ctx = { - .connection = nmc_tab_completion.connection, - }; - - values = nm_meta_abstract_info_complete(abstract_info, - nmc_meta_environment, - (gpointer) nmc_meta_environment_arg, - &ctx, - text, - NULL, - &values_to_free); - if (values) - return values_to_free ?: g_strdupv((char **) values); - return NULL; -} - -static char * -_meta_abstract_generator(const char *text, int state) -{ - if (nmc_tab_completion.words) { - return nmc_rl_gen_func_basic(text, state, (const char *const *) nmc_tab_completion.words); - } - - return NULL; -} - -static void -_meta_abstract_get(const NMMetaAbstractInfo * abstract_info, - const NMMetaSettingInfoEditor **out_setting_info, - const char ** out_setting_name, - const char ** out_property_name, - const char ** out_option, - NMMetaPropertyInfFlags * out_inf_flags, - const char ** out_prompt, - const char ** out_def_hint) -{ - const NMMetaPropertyInfo *info = (const NMMetaPropertyInfo *) abstract_info; - - NM_SET_OUT(out_option, info->property_alias); - NM_SET_OUT(out_setting_info, info->setting_info); - NM_SET_OUT(out_setting_name, info->setting_info->general->setting_name); - NM_SET_OUT(out_property_name, info->property_name); - NM_SET_OUT(out_option, info->property_alias); - NM_SET_OUT(out_inf_flags, info->inf_flags); - NM_SET_OUT(out_prompt, info->prompt); - NM_SET_OUT(out_def_hint, info->def_hint); -} - -static const OptionInfo *_meta_abstract_get_option_info(const NMMetaAbstractInfo *abstract_info); - -/* - * Mark options in option_info as relevant. - * The questionnaire (for --ask) will ask for them. - */ -static void -enable_options(const char *setting_name, const char *property, const char *const *opts) -{ - const NMMetaPropertyInfo *property_info; - - property_info = nm_meta_property_info_find_by_name(setting_name, property); - - if (!property_info) - g_return_if_reached(); - - if (_meta_property_needs_bond_hack(property_info)) { - guint i; - - for (i = 0; i < nm_meta_property_typ_data_bond.nested_len; i++) { - const NMMetaNestedPropertyInfo *bi = &nm_meta_property_typ_data_bond.nested[i]; - - if (bi->base.inf_flags & NM_META_PROPERTY_INF_FLAG_DONT_ASK && bi->base.property_alias - && g_strv_contains(opts, bi->base.property_alias)) - _dynamic_options_set((const NMMetaAbstractInfo *) bi, - PROPERTY_INF_FLAG_ENABLED, - PROPERTY_INF_FLAG_ENABLED); - } - return; - } - - if (!property_info->is_cli_option) - g_return_if_reached(); - - if (property_info->inf_flags & NM_META_PROPERTY_INF_FLAG_DONT_ASK - && property_info->property_alias && g_strv_contains(opts, property_info->property_alias)) - _dynamic_options_set((const NMMetaAbstractInfo *) property_info, - PROPERTY_INF_FLAG_ENABLED, - PROPERTY_INF_FLAG_ENABLED); -} - -/* - * Mark options in option_info as irrelevant (because we learned they make no sense - * or they have been set via different means). - * The questionnaire (for --ask) will not ask for them. - */ -static void -disable_options(const char *setting_name, const char *property) -{ - const NMMetaPropertyInfo * property_infos_local[2]; - const NMMetaPropertyInfo *const *property_infos; - guint p; - - if (property) { - const NMMetaPropertyInfo *pi; - - pi = nm_meta_property_info_find_by_name(setting_name, property); - if (!pi) - g_return_if_reached(); - if (!_meta_property_needs_bond_hack(pi) && !pi->is_cli_option) - return; - property_infos_local[0] = pi; - property_infos_local[1] = NULL; - property_infos = property_infos_local; - } else { - const NMMetaSettingInfoEditor *setting_info; - - setting_info = nm_meta_setting_info_editor_find_by_name(setting_name, FALSE); - if (!setting_info) - g_return_if_reached(); - property_infos = setting_info->properties; - if (!property_infos) - return; - } - - for (p = 0; property_infos[p]; p++) { - const NMMetaPropertyInfo *property_info = property_infos[p]; - - if (_meta_property_needs_bond_hack(property_info)) { - guint i; - - for (i = 0; i < nm_meta_property_typ_data_bond.nested_len; i++) { - const NMMetaNestedPropertyInfo *bi = &nm_meta_property_typ_data_bond.nested[i]; - - _dynamic_options_set((const NMMetaAbstractInfo *) bi, - PROPERTY_INF_FLAG_DISABLED, - PROPERTY_INF_FLAG_DISABLED); - } - nm_assert(p == 0 && !property_infos[1]); - } else { - if (property_info->is_cli_option) - _dynamic_options_set((const NMMetaAbstractInfo *) property_info, - PROPERTY_INF_FLAG_DISABLED, - PROPERTY_INF_FLAG_DISABLED); - } - } -} - -/* - * Reset marks done with enable_options() and disable_options(). - * Ensures correct operation in case more than one connection is added in a single - * nmcli session. - */ -static void -reset_options(void) -{ - NMMetaSettingType s; - - for (s = 0; s < _NM_META_SETTING_TYPE_NUM; s++) { - const NMMetaPropertyInfo *const *property_infos; - guint p; - - property_infos = nm_meta_setting_infos_editor[s].properties; - if (!property_infos) - continue; - for (p = 0; property_infos[p]; p++) { - const NMMetaPropertyInfo *property_info = property_infos[p]; - - if (_meta_property_needs_bond_hack(property_info)) { - guint i; - - for (i = 0; i < nm_meta_property_typ_data_bond.nested_len; i++) { - const NMMetaNestedPropertyInfo *bi = &nm_meta_property_typ_data_bond.nested[i]; - - _dynamic_options_set((const NMMetaAbstractInfo *) bi, PROPERTY_INF_FLAG_ALL, 0); - } - } else { - if (property_info->is_cli_option) - _dynamic_options_set((const NMMetaAbstractInfo *) property_info, - PROPERTY_INF_FLAG_ALL, - 0); - } - } - } -} - -static gboolean -set_property(NMClient * client, - NMConnection * connection, - const char * setting_name, - const char * property, - const char * value, - NMMetaAccessorModifier modifier, - GError ** error) -{ - gs_free char *property_name = NULL; - gs_free_error GError *local = NULL; - NMSetting * setting; - - nm_assert(setting_name && setting_name[0]); - nm_assert(NM_IN_SET(modifier, - NM_META_ACCESSOR_MODIFIER_SET, - NM_META_ACCESSOR_MODIFIER_ADD, - NM_META_ACCESSOR_MODIFIER_DEL)); - - setting = nm_connection_get_setting_by_name(connection, setting_name); - if (!setting) { - setting = nm_meta_setting_info_editor_new_setting( - nm_meta_setting_info_editor_find_by_name(setting_name, FALSE), - NM_META_ACCESSOR_SETTING_INIT_TYPE_CLI); - nm_connection_add_setting(connection, setting); - } - - property_name = is_property_valid(setting, property, &local); - if (!property_name) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: invalid property '%s': %s."), - property, - local->message); - return FALSE; - } - - if (!nmc_setting_set_property(client, - setting, - property_name, - ((modifier == NM_META_ACCESSOR_MODIFIER_DEL && !value) - ? NM_META_ACCESSOR_MODIFIER_SET - : modifier), - value, - &local)) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: failed to %s %s.%s: %s."), - (modifier != NM_META_ACCESSOR_MODIFIER_DEL ? "modify" : "remove a value from"), - setting_name, - property, - local->message); - return FALSE; - } - - /* Don't ask for this property in interactive mode. */ - disable_options(setting_name, property_name); - - return TRUE; -} - -static gboolean -set_option(NmCli * nmc, - NMConnection * connection, - const NMMetaAbstractInfo *abstract_info, - const char * value, - GError ** error) -{ - const char * setting_name, *property_name, *option_name; - NMMetaPropertyInfFlags inf_flags; - const OptionInfo * option; - - option = _meta_abstract_get_option_info(abstract_info); - - _dynamic_options_set(abstract_info, PROPERTY_INF_FLAG_DISABLED, PROPERTY_INF_FLAG_DISABLED); - - _meta_abstract_get(abstract_info, - NULL, - &setting_name, - &property_name, - &option_name, - &inf_flags, - NULL, - NULL); - if (option && option->check_and_set) { - return option->check_and_set(nmc, connection, option, value, error); - } else if (value) { - return set_property(nmc->client, - connection, - setting_name, - property_name, - value, - inf_flags & NM_META_PROPERTY_INF_FLAG_MULTI - ? NM_META_ACCESSOR_MODIFIER_ADD - : NM_META_ACCESSOR_MODIFIER_SET, - error); - } else if (inf_flags & NM_META_PROPERTY_INF_FLAG_REQD) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: '%s' is mandatory."), - option_name); - return FALSE; - } - - return TRUE; -} - -/* - * Return relevant NameItem[] tables for given connection (based on connection type - * and slave type. - */ -static gboolean -con_settings(NMConnection * connection, - const NMMetaSettingValidPartItem *const **type_settings, - const NMMetaSettingValidPartItem *const **slv_settings, - GError ** error) -{ - const char * con_type; - NMSettingConnection *s_con; - - g_return_val_if_fail(type_settings, FALSE); - g_return_val_if_fail(slv_settings, FALSE); - - s_con = nm_connection_get_setting_connection(connection); - g_assert(s_con); - - con_type = nm_setting_connection_get_slave_type(s_con); - *slv_settings = nm_meta_setting_info_valid_parts_for_slave_type(con_type, NULL); - if (!*slv_settings) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: invalid slave type; %s."), - con_type); - return FALSE; - } - - con_type = nm_setting_connection_get_connection_type(s_con); - *type_settings = get_valid_settings_array(con_type); - if (!*type_settings) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: invalid connection type; %s."), - con_type); - return FALSE; - } - - return TRUE; -} - -/* - * Make sure all required settings are in place (should be called when - * it's possible that a type is already set). - */ -static void -ensure_settings(NMConnection *connection, const NMMetaSettingValidPartItem *const *item) -{ - NMSetting *setting; - - for (; item && *item; item++) { - if (!(*item)->mandatory) - continue; - if (nm_connection_get_setting_by_name(connection, - (*item)->setting_info->general->setting_name)) - continue; - setting = nm_meta_setting_info_editor_new_setting((*item)->setting_info, - NM_META_ACCESSOR_SETTING_INIT_TYPE_CLI); - nm_connection_add_setting(connection, setting); - } -} - -/*****************************************************************************/ - -static char * -gen_func_bool_values_l10n(const char *text, int state) -{ - const char *words[] = {WORD_YES, WORD_NO, NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -static char * -gen_func_bt_type(const char *text, int state) -{ - const char *words[] = {"panu", "nap", "dun-gsm", "dun-cdma", NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -static char * -gen_func_bond_mode(const char *text, int state) -{ - const char *words[] = {"balance-rr", - "active-backup", - "balance-xor", - "broadcast", - "802.3ad", - "balance-tlb", - "balance-alb", - NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} -static char * -gen_func_bond_mon_mode(const char *text, int state) -{ - const char *words[] = {"miimon", "arp", NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} -static char * -gen_func_bond_lacp_rate(const char *text, int state) -{ - const char *words[] = {"slow", "fast", NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -/*****************************************************************************/ - -static gboolean -set_connection_type(NmCli * nmc, - NMConnection * con, - const OptionInfo *option, - const char * value, - GError ** error) -{ - const NMMetaSettingValidPartItem *const *type_settings; - const NMMetaSettingValidPartItem *const *slv_settings; - GError * local = NULL; - const char * master[] = {"master", NULL}; - const char * slave_type = NULL; - - value = check_valid_name_toplevel(value, &slave_type, &local); - if (!value) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: bad connection type: %s"), - local->message); - g_clear_error(&local); - return FALSE; - } - - if (slave_type) { - if (!set_property(nmc->client, - con, - NM_SETTING_CONNECTION_SETTING_NAME, - NM_SETTING_CONNECTION_SLAVE_TYPE, - slave_type, - NM_META_ACCESSOR_MODIFIER_SET, - error)) { - return FALSE; - } - enable_options(NM_SETTING_CONNECTION_SETTING_NAME, NM_SETTING_CONNECTION_MASTER, master); - } - - /* ifname is mandatory for all connection types except virtual ones (bond, team, bridge, vlan) */ - if ((strcmp(value, NM_SETTING_BOND_SETTING_NAME) == 0) - || (strcmp(value, NM_SETTING_TEAM_SETTING_NAME) == 0) - || (strcmp(value, NM_SETTING_BRIDGE_SETTING_NAME) == 0) - || (strcmp(value, NM_SETTING_VLAN_SETTING_NAME) == 0)) { - disable_options(NM_SETTING_CONNECTION_SETTING_NAME, NM_SETTING_CONNECTION_INTERFACE_NAME); - } - - if (!set_property(nmc->client, - con, - option->setting_info->general->setting_name, - option->property, - value, - NM_META_ACCESSOR_MODIFIER_SET, - error)) - return FALSE; - - if (!con_settings(con, &type_settings, &slv_settings, error)) - return FALSE; - - ensure_settings(con, slv_settings); - ensure_settings(con, type_settings); - - return TRUE; -} - -static gboolean -set_connection_iface(NmCli * nmc, - NMConnection * con, - const OptionInfo *option, - const char * value, - GError ** error) -{ - if (value) { - /* Special value of '*' means no specific interface name */ - if (strcmp(value, "*") == 0) - value = NULL; - } - - return set_property(nmc->client, - con, - option->setting_info->general->setting_name, - option->property, - value, - NM_META_ACCESSOR_MODIFIER_SET, - error); -} - -static gboolean -set_connection_master(NmCli * nmc, - NMConnection * con, - const OptionInfo *option, - const char * value, - GError ** error) -{ - const GPtrArray * connections; - NMSettingConnection *s_con; - const char * slave_type; - - s_con = nm_connection_get_setting_connection(con); - g_return_val_if_fail(s_con, FALSE); - - if (!value) { - g_set_error_literal(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: master is required")); - return FALSE; - } - - slave_type = nm_setting_connection_get_slave_type(s_con); - connections = nm_client_get_connections(nmc->client); - value = normalized_master_for_slave(connections, value, slave_type, &slave_type); - - if (!set_property(nmc->client, - con, - NM_SETTING_CONNECTION_SETTING_NAME, - NM_SETTING_CONNECTION_SLAVE_TYPE, - slave_type, - NM_META_ACCESSOR_MODIFIER_SET, - error)) { - return FALSE; - } - - return set_property(nmc->client, - con, - option->setting_info->general->setting_name, - option->property, - value, - NM_META_ACCESSOR_MODIFIER_SET, - error); -} - -static gboolean -set_bond_option(NmCli * nmc, - NMConnection * con, - const OptionInfo *option, - const char * value, - GError ** error) -{ - NMSettingBond *s_bond; - gboolean success; - gs_free char * name = NULL; - char * p; - - s_bond = nm_connection_get_setting_bond(con); - g_return_val_if_fail(s_bond, FALSE); - - name = g_strdup(option->option); - for (p = name; p[0]; p++) { - if (p[0] == '-') - p[0] = '_'; - } - - if (nm_str_is_empty(value)) { - nm_setting_bond_remove_option(s_bond, name); - success = TRUE; - } else - success = _nm_meta_setting_bond_add_option(NM_SETTING(s_bond), name, value, error); - - if (!success) - return FALSE; - - if (success) { - if (nm_streq(name, NM_SETTING_BOND_OPTION_MODE)) { - value = nmc_bond_validate_mode(value, error); - if (nm_streq(value, "active-backup")) { - enable_options(NM_SETTING_BOND_SETTING_NAME, - NM_SETTING_BOND_OPTIONS, - NM_MAKE_STRV("primary")); - } - } - } - - return success; -} - -static gboolean -set_bond_monitoring_mode(NmCli * nmc, - NMConnection * con, - const OptionInfo *option, - const char * value, - GError ** error) -{ - NMSettingBond *s_bond; - gs_free char * monitor_mode = NULL; - const char * miimon_opts[] = {"miimon", "downdelay", "updelay", NULL}; - const char * arp_opts[] = {"arp-interval", "arp-ip-target", NULL}; - - s_bond = nm_connection_get_setting_bond(con); - g_return_val_if_fail(s_bond, FALSE); - - if (value) { - monitor_mode = g_strdup(value); - g_strstrip(monitor_mode); - } else { - monitor_mode = g_strdup(NM_META_TEXT_WORD_MIIMON); - } - - if (matches(monitor_mode, NM_META_TEXT_WORD_MIIMON)) - enable_options(NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS, miimon_opts); - else if (matches(monitor_mode, NM_META_TEXT_WORD_ARP)) - enable_options(NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS, arp_opts); - else { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: '%s' is not a valid monitoring mode; use '%s' or '%s'.\n"), - monitor_mode, - NM_META_TEXT_WORD_MIIMON, - NM_META_TEXT_WORD_ARP); - return FALSE; - } - - return TRUE; -} - -static gboolean -set_bluetooth_type(NmCli * nmc, - NMConnection * con, - const OptionInfo *option, - const char * value, - GError ** error) -{ - NMSetting *setting; - - if (!value) - return TRUE; - - /* 'dun' type requires adding 'gsm' or 'cdma' setting */ - if (!strcmp(value, NM_SETTING_BLUETOOTH_TYPE_DUN) - || !strcmp(value, NM_SETTING_BLUETOOTH_TYPE_DUN "-gsm")) { - value = NM_SETTING_BLUETOOTH_TYPE_DUN; - setting = nm_meta_setting_info_editor_new_setting( - &nm_meta_setting_infos_editor[NM_META_SETTING_TYPE_GSM], - NM_META_ACCESSOR_SETTING_INIT_TYPE_CLI); - nm_connection_add_setting(con, setting); - } else if (!strcmp(value, NM_SETTING_BLUETOOTH_TYPE_DUN "-cdma")) { - value = NM_SETTING_BLUETOOTH_TYPE_DUN; - setting = nm_setting_cdma_new(); - nm_connection_add_setting(con, setting); - } else if (!strcmp(value, NM_SETTING_BLUETOOTH_TYPE_PANU) - || !strcmp(value, NM_SETTING_BLUETOOTH_TYPE_NAP)) { - /* no op */ - } else { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: 'bt-type': '%s' not valid; use [%s, %s, %s (%s), %s]."), - value, - NM_SETTING_BLUETOOTH_TYPE_PANU, - NM_SETTING_BLUETOOTH_TYPE_NAP, - NM_SETTING_BLUETOOTH_TYPE_DUN, - NM_SETTING_BLUETOOTH_TYPE_DUN "-gsm", - NM_SETTING_BLUETOOTH_TYPE_DUN "-cdma"); - return FALSE; - } - - return set_property(nmc->client, - con, - option->setting_info->general->setting_name, - option->property, - value, - NM_META_ACCESSOR_MODIFIER_SET, - error); -} - -static gboolean -set_ip4_address(NmCli * nmc, - NMConnection * con, - const OptionInfo *option, - const char * value, - GError ** error) -{ - NMSettingIPConfig *s_ip4; - - if (!value) - return TRUE; - - s_ip4 = nm_connection_get_setting_ip4_config(con); - if (!s_ip4) { - s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); - nm_connection_add_setting(con, NM_SETTING(s_ip4)); - g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL); - } - return set_property(nmc->client, - con, - option->setting_info->general->setting_name, - option->property, - value, - NM_META_ACCESSOR_MODIFIER_ADD, - error); -} - -static gboolean -set_ip6_address(NmCli * nmc, - NMConnection * con, - const OptionInfo *option, - const char * value, - GError ** error) -{ - NMSettingIPConfig *s_ip6; - - if (!value) - return TRUE; - - s_ip6 = nm_connection_get_setting_ip6_config(con); - if (!s_ip6) { - s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); - nm_connection_add_setting(con, NM_SETTING(s_ip6)); - g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL); - } - return set_property(nmc->client, - con, - option->setting_info->general->setting_name, - option->property, - value, - NM_META_ACCESSOR_MODIFIER_ADD, - error); -} - -/*****************************************************************************/ - -static const OptionInfo * -_meta_abstract_get_option_info(const NMMetaAbstractInfo *abstract_info) -{ - static const OptionInfo option_info[] = { -#define OPTION_INFO(name, property_name_, property_alias_, check_and_set_, generator_func_) \ - { \ - .setting_info = &nm_meta_setting_infos_editor[NM_META_SETTING_TYPE_##name], \ - .property = property_name_, \ - .option = property_alias_, \ - .check_and_set = check_and_set_, \ - .generator_func = generator_func_, \ - } - OPTION_INFO(CONNECTION, NM_SETTING_CONNECTION_TYPE, "type", set_connection_type, NULL), - OPTION_INFO(CONNECTION, - NM_SETTING_CONNECTION_INTERFACE_NAME, - "ifname", - set_connection_iface, - NULL), - OPTION_INFO(CONNECTION, - NM_SETTING_CONNECTION_MASTER, - "master", - set_connection_master, - NULL), - OPTION_INFO(BLUETOOTH, - NM_SETTING_BLUETOOTH_TYPE, - "bt-type", - set_bluetooth_type, - gen_func_bt_type), - OPTION_INFO(BOND, NM_SETTING_BOND_OPTIONS, "mode", set_bond_option, gen_func_bond_mode), - OPTION_INFO(BOND, - NM_SETTING_BOND_OPTIONS, - "primary", - set_bond_option, - nmc_rl_gen_func_ifnames), - OPTION_INFO(BOND, - NM_SETTING_BOND_OPTIONS, - NULL, - set_bond_monitoring_mode, - gen_func_bond_mon_mode), - OPTION_INFO(BOND, NM_SETTING_BOND_OPTIONS, "miimon", set_bond_option, NULL), - OPTION_INFO(BOND, NM_SETTING_BOND_OPTIONS, "downdelay", set_bond_option, NULL), - OPTION_INFO(BOND, NM_SETTING_BOND_OPTIONS, "updelay", set_bond_option, NULL), - OPTION_INFO(BOND, NM_SETTING_BOND_OPTIONS, "arp-interval", set_bond_option, NULL), - OPTION_INFO(BOND, NM_SETTING_BOND_OPTIONS, "arp-ip-target", set_bond_option, NULL), - OPTION_INFO(BOND, - NM_SETTING_BOND_OPTIONS, - "lacp-rate", - set_bond_option, - gen_func_bond_lacp_rate), - OPTION_INFO(IP4_CONFIG, NM_SETTING_IP_CONFIG_ADDRESSES, "ip4", set_ip4_address, NULL), - OPTION_INFO(IP6_CONFIG, NM_SETTING_IP_CONFIG_ADDRESSES, "ip6", set_ip6_address, NULL), - {0}, - }; - const char * property_name, *option; - const NMMetaSettingInfoEditor *setting_info; - const OptionInfo * candidate; - - _meta_abstract_get(abstract_info, - &setting_info, - NULL, - &property_name, - &option, - NULL, - NULL, - NULL); - - for (candidate = option_info; candidate->setting_info; candidate++) { - if (candidate->setting_info == setting_info && nm_streq0(candidate->property, property_name) - && nm_streq0(candidate->option, option)) { - return candidate; - } - } - return NULL; -} - -static gboolean -option_relevant(NMConnection *connection, const NMMetaAbstractInfo *abstract_info) -{ - const char * setting_name; - NMMetaPropertyInfFlags inf_flags; - - _meta_abstract_get(abstract_info, NULL, &setting_name, NULL, NULL, &inf_flags, NULL, NULL); - - if ((inf_flags & NM_META_PROPERTY_INF_FLAG_DONT_ASK) - && !(_dynamic_options_get(abstract_info) & PROPERTY_INF_FLAG_ENABLED)) - return FALSE; - if (_dynamic_options_get(abstract_info) & PROPERTY_INF_FLAG_DISABLED) - return FALSE; - if (!nm_connection_get_setting_by_name(connection, setting_name)) - return FALSE; - return TRUE; -} - -/*****************************************************************************/ - -static void -complete_property_name(NmCli * nmc, - NMConnection * connection, - NMMetaAccessorModifier modifier, - const char * prefix, - const char * postfix) -{ - NMSettingConnection * s_con; - const NMMetaSettingValidPartItem *const *valid_settings_main; - const NMMetaSettingValidPartItem *const *valid_settings_slave; - const char * connection_type = NULL; - const char * slave_type = NULL; - gs_free char * word_list = NULL; - NMMetaSettingType s; - - connection_type = nm_connection_get_connection_type(connection); - s_con = nm_connection_get_setting_connection(connection); - if (s_con) - slave_type = nm_setting_connection_get_slave_type(s_con); - valid_settings_main = get_valid_settings_array(connection_type); - valid_settings_slave = nm_meta_setting_info_valid_parts_for_slave_type(slave_type, NULL); - - word_list = get_valid_properties_string(valid_settings_main, - valid_settings_slave, - modifier, - prefix, - postfix); - if (word_list) - g_print("%s", word_list); - - if (modifier != NM_META_ACCESSOR_MODIFIER_SET) - return; - - for (s = 0; s < _NM_META_SETTING_TYPE_NUM; s++) { - const NMMetaPropertyInfo *const *property_infos; - guint p; - - if (!nm_connection_get_setting_by_name( - connection, - nm_meta_setting_infos_editor[s].general->setting_name)) - continue; - - property_infos = nm_meta_setting_infos_editor[s].properties; - if (!property_infos) - continue; - for (p = 0; property_infos[p]; p++) { - const NMMetaPropertyInfo *property_info = property_infos[p]; - - if (_meta_property_needs_bond_hack(property_info)) { - guint i; - - for (i = 0; i < nm_meta_property_typ_data_bond.nested_len; i++) { - const NMMetaNestedPropertyInfo *bi = &nm_meta_property_typ_data_bond.nested[i]; - - if (!bi->base.property_alias - || !g_str_has_prefix(bi->base.property_alias, prefix)) - continue; - g_print("%s\n", bi->base.property_alias); - } - } else { - if (!property_info->is_cli_option) - continue; - if (!property_info->property_alias - || !g_str_has_prefix(property_info->property_alias, prefix)) - continue; - g_print("%s\n", property_info->property_alias); - } - } - } -} - -static void -run_rl_generator(rl_compentry_func_t *generator_func, const char *prefix) -{ - int state = 0; - char *str; - - while ((str = generator_func(prefix, state))) { - g_print("%s\n", str); - g_free(str); - if (state == 0) - state = 1; - } -} - -static gboolean -complete_option(NmCli * nmc, - const NMMetaAbstractInfo *abstract_info, - const char * prefix, - NMConnection * context_connection) -{ - const OptionInfo * candidate; - const char *const * values; - gs_strfreev char ** values_to_free = NULL; - gboolean complete_filename = FALSE; - const NMMetaOperationContext ctx = { - .connection = context_connection, - }; - - values = nm_meta_abstract_info_complete(abstract_info, - nmc_meta_environment, - (gpointer) nmc_meta_environment_arg, - &ctx, - prefix, - &complete_filename, - &values_to_free); - if (complete_filename) { - nmc->return_value = NMC_RESULT_COMPLETE_FILE; - return TRUE; - } - if (values) { - for (; values[0]; values++) - g_print("%s\n", values[0]); - return TRUE; - } - - candidate = _meta_abstract_get_option_info(abstract_info); - if (candidate && candidate->generator_func) { - run_rl_generator(candidate->generator_func, prefix); - return TRUE; - } - - return FALSE; -} - -static void -complete_existing_setting(NmCli *nmc, NMConnection *connection, const char *prefix) -{ - gs_free NMSetting ** settings = NULL; - const NMMetaSettingInfoEditor *editor; - guint i; - - settings = nm_connection_get_settings(connection, NULL); - for (i = 0; settings && settings[i]; i++) { - editor = nm_meta_setting_info_editor_find_by_setting(settings[i]); - - if (!prefix || g_str_has_prefix(editor->general->setting_name, prefix)) - g_print("%s\n", editor->general->setting_name); - - if (editor->alias) { - if (!prefix || g_str_has_prefix(editor->alias, prefix)) - g_print("%s\n", editor->alias); - } - } -} - -static void -complete_property(NmCli * nmc, - const char * setting_name, - const char * property, - const char * prefix, - NMConnection *connection) -{ - const NMMetaPropertyInfo *property_info; - - property_info = nm_meta_property_info_find_by_name(setting_name, property); - if (property_info) - complete_option(nmc, (const NMMetaAbstractInfo *) property_info, prefix, connection); -} - -/*****************************************************************************/ - -static gboolean -connection_remove_setting(NMConnection *connection, NMSetting *setting, GError **error) -{ - gboolean mandatory; - - g_return_val_if_fail(setting, FALSE); - - mandatory = is_setting_mandatory(connection, setting); - if (!mandatory) { - nm_connection_remove_setting(connection, G_OBJECT_TYPE(setting)); - return TRUE; - } - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: setting '%s' is mandatory and cannot be removed."), - nm_setting_get_name(setting)); - return FALSE; -} - -static gboolean -get_value(const char ** value, - int * argc, - const char *const **argv, - const char * option, - GError ** error) -{ - if (!**argv) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: value for '%s' is missing."), - option); - return FALSE; - } - - /* Empty string will reset the value to default */ - if (**argv[0] == '\0') - *value = NULL; - else - *value = *argv[0]; - - (*argc)--; - (*argv)++; - return TRUE; -} - -gboolean -nmc_process_connection_properties(NmCli * nmc, - NMConnection * connection, - int * argc, - const char *const **argv, - gboolean allow_setting_removal, - GError ** error) -{ - /* First check if we have a slave-type, as this would mean we will not - * have ip properties but possibly others, slave-type specific. - */ - /* Go through arguments and set properties */ - do { - const NMMetaSettingValidPartItem *const *type_settings; - const NMMetaSettingValidPartItem *const *slv_settings; - NMMetaAccessorModifier modifier; - const char * option_orig; - const char * option; - const char * value = NULL; - const char * tmp; - const NMMetaAbstractInfo * chosen = NULL; - const char * chosen_setting_name = NULL; - const char * chosen_option = NULL; - NMMetaSettingType s; - - if (!con_settings(connection, &type_settings, &slv_settings, error)) - return FALSE; - - ensure_settings(connection, slv_settings); - ensure_settings(connection, type_settings); - - if (*argc <= 0) { - g_set_error_literal(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: <setting>.<property> argument is missing.")); - return FALSE; - } - - nm_assert(argv); - nm_assert(*argv); - nm_assert(**argv); - - option_orig = **argv; - - switch (option_orig[0]) { - case '+': - modifier = NM_META_ACCESSOR_MODIFIER_ADD; - option = &option_orig[1]; - break; - case '-': - modifier = NM_META_ACCESSOR_MODIFIER_DEL; - option = &option_orig[1]; - break; - default: - modifier = NM_META_ACCESSOR_MODIFIER_SET; - option = option_orig; - break; - } - - if (allow_setting_removal && modifier == NM_META_ACCESSOR_MODIFIER_SET - && nm_streq(option, "remove")) { - NMSetting * ss; - const char *setting_name; - - (*argc)--; - (*argv)++; - - if (*argc == 1 && nmc->complete) { - complete_existing_setting(nmc, connection, value); - return TRUE; - } - - if (!*argc) { - g_set_error_literal(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: missing setting.")); - return FALSE; - } - - setting_name = **argv; - (*argc)--; - (*argv)++; - - ss = is_setting_valid(connection, type_settings, slv_settings, setting_name); - if (!ss) { - if (!check_valid_name(setting_name, type_settings, slv_settings, NULL)) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: invalid setting argument '%s'."), - setting_name); - return FALSE; - } - continue; - } - - if (!connection_remove_setting(connection, ss, error)) - return FALSE; - - continue; - } - - if ((tmp = strchr(option, '.'))) { - gs_free char *option_sett = g_strndup(option, tmp - option); - const char * option_prop = &tmp[1]; - const char * option_sett_expanded; - GError * local = NULL; - - /* This seems like a <setting>.<property> (such as "connection.id" or "bond.mode"), - * optionally prefixed with "+| or "-". */ - - if (*argc == 1 && nmc->complete) - complete_property_name(nmc, connection, modifier, option_sett, option_prop); - - option_sett_expanded = - check_valid_name(option_sett, type_settings, slv_settings, &local); - if (!option_sett_expanded) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: invalid or not allowed setting '%s': %s."), - option_sett, - local->message); - g_clear_error(&local); - return FALSE; - } - - (*argc)--; - (*argv)++; - if (!get_value(&value, argc, argv, option_orig, error)) - return FALSE; - - if (!*argc && nmc->complete) { - complete_property(nmc, option_sett, option_prop, value ?: "", connection); - return TRUE; - } - - if (!set_property(nmc->client, - connection, - option_sett_expanded, - option_prop, - value, - modifier, - error)) - return FALSE; - - continue; - } - - /* Let's see if this is an property alias (such as "id", "mode", "type" or "con-name")*/ - for (s = 0; s < _NM_META_SETTING_TYPE_NUM; s++) { - const NMMetaPropertyInfo *const *property_infos; - guint p; - - if (!check_valid_name(nm_meta_setting_infos[s].setting_name, - type_settings, - slv_settings, - NULL)) - continue; - - property_infos = nm_meta_setting_infos_editor[s].properties; - if (!property_infos) - continue; - for (p = 0; property_infos[p]; p++) { - const NMMetaPropertyInfo *property_info = property_infos[p]; - - if (_meta_property_needs_bond_hack(property_info)) { - guint i; - - for (i = 0; i < nm_meta_property_typ_data_bond.nested_len; i++) { - const NMMetaNestedPropertyInfo *bi = - &nm_meta_property_typ_data_bond.nested[i]; - - if (!nm_streq0(bi->base.property_alias, option)) - continue; - if (chosen) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: '%s' is ambiguous (%s.%s or %s.%s)."), - option, - chosen_setting_name, - chosen_option, - nm_meta_setting_infos[s].setting_name, - option); - return FALSE; - } - chosen_setting_name = nm_meta_setting_infos[s].setting_name; - chosen_option = option; - chosen = (const NMMetaAbstractInfo *) bi; - } - } else { - if (!property_info->is_cli_option) - continue; - if (!nm_streq0(property_info->property_alias, option)) - continue; - if (chosen) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: '%s' is ambiguous (%s.%s or %s.%s)."), - option, - chosen_setting_name, - chosen_option, - nm_meta_setting_infos[s].setting_name, - option); - return FALSE; - } - chosen_setting_name = nm_meta_setting_infos[s].setting_name; - chosen_option = option; - chosen = (const NMMetaAbstractInfo *) property_info; - } - } - } - - if (!chosen) { - if (*argc == 1 && nmc->complete) { - if (allow_setting_removal && g_str_has_prefix("remove", option)) - g_print("remove\n"); - complete_property_name(nmc, connection, modifier, option, NULL); - } - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: invalid <setting>.<property> '%s'."), - option); - return FALSE; - } - - if (*argc == 1 && nmc->complete) - complete_property_name(nmc, connection, modifier, option, NULL); - - (*argc)--; - (*argv)++; - if (!get_value(&value, argc, argv, option_orig, error)) - return FALSE; - - if (!*argc && nmc->complete) - complete_option(nmc, chosen, value ?: "", connection); - - if (!set_option(nmc, connection, chosen, value, error)) - return FALSE; - - } while (*argc); - - return TRUE; -} - -static void -add_connection_cb(GObject *client, GAsyncResult *result, gpointer user_data) -{ - nm_auto_free_add_connection_info AddConnectionInfo *info = user_data; - NmCli * nmc = info->nmc; - NMRemoteConnection * connection; - GError * error = NULL; - const GPtrArray * connections; - guint i, found; - - connection = nm_client_add_connection2_finish(NM_CLIENT(client), result, NULL, &error); - if (error) { - g_string_printf(nmc->return_text, - _("Error: Failed to add '%s' connection: %s"), - info->new_id, - error->message); - g_error_free(error); - nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; - } else { - connections = nm_client_get_connections(nmc->client); - if (connections) { - found = 0; - for (i = 0; i < connections->len; i++) { - NMConnection *candidate = NM_CONNECTION(connections->pdata[i]); - - if ((NMConnection *) connection == candidate) - continue; - if (nm_streq0(nm_connection_get_id(candidate), info->new_id)) - found++; - } - if (found > 0) { - g_printerr(g_dngettext(GETTEXT_PACKAGE, - "Warning: There is another connection with the name '%1$s'. " - "Reference the connection by its uuid '%2$s'\n", - "Warning: There are %3$u other connections with the name " - "'%1$s'. Reference the connection by its uuid '%2$s'\n", - found), - info->new_id, - nm_connection_get_uuid(NM_CONNECTION(connection)), - found); - } - } - - g_print(_("Connection '%s' (%s) successfully added.\n"), - nm_connection_get_id(NM_CONNECTION(connection)), - nm_connection_get_uuid(NM_CONNECTION(connection))); - g_object_unref(connection); - } - - quit(); -} - -static void -add_connection(NMClient * client, - NMConnection * connection, - gboolean temporary, - GAsyncReadyCallback callback, - gpointer user_data) -{ - nm_client_add_connection2(client, - nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL), - temporary ? NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY - : NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK, - NULL, - TRUE, - NULL, - callback, - user_data); -} - -static void -update_connection(NMRemoteConnection *connection, - gboolean temporary, - GAsyncReadyCallback callback, - gpointer user_data) -{ - nm_remote_connection_commit_changes_async(connection, !temporary, NULL, callback, user_data); -} - -static gboolean -is_single_word(const char *line) -{ - size_t n1, n2, n3; - - n1 = strspn(line, " \t"); - n2 = strcspn(line + n1, " \t\0") + n1; - n3 = strspn(line + n2, " \t"); - - if (n3 == 0) - return TRUE; - else - return FALSE; -} - -static char ** -nmcli_con_add_tab_completion(const char *text, int start, int end) -{ - NMMetaSettingType s; - char ** match_array = NULL; - rl_compentry_func_t * generator_func = NULL; - gs_free char * no = g_strdup_printf("[%s]: ", _("no")); - gs_free char * yes = g_strdup_printf("[%s]: ", _("yes")); - const NMMetaAbstractInfo *info; - - /* Disable readline's default filename completion */ - rl_attempted_completion_over = 1; - - /* Restore standard append character to space */ - rl_completion_append_character = '\x00'; - - if (!is_single_word(rl_line_buffer)) - return NULL; - - for (s = 0; s < _NM_META_SETTING_TYPE_NUM; s++) { - const NMMetaPropertyInfo *const *property_infos; - guint p; - - property_infos = nm_meta_setting_infos_editor[s].properties; - if (!property_infos) - continue; - for (p = 0; property_infos[p]; p++) { - const NMMetaPropertyInfo *property_info = property_infos[p]; - - if (_meta_property_needs_bond_hack(property_info)) { - guint i; - - for (i = 0; i < nm_meta_property_typ_data_bond.nested_len; i++) { - const NMMetaNestedPropertyInfo *bi = &nm_meta_property_typ_data_bond.nested[i]; - - if (bi->base.prompt && g_str_has_prefix(rl_prompt, bi->base.prompt)) { - goto next; - } - } - } else { - if (property_info->prompt && g_str_has_prefix(rl_prompt, property_info->prompt)) { - info = (const NMMetaAbstractInfo *) property_info; - nmc_tab_completion.words = _meta_abstract_complete(info, text); - if (nmc_tab_completion.words) { - match_array = rl_completion_matches(text, _meta_abstract_generator); - nm_clear_pointer(&nmc_tab_completion.words, g_strfreev); - } - return match_array; - } - } - } - } - -next: - if (g_str_has_prefix(rl_prompt, NM_META_TEXT_PROMPT_BT_TYPE)) - generator_func = gen_func_bt_type; - else if (g_str_has_prefix(rl_prompt, NM_META_TEXT_PROMPT_BOND_MODE)) - generator_func = gen_func_bond_mode; - else if (g_str_has_prefix(rl_prompt, NM_META_TEXT_PROMPT_BOND_MON_MODE)) - generator_func = gen_func_bond_mon_mode; - else if (g_str_has_suffix(rl_prompt, yes) || g_str_has_suffix(rl_prompt, no)) - generator_func = gen_func_bool_values_l10n; - - if (generator_func) - match_array = rl_completion_matches(text, generator_func); - - return match_array; -} - -static void -ask_option(NmCli *nmc, NMConnection *connection, const NMMetaAbstractInfo *abstract_info) -{ - char * value; - GError * error = NULL; - gs_free char * prompt = NULL; - gboolean multi; - const char * opt_prompt, *opt_def_hint; - NMMetaPropertyInfFlags inf_flags; - - _meta_abstract_get(abstract_info, - NULL, - NULL, - NULL, - NULL, - &inf_flags, - &opt_prompt, - &opt_def_hint); - prompt = - g_strjoin("", gettext(opt_prompt), opt_def_hint ? " " : "", opt_def_hint ?: "", ": ", NULL); - - multi = NM_FLAGS_HAS(inf_flags, NM_META_PROPERTY_INF_FLAG_MULTI); - - if (multi) - g_print(_("You can specify this option more than once. Press <Enter> when you're done.\n")); - -again: - value = nmc_readline(&nmc->nmc_config, "%s", prompt); - if (multi && !value) - return; - - if (!set_option(nmc, connection, abstract_info, value, &error)) { - g_printerr("%s\n", error->message); - g_clear_error(&error); - goto again; - } - - if (multi && value) - goto again; -} - -static NMMetaSettingType -connection_get_base_meta_setting_type(NMConnection *connection) -{ - const char * connection_type; - NMSetting * base_setting; - const NMMetaSettingInfoEditor *editor; - - connection_type = nm_connection_get_connection_type(connection); - nm_assert(connection_type); - base_setting = nm_connection_get_setting_by_name(connection, connection_type); - nm_assert(base_setting); - editor = nm_meta_setting_info_editor_find_by_setting(base_setting); - nm_assert(editor); - - return editor - nm_meta_setting_infos_editor; -} - -static void -questionnaire_mandatory_ask_setting(NmCli *nmc, NMConnection *connection, NMMetaSettingType type) -{ - const NMMetaSettingInfoEditor *editor; - const NMMetaPropertyInfo * property_info; - guint p; - - editor = &nm_meta_setting_infos_editor[type]; - if (!editor->properties) - return; - - for (p = 0; editor->properties[p]; p++) { - property_info = editor->properties[p]; - - if (_meta_property_needs_bond_hack(property_info)) { - guint i; - - for (i = 0; i < nm_meta_property_typ_data_bond.nested_len; i++) { - const NMMetaNestedPropertyInfo *bi = &nm_meta_property_typ_data_bond.nested[i]; - - if (!option_relevant(connection, (const NMMetaAbstractInfo *) bi)) - continue; - if ((bi->base.inf_flags & NM_META_PROPERTY_INF_FLAG_REQD) - || (_dynamic_options_get((const NMMetaAbstractInfo *) bi) - & PROPERTY_INF_FLAG_ENABLED)) - ask_option(nmc, connection, (const NMMetaAbstractInfo *) bi); - } - } else { - if (!property_info->is_cli_option) - continue; - - if (!option_relevant(connection, (const NMMetaAbstractInfo *) property_info)) - continue; - if ((property_info->inf_flags & NM_META_PROPERTY_INF_FLAG_REQD) - || (_dynamic_options_get((const NMMetaAbstractInfo *) property_info) - & PROPERTY_INF_FLAG_ENABLED)) - ask_option(nmc, connection, (const NMMetaAbstractInfo *) property_info); - } - } -} - -static void -questionnaire_mandatory(NmCli *nmc, NMConnection *connection) -{ - NMMetaSettingType s, base; - - /* First ask connection properties */ - questionnaire_mandatory_ask_setting(nmc, connection, NM_META_SETTING_TYPE_CONNECTION); - - /* Ask properties of the base setting */ - base = connection_get_base_meta_setting_type(connection); - questionnaire_mandatory_ask_setting(nmc, connection, base); - - /* Remaining settings */ - for (s = 0; s < _NM_META_SETTING_TYPE_NUM; s++) { - if (!NM_IN_SET(s, NM_META_SETTING_TYPE_CONNECTION, base)) - questionnaire_mandatory_ask_setting(nmc, connection, s); - } -} - -static gboolean -want_provide_opt_args(const NmcConfig *nmc_config, const char *type, guint num) -{ - gs_free char *answer = NULL; - - /* Ask for optional arguments. */ - g_print(ngettext("There is %d optional setting for %s.\n", - "There are %d optional settings for %s.\n", - num), - (int) num, - type); - answer = nmc_readline( - nmc_config, - ngettext("Do you want to provide it? %s", "Do you want to provide them? %s", num), - prompt_yes_no(TRUE, NULL)); - nm_strstrip(answer); - return !answer || matches(answer, WORD_YES); -} - -static gboolean -questionnaire_one_optional(NmCli *nmc, NMConnection *connection) -{ - NMMetaSettingType base; - gs_unref_ptrarray GPtrArray *infos = NULL; - guint i, j; - gboolean already_confirmed = FALSE; - NMMetaSettingType s_asking = NM_META_SETTING_TYPE_UNKNOWN; - NMMetaSettingType settings[_NM_META_SETTING_TYPE_NUM]; - - base = connection_get_base_meta_setting_type(connection); - - i = 0; - settings[i++] = NM_META_SETTING_TYPE_CONNECTION; - settings[i++] = base; - for (j = 0; j < _NM_META_SETTING_TYPE_NUM; j++) { - if (!NM_IN_SET(j, NM_META_SETTING_TYPE_CONNECTION, base)) - settings[i++] = j; - } - - infos = g_ptr_array_new(); - - /* Find first setting with relevant options and count them. */ -again: - for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { - const NMMetaPropertyInfo *const *property_infos; - guint p; - - if (s_asking != NM_META_SETTING_TYPE_UNKNOWN && settings[i] != s_asking) - continue; - - property_infos = nm_meta_setting_infos_editor[settings[i]].properties; - if (!property_infos) - continue; - for (p = 0; property_infos[p]; p++) { - const NMMetaPropertyInfo *property_info = property_infos[p]; - - if (_meta_property_needs_bond_hack(property_info)) { - for (j = 0; j < nm_meta_property_typ_data_bond.nested_len; j++) { - const NMMetaNestedPropertyInfo *bi = &nm_meta_property_typ_data_bond.nested[j]; - - if (!option_relevant(connection, (const NMMetaAbstractInfo *) bi)) - continue; - g_ptr_array_add(infos, (gpointer) bi); - } - } else { - if (!property_info->is_cli_option) - continue; - if (!option_relevant(connection, (const NMMetaAbstractInfo *) property_info)) - continue; - g_ptr_array_add(infos, (gpointer) property_info); - } - } - if (infos->len) { - s_asking = settings[i]; - break; - } - } - - if (infos->len) { - const NMMetaSettingInfoEditor *setting_info = NULL; - - _meta_abstract_get(infos->pdata[0], &setting_info, NULL, NULL, NULL, NULL, NULL, NULL); - - /* Now ask for the settings. */ - if (already_confirmed - || want_provide_opt_args(&nmc->nmc_config, _(setting_info->pretty_name), infos->len)) { - ask_option(nmc, connection, infos->pdata[0]); - already_confirmed = TRUE; - /* asking for an option may enable other options. Create the list again. */ - g_ptr_array_set_size(infos, 0); - goto again; - } - } - - if (s_asking == NM_META_SETTING_TYPE_UNKNOWN) - return FALSE; - - /* Make sure we won't ask again. */ - disable_options(nm_meta_setting_infos[s_asking].setting_name, NULL); - return TRUE; -} - -static void -do_connection_add(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - gs_unref_object NMConnection *connection = NULL; - NMSettingConnection * s_con; - gs_free_error GError *error = NULL; - gboolean save_bool = TRUE; - gboolean seen_dash_dash = FALSE; - NMMetaSettingType s; - - next_arg(nmc, &argc, &argv, NULL); - - rl_attempted_completion_function = nmcli_con_add_tab_completion; - - nmc->return_value = NMC_RESULT_SUCCESS; - - connection = nm_simple_connection_new(); - - s_con = (NMSettingConnection *) nm_setting_connection_new(); - nm_connection_add_setting(connection, NM_SETTING(s_con)); - -read_properties: - g_clear_error(&error); - /* Get the arguments from the command line if any */ - if (argc && !nmc_process_connection_properties(nmc, connection, &argc, &argv, FALSE, &error)) { - if (g_strcmp0(*argv, "--") == 0 && !seen_dash_dash) { - /* This is for compatibility with older nmcli that required - * options and properties to be separated with "--" */ - seen_dash_dash = TRUE; - next_arg(nmc, &argc, &argv, NULL); - goto read_properties; - } else if (g_strcmp0(*argv, "save") == 0) { - /* It would be better if "save" was a separate argument and not - * mixed with properties, but there's not much we can do about it now. */ - argc--; - argv++; - if (!argc) { - g_string_printf(nmc->return_text, - _("Error: value for '%s' argument is required."), - "save"); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - g_clear_error(&error); - if (!nmc_string_to_bool(*argv, &save_bool, &error)) { - g_string_printf(nmc->return_text, _("Error: 'save': %s."), error->message); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - next_arg(nmc, &argc, &argv, NULL); - goto read_properties; - } - - g_string_assign(nmc->return_text, error->message); - nmc->return_value = error->code; - goto finish; - } - - if (nmc->complete) - goto finish; - - /* Now ask user for the rest of the mandatory options. */ - if (nmc->ask) - questionnaire_mandatory(nmc, connection); - - /* Traditionally, we didn't ask for these options for ethernet slaves. They don't - * make much sense, since these are likely to be set by the master anyway. */ - if (nm_setting_connection_get_slave_type(s_con)) { - disable_options(NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MTU); - disable_options(NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MAC_ADDRESS); - disable_options(NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_CLONED_MAC_ADDRESS); - } - - /* Connection id is special in that it's required but we don't insist - * on getting it from the user -- we just make up something sensible. */ - if (!nm_setting_connection_get_id(s_con)) { - const char *ifname = nm_setting_connection_get_interface_name(s_con); - const char *type = nm_setting_connection_get_connection_type(s_con); - const char *slave_type = nm_setting_connection_get_slave_type(s_con); - - /* If only bother when there's a type, which is not guaranteed at this point. - * Otherwise, the validation will fail anyway. */ - if (type) { - gs_free char * try_name = NULL; - gs_free char * default_name = NULL; - const GPtrArray *connections; - - connections = nm_client_get_connections(nmc->client); - try_name = - ifname ? g_strdup_printf("%s-%s", get_name_alias_toplevel(type, slave_type), ifname) - : g_strdup(get_name_alias_toplevel(type, slave_type)); - default_name = nmc_unique_connection_name(connections, try_name); - g_object_set(s_con, NM_SETTING_CONNECTION_ID, default_name, NULL); - } - } - - /* For some software connection types we generate the interface name for the user. */ - set_default_interface_name(nmc, s_con); - - /* Now see if there's something optional that needs to be asked for. - * Keep asking until there's no more things to ask for. */ - do { - /* This ensures all settings that make sense are present. */ - nm_connection_normalize(connection, NULL, NULL, NULL); - } while (nmc->ask && questionnaire_one_optional(nmc, connection)); - - /* Mandatory settings. No good reason to check this other than guarding the user - * from doing something that's not likely to make sense (such as missing ifname - * on a bond/bridge/team, etc.). Added just to preserve traditional behavior, it - * perhaps is a good idea to just remove this. */ - for (s = 0; s < _NM_META_SETTING_TYPE_NUM; s++) { - const NMMetaPropertyInfo *const *property_infos; - guint p; - - property_infos = nm_meta_setting_infos_editor[s].properties; - if (!property_infos) - continue; - for (p = 0; property_infos[p]; p++) { - const NMMetaPropertyInfo *property_info = property_infos[p]; - - if (_meta_property_needs_bond_hack(property_info)) { - guint i; - - for (i = 0; i < nm_meta_property_typ_data_bond.nested_len; i++) { - const NMMetaNestedPropertyInfo *bi = &nm_meta_property_typ_data_bond.nested[i]; - - if (!option_relevant(connection, (const NMMetaAbstractInfo *) bi)) - continue; - if (bi->base.inf_flags & NM_META_PROPERTY_INF_FLAG_REQD) { - g_string_printf(nmc->return_text, - _("Error: '%s' argument is required."), - bi->base.property_alias); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - } - } else { - if (!property_info->is_cli_option) - continue; - if (!option_relevant(connection, (const NMMetaAbstractInfo *) property_info)) - continue; - if (property_info->inf_flags & NM_META_PROPERTY_INF_FLAG_REQD) { - g_string_printf(nmc->return_text, - _("Error: '%s' argument is required."), - property_info->property_alias); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - } - } - } - - add_connection(nmc->client, - connection, - !save_bool, - add_connection_cb, - _add_connection_info_new(nmc, NULL, connection)); - nmc->should_wait++; - -finish: - reset_options(); -} - -/*****************************************************************************/ -/* Functions for readline TAB completion in editor */ - -static void -uuid_display_hook(char **array, int len, int max_len) -{ - const GPtrArray *connections; - NMConnection * con; - int i, max = 0; - char * tmp; - const char * id; - for (i = 1; i <= len; i++) { - connections = nm_client_get_connections(nmc_tab_completion.nmc->client); - con = nmc_find_connection(connections, "uuid", array[i], NULL, FALSE); - id = con ? nm_connection_get_id(con) : NULL; - if (id) { - tmp = g_strdup_printf("%s (%s)", array[i], id); - g_free(array[i]); - array[i] = tmp; - if (max < strlen(id)) - max = strlen(id); - } - } - rl_display_match_list(array, len, max_len + max + 3); - rl_forced_update_display(); -} - -static char * -gen_nmcli_cmds_menu(const char *text, int state) -{ - const char *words[] = {"goto", - "set", - "remove", - "describe", - "print", - "verify", - "save", - "activate", - "back", - "help", - "quit", - "nmcli", - NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -static char * -gen_nmcli_cmds_submenu(const char *text, int state) -{ - const char *words[] = - {"set", "add", "change", "remove", "describe", "print", "back", "help", "quit", NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -static char * -gen_cmd_nmcli(const char *text, int state) -{ - const char *words[] = {"status-line", "save-confirmation", "show-secrets", NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -static char * -gen_func_bool_values(const char *text, int state) -{ - const char *words[] = {"yes", "no", NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -static char * -gen_cmd_verify0(const char *text, int state) -{ - const char *words[] = {"all", "fix", NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -static char * -gen_cmd_print0(const char *text, int state) -{ - static char **words = NULL; - char * ret = NULL; - - if (!state) { - GVariant * settings; - GVariantIter iter; - const char * setting_name; - int i = 0; - - settings = nm_connection_to_dbus(nmc_tab_completion.connection, - NM_CONNECTION_SERIALIZE_NO_SECRETS); - words = g_new(char *, g_variant_n_children(settings) + 2); - g_variant_iter_init(&iter, settings); - while (g_variant_iter_next(&iter, "{&s@a{sv}}", &setting_name, NULL)) - words[i++] = g_strdup(setting_name); - words[i++] = g_strdup("all"); - words[i] = NULL; - g_variant_unref(settings); - } - - if (words) { - ret = nmc_rl_gen_func_basic(text, state, (const char **) words); - if (ret == NULL) { - g_strfreev(words); - words = NULL; - } - } - return ret; -} - -static char * -gen_cmd_print2(const char *text, int state) -{ - const char *words[] = {"setting", "connection", "all", NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -static char * -gen_cmd_save(const char *text, int state) -{ - const char *words[] = {"persistent", "temporary", NULL}; - return nmc_rl_gen_func_basic(text, state, words); -} - -static rl_compentry_func_t * -gen_connection_types(const char *text) -{ - gs_free char ** values = NULL; - const NMMetaSettingInfoEditor *editor; - GPtrArray * array; - int i; - - array = g_ptr_array_new(); - - for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { - editor = &nm_meta_setting_infos_editor[i]; - if (!editor->valid_parts) - continue; - g_ptr_array_add(array, (gpointer) nm_meta_setting_infos[i].setting_name); - if (editor->alias) - g_ptr_array_add(array, (gpointer) editor->alias); - } - - g_ptr_array_add(array, "bond-slave"); - g_ptr_array_add(array, "bridge-slave"); - g_ptr_array_add(array, "team-slave"); - g_ptr_array_add(array, NULL); - - values = (char **) g_ptr_array_free(array, FALSE); - - return nmc_rl_compentry_func_wrap((const char *const *) values); -} - -static char * -gen_setting_names(const char *text, int state) -{ - static int list_idx, len, is_slv; - const char * s_name, *a_name; - const NMMetaSettingValidPartItem *const *valid_settings_arr; - NMSettingConnection * s_con; - const char * s_type = NULL; - - if (!state) { - list_idx = 0; - len = strlen(text); - is_slv = 0; - } - - if (!is_slv) { - valid_settings_arr = get_valid_settings_array(nmc_tab_completion.con_type); - if (list_idx >= NM_PTRARRAY_LEN(valid_settings_arr)) - return NULL; - for (; valid_settings_arr[list_idx];) { - const NMMetaSettingInfoEditor *setting_info = - valid_settings_arr[list_idx]->setting_info; - - a_name = setting_info->alias; - s_name = setting_info->general->setting_name; - list_idx++; - if (len == 0 && a_name) - return g_strdup_printf("%s (%s)", s_name, a_name); - if (a_name && !strncmp(text, a_name, len)) - return g_strdup(a_name); - if (s_name && !strncmp(text, s_name, len)) - return g_strdup(s_name); - } - - /* Let's give a try to parameters related to slave type */ - list_idx = 0; - is_slv = 1; - } - - /* is_slv */ - s_con = nm_connection_get_setting_connection(nmc_tab_completion.connection); - if (s_con) - s_type = nm_setting_connection_get_slave_type(s_con); - valid_settings_arr = nm_meta_setting_info_valid_parts_for_slave_type(s_type, NULL); - - if (list_idx < NM_PTRARRAY_LEN(valid_settings_arr)) { - while (valid_settings_arr[list_idx]) { - const NMMetaSettingInfoEditor *setting_info = - valid_settings_arr[list_idx]->setting_info; - - a_name = setting_info->alias; - s_name = setting_info->general->setting_name; - list_idx++; - if (len == 0 && a_name) - return g_strdup_printf("%s (%s)", s_name, a_name); - if (a_name && !strncmp(text, a_name, len)) - return g_strdup(a_name); - if (s_name && !strncmp(text, s_name, len)) - return g_strdup(s_name); - } - } - - return NULL; -} - -static char * -gen_property_names(const char *text, int state) -{ - NMSetting * setting = NULL; - char ** valid_props = NULL; - char * ret = NULL; - const char * line = rl_line_buffer; - const char * setting_name; - char ** strv = NULL; - const NMMetaSettingValidPartItem *const *valid_settings_main; - const NMMetaSettingValidPartItem *const *valid_settings_slave; - const char * p1; - const char * slv_type; - - /* Try to get the setting from 'line' - setting_name.property */ - p1 = strchr(line, '.'); - if (p1) { - while (p1 > line && !g_ascii_isspace(*p1)) - p1--; - - strv = g_strsplit(p1 + 1, ".", 2); - - valid_settings_main = get_valid_settings_array(nmc_tab_completion.con_type); - - /* Support autocompletion of slave-connection parameters - * guessing the slave type from the setting name already - * typed (or autocompleted) */ - if (nm_streq0(strv[0], NM_SETTING_TEAM_PORT_SETTING_NAME)) - slv_type = NM_SETTING_TEAM_SETTING_NAME; - else if (nm_streq0(strv[0], NM_SETTING_BRIDGE_PORT_SETTING_NAME)) - slv_type = NM_SETTING_BRIDGE_SETTING_NAME; - else - slv_type = NULL; - valid_settings_slave = nm_meta_setting_info_valid_parts_for_slave_type(slv_type, NULL); - - setting_name = check_valid_name(strv[0], valid_settings_main, valid_settings_slave, NULL); - if (setting_name) { - setting = nm_meta_setting_info_editor_new_setting( - nm_meta_setting_info_editor_find_by_name(setting_name, FALSE), - NM_META_ACCESSOR_SETTING_INIT_TYPE_DEFAULT); - } - } - - if (!setting) { - /* Else take the current setting, if any */ - setting = nmc_tab_completion.setting ? g_object_ref(nmc_tab_completion.setting) : NULL; - } - - if (setting) { - valid_props = nmc_setting_get_valid_properties(setting); - ret = nmc_rl_gen_func_basic(text, state, (const char **) valid_props); - } - - g_strfreev(strv); - g_strfreev(valid_props); - if (setting) - g_object_unref(setting); - return ret; -} - -static char * -gen_compat_devices(const char *text, int state) -{ - guint i, j = 0; - const GPtrArray *devices; - const char ** compatible_devices; - char * ret; - - devices = nm_client_get_devices(nmc_tab_completion.nmc->client); - if (devices->len == 0) - return NULL; - - compatible_devices = g_new(const char *, devices->len + 1); - for (i = 0; i < devices->len; i++) { - NMDevice * dev = g_ptr_array_index(devices, i); - const char *ifname = nm_device_get_iface(dev); - NMDevice * device = NULL; - const char *spec_object = NULL; - - if (find_device_for_connection(nmc_tab_completion.nmc, - nmc_tab_completion.connection, - ifname, - NULL, - NULL, - &device, - &spec_object, - NULL)) { - compatible_devices[j++] = ifname; - } - } - compatible_devices[j] = NULL; - - ret = nmc_rl_gen_func_basic(text, state, compatible_devices); - - g_free(compatible_devices); - return ret; -} - -static const char ** -_create_vpn_array(const GPtrArray *connections, gboolean uuid) -{ - int c, idx = 0; - const char **array; - - if (connections->len < 1) - return NULL; - - array = g_new(const char *, connections->len + 1); - for (c = 0; c < connections->len; c++) { - NMConnection *connection = NM_CONNECTION(connections->pdata[c]); - const char * type = nm_connection_get_connection_type(connection); - - if (g_strcmp0(type, NM_SETTING_VPN_SETTING_NAME) == 0) - array[idx++] = - uuid ? nm_connection_get_uuid(connection) : nm_connection_get_id(connection); - } - array[idx] = NULL; - return array; -} - -static char * -gen_vpn_uuids(const char *text, int state) -{ - const GPtrArray *connections; - const char ** uuids; - char * ret; - - connections = nm_client_get_connections(nm_cli_global_readline->client); - if (connections->len < 1) - return NULL; - - uuids = _create_vpn_array(connections, TRUE); - ret = nmc_rl_gen_func_basic(text, state, uuids); - g_free(uuids); - return ret; -} - -static char * -gen_vpn_ids(const char *text, int state) -{ - const GPtrArray *connections; - const char ** ids; - char * ret; - - connections = nm_client_get_connections(nm_cli_global_readline->client); - if (connections->len < 1) - return NULL; - - ids = _create_vpn_array(connections, FALSE); - ret = nmc_rl_gen_func_basic(text, state, ids); - g_free(ids); - return ret; -} - -static rl_compentry_func_t * -get_gen_func_cmd_nmcli(const char *str) -{ - if (!str) - return NULL; - if (matches(str, "status-line")) - return gen_func_bool_values; - if (matches(str, "save-confirmation")) - return gen_func_bool_values; - if (matches(str, "show-secrets")) - return gen_func_bool_values; - return NULL; -} - -/* - * Helper function parsing line for completion. - * IN: - * line : the whole line to be parsed - * end : the position of cursor in the line - * cmd : command to match - * OUT: - * cw_num : is set to the word number being completed (1, 2, 3, 4). - * prev_word : returns the previous word (so that we have some context). - * - * Returns TRUE when the first word of the 'line' matches 'cmd'. - * - * Examples: - * line="rem" cmd="remove" -> TRUE cw_num=1 - * line="set con" cmd="set" -> TRUE cw_num=2 - * line="go ipv4.method" cmd="goto" -> TRUE cw_num=2 - * line=" des eth.mtu " cmd="describe" -> TRUE cw_num=3 - * line=" bla ipv4.method" cmd="goto" -> FALSE - */ -static gboolean -should_complete_cmd(const char *line, int end, const char *cmd, int *cw_num, char **prev_word) -{ - char * tmp; - const char *word1, *word2, *word3; - size_t n1, n2, n3, n4, n5, n6; - gboolean word1_done, word2_done, word3_done; - gboolean ret = FALSE; - - if (!line) - return FALSE; - - tmp = g_strdup(line); - - n1 = strspn(tmp, " \t"); - n2 = strcspn(tmp + n1, " \t\0") + n1; - n3 = strspn(tmp + n2, " \t") + n2; - n4 = strcspn(tmp + n3, " \t\0") + n3; - n5 = strspn(tmp + n4, " \t") + n4; - n6 = strcspn(tmp + n5, " \t\0") + n5; - - word1_done = end > n2; - word2_done = end > n4; - word3_done = end > n6; - tmp[n2] = tmp[n4] = tmp[n6] = '\0'; - - word1 = tmp[n1] ? tmp + n1 : NULL; - word2 = tmp[n3] ? tmp + n3 : NULL; - word3 = tmp[n5] ? tmp + n5 : NULL; - - if (!word1_done) { - if (cw_num) - *cw_num = 1; - if (prev_word) - *prev_word = NULL; - } else if (!word2_done) { - if (cw_num) - *cw_num = 2; - if (prev_word) - *prev_word = g_strdup(word1); - } else if (!word3_done) { - if (cw_num) - *cw_num = 3; - if (prev_word) - *prev_word = g_strdup(word2); - } else { - if (cw_num) - *cw_num = 4; - if (prev_word) - *prev_word = g_strdup(word3); - } - - if (word1 && matches(word1, cmd)) - ret = TRUE; - - g_free(tmp); - return ret; -} - -/* - * extract_setting_and_property: - * prompt: (in) (allow-none): prompt string, or NULL - * line: (in) (allow-none): line, or NULL - * setting: (out) (transfer full) (array zero-terminated=1): - * return location for setting name - * property: (out) (transfer full) (array zero-terminated=1): - * return location for property name - * - * Extract setting and property names from prompt and/or line. - */ -static void -extract_setting_and_property(const char *prompt, const char *line, char **setting, char **property) -{ - char *prop = NULL; - char *sett = NULL; - - if (prompt) { - /* prompt looks like this: - * "nmcli 802-1x>" or "nmcli 802-1x.pac-file>" */ - const char *p1, *p2, *dot; - size_t num1, num2; - p1 = strchr(prompt, ' '); - if (p1) { - dot = strchr(++p1, '.'); - if (dot) { - p2 = dot + 1; - num1 = strcspn(p1, "."); - num2 = strcspn(p2, ">"); - sett = num1 > 0 ? g_strndup(p1, num1) : NULL; - prop = num2 > 0 ? g_strndup(p2, num2) : NULL; - } else { - num1 = strcspn(p1, ">"); - sett = num1 > 0 ? g_strndup(p1, num1) : NULL; - } - } - } - - if (line) { - /* line looks like this: - * " set 802-1x.pac-file ..." or " set pac-file ..." */ - const char *p1, *p2, *dot; - size_t n1, n2, n3, n4; - size_t num1, num2, len; - n1 = strspn(line, " \t"); /* white-space */ - n2 = strcspn(line + n1, " \t\0") + n1; /* command */ - n3 = strspn(line + n2, " \t") + n2; /* white-space */ - n4 = strcspn(line + n3, " \t\0") + n3; /* setting/property */ - p1 = line + n3; - len = n4 - n3; - - dot = strchr(p1, '.'); - if (dot && dot < p1 + len) { - p2 = dot + 1; - num1 = strcspn(p1, "."); - num2 = len > num1 + 1 ? len - num1 - 1 : 0; - sett = num1 > 0 ? g_strndup(p1, num1) : sett; - prop = num2 > 0 ? g_strndup(p2, num2) : prop; - } else { - if (!prop) - prop = len > 0 ? g_strndup(p1, len) : NULL; - } - } - - if (setting) - *setting = sett; - else - g_free(sett); - if (property) - *property = prop; - else - g_free(prop); -} - -static void -get_setting_and_property(const char *prompt, - const char *line, - NMSetting **setting_out, - char ** property_out) -{ - const NMMetaSettingValidPartItem *const *valid_settings_main; - const NMMetaSettingValidPartItem *const *valid_settings_slave; - gs_unref_object NMSetting *setting = NULL; - gs_free char * property = NULL; - NMSettingConnection * s_con; - gs_free char * sett = NULL; - gs_free char * prop = NULL; - const char * s_type = NULL; - const char * setting_name; - - extract_setting_and_property(prompt, line, &sett, &prop); - - if (sett) { - /* Is this too much (and useless?) effort for an unlikely case? */ - s_con = nm_connection_get_setting_connection(nmc_tab_completion.connection); - if (s_con) - s_type = nm_setting_connection_get_slave_type(s_con); - - valid_settings_main = get_valid_settings_array(nmc_tab_completion.con_type); - valid_settings_slave = nm_meta_setting_info_valid_parts_for_slave_type(s_type, NULL); - - setting_name = check_valid_name(sett, valid_settings_main, valid_settings_slave, NULL); - setting = nm_meta_setting_info_editor_new_setting( - nm_meta_setting_info_editor_find_by_name(setting_name, FALSE), - NM_META_ACCESSOR_SETTING_INIT_TYPE_DEFAULT); - } else - setting = nm_g_object_ref(nmc_tab_completion.setting); - - if (setting && prop) - property = is_property_valid(setting, prop, NULL); - else - property = g_strdup(nmc_tab_completion.property); - - *setting_out = g_steal_pointer(&setting); - *property_out = g_steal_pointer(&property); -} - -static gboolean -_get_and_check_property(const char * prompt, - const char * line, - const char **array, - const char **array_multi, - gboolean * multi) -{ - gs_free char *prop = NULL; - gboolean found = FALSE; - - extract_setting_and_property(prompt, line, NULL, &prop); - if (prop) { - if (array) - found = !!nmc_string_is_valid(prop, array, NULL); - if (array_multi && multi) - *multi = !!nmc_string_is_valid(prop, array_multi, NULL); - } - return found; -} - -static gboolean -should_complete_files(const char *prompt, const char *line) -{ - const char *file_properties[] = {/* '802-1x' properties */ - "ca-cert", - "ca-path", - "client-cert", - "pac-file", - "phase2-ca-cert", - "phase2-ca-path", - "phase2-client-cert", - "private-key", - "phase2-private-key", - /* 'team' and 'team-port' properties */ - "config", - /* 'proxy' properties */ - "pac-script", - NULL}; - return _get_and_check_property(prompt, line, file_properties, NULL, NULL); -} - -static gboolean -should_complete_vpn_uuids(const char *prompt, const char *line) -{ - const char *uuid_properties[] = {/* 'connection' properties */ - "secondaries", - NULL}; - return _get_and_check_property(prompt, line, uuid_properties, NULL, NULL); -} - -static const char *const * -get_allowed_property_values(char ***out_to_free) -{ - gs_unref_object NMSetting *setting = NULL; - gs_free char * property = NULL; - const char *const * avals = NULL; - - get_setting_and_property(rl_prompt, rl_line_buffer, &setting, &property); - if (setting && property) - avals = nmc_setting_get_property_allowed_values(setting, property, out_to_free); - return avals; -} - -static gboolean -should_complete_property_values(const char *prompt, const char *line, gboolean *multi) -{ - gs_strfreev char **to_free = NULL; - - /* properties allowing multiple values */ - const char *multi_props[] = {/* '802-1x' properties */ - NM_SETTING_802_1X_EAP, - /* '802-11-wireless-security' properties */ - NM_SETTING_WIRELESS_SECURITY_PROTO, - NM_SETTING_WIRELESS_SECURITY_PAIRWISE, - NM_SETTING_WIRELESS_SECURITY_GROUP, - /* 'bond' properties */ - NM_SETTING_BOND_OPTIONS, - /* 'ethernet' properties */ - NM_SETTING_WIRED_S390_OPTIONS, - NULL}; - _get_and_check_property(prompt, line, NULL, multi_props, multi); - return !!get_allowed_property_values(&to_free); -} - -static gboolean -_setting_property_is_boolean(NMSetting *setting, const char *property_name) -{ - const GParamSpec *pspec; - - nm_assert(NM_IS_SETTING(setting)); - nm_assert(property_name); - - pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(setting), property_name); - return pspec && pspec->value_type == G_TYPE_BOOLEAN; -} - -static gboolean -should_complete_boolean(const char *prompt, const char *line) -{ - gs_unref_object NMSetting *setting = NULL; - gs_free char * property = NULL; - - get_setting_and_property(prompt, line, &setting, &property); - return setting && property && _setting_property_is_boolean(setting, property); -} - -static char * -gen_property_values(const char *text, int state) -{ - gs_strfreev char **to_free = NULL; - const char *const *avals; - - avals = get_allowed_property_values(&to_free); - if (!avals) - return NULL; - return nmc_rl_gen_func_basic(text, state, avals); -} - -/* from readline */ -extern int rl_complete_with_tilde_expansion; - -/* - * Attempt to complete on the contents of TEXT. START and END show the - * region of TEXT that contains the word to complete. We can use the - * entire line in case we want to do some simple parsing. Return the - * array of matches, or NULL if there aren't any. - */ -static char ** -nmcli_editor_tab_completion(const char *text, int start, int end) -{ - rl_compentry_func_t *generator_func = NULL; - const char * line = rl_line_buffer; - gs_free char * prompt_tmp = NULL; - gs_free char * word = NULL; - char ** match_array = NULL; - size_t n1; - int num; - - /* Restore standard append character to space */ - rl_completion_append_character = ' '; - - /* Restore standard function for displaying matches */ - rl_completion_display_matches_hook = NULL; - - /* Disable default filename completion */ - rl_attempted_completion_over = 1; - - /* Enable tilde expansion when filenames are completed */ - rl_complete_with_tilde_expansion = 1; - - /* Filter out possible ANSI color escape sequences */ - prompt_tmp = nmc_filter_out_colors((const char *) rl_prompt); - - /* Find the first non-space character */ - n1 = strspn(line, " \t"); - - /* Choose the right generator function */ - if (strcmp(prompt_tmp, EDITOR_PROMPT_CON_TYPE) == 0) - generator_func = gen_connection_types(text); - else if (strcmp(prompt_tmp, EDITOR_PROMPT_SETTING) == 0) - generator_func = gen_setting_names; - else if (strcmp(prompt_tmp, EDITOR_PROMPT_PROPERTY) == 0) - generator_func = gen_property_names; - else if (g_str_has_suffix(rl_prompt, prompt_yes_no(TRUE, NULL)) - || g_str_has_suffix(rl_prompt, prompt_yes_no(FALSE, NULL))) - generator_func = gen_func_bool_values_l10n; - else if (g_str_has_prefix(prompt_tmp, "nmcli")) { - if (!strchr(prompt_tmp, '.')) { - int level = g_str_has_prefix(prompt_tmp, "nmcli>") ? 0 : 1; - const char *dot = strchr(line, '.'); - gboolean multi; - - /* Main menu - level 0,1 */ - if (start == n1) - generator_func = gen_nmcli_cmds_menu; - else { - if (should_complete_cmd(line, end, "goto", &num, NULL) && num <= 2) { - if (level == 0 && (!dot || dot >= line + end)) - generator_func = gen_setting_names; - else - generator_func = gen_property_names; - } else if (should_complete_cmd(line, end, "set", &num, NULL)) { - if (num < 3) { - if (level == 0 && (!dot || dot >= line + end)) { - generator_func = gen_setting_names; - rl_completion_append_character = '.'; - } else - generator_func = gen_property_names; - } else { - if (num == 3 && should_complete_files(NULL, line)) - rl_attempted_completion_over = 0; - else if (should_complete_vpn_uuids(NULL, line)) { - rl_completion_display_matches_hook = uuid_display_hook; - generator_func = gen_vpn_uuids; - } else if (should_complete_property_values(NULL, line, &multi) - && (num == 3 || multi)) { - generator_func = gen_property_values; - } else if (should_complete_boolean(NULL, line) && num == 3) - generator_func = gen_func_bool_values; - } - } else if ((should_complete_cmd(line, end, "remove", &num, NULL) - || should_complete_cmd(line, end, "describe", &num, NULL)) - && num <= 2) { - if (level == 0 && (!dot || dot >= line + end)) { - generator_func = gen_setting_names; - rl_completion_append_character = '.'; - } else - generator_func = gen_property_names; - } else if (should_complete_cmd(line, end, "nmcli", &num, &word)) { - if (num < 3) - generator_func = gen_cmd_nmcli; - else if (num == 3) - generator_func = get_gen_func_cmd_nmcli(word); - } else if (should_complete_cmd(line, end, "print", &num, NULL) && num <= 2) { - if (level == 0 && (!dot || dot >= line + end)) - generator_func = gen_cmd_print0; - else - generator_func = gen_property_names; - } else if (should_complete_cmd(line, end, "verify", &num, NULL) && num <= 2) { - generator_func = gen_cmd_verify0; - } else if (should_complete_cmd(line, end, "activate", &num, NULL) && num <= 2) { - generator_func = gen_compat_devices; - } else if (should_complete_cmd(line, end, "save", &num, NULL) && num <= 2) { - generator_func = gen_cmd_save; - } else if (should_complete_cmd(line, end, "help", &num, NULL) && num <= 2) - generator_func = gen_nmcli_cmds_menu; - } - } else { - /* Submenu - level 2 */ - if (start == n1) - generator_func = gen_nmcli_cmds_submenu; - else { - gboolean multi; - - if (should_complete_cmd(line, end, "add", &num, NULL) - || should_complete_cmd(line, end, "set", &num, NULL)) { - if (num <= 2 && should_complete_files(prompt_tmp, line)) - rl_attempted_completion_over = 0; - else if (should_complete_vpn_uuids(prompt_tmp, line)) { - rl_completion_display_matches_hook = uuid_display_hook; - generator_func = gen_vpn_uuids; - } else if (should_complete_property_values(prompt_tmp, NULL, &multi) - && (num <= 2 || multi)) { - generator_func = gen_property_values; - } else if (should_complete_boolean(prompt_tmp, NULL) && num <= 2) - generator_func = gen_func_bool_values; - } - if (should_complete_cmd(line, end, "print", &num, NULL) && num <= 2) - generator_func = gen_cmd_print2; - else if (should_complete_cmd(line, end, "help", &num, NULL) && num <= 2) - generator_func = gen_nmcli_cmds_submenu; - } - } - } - - if (generator_func) - match_array = rl_completion_matches(text, generator_func); - - return match_array; -} - -#define NMCLI_EDITOR_HISTORY ".nmcli-history" - -static void -load_history_cmds(const char *uuid) -{ - GKeyFile *kf; - char * filename; - char ** keys; - char * line; - size_t i; - GError * err = NULL; - - filename = g_build_filename(g_get_home_dir(), NMCLI_EDITOR_HISTORY, NULL); - kf = g_key_file_new(); - if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &err)) { - if (g_error_matches(err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE)) - g_print("Warning: %s parse error: %s\n", filename, err->message); - g_key_file_free(kf); - g_free(filename); - return; - } - keys = g_key_file_get_keys(kf, uuid, NULL, NULL); - for (i = 0; keys && keys[i]; i++) { - line = g_key_file_get_string(kf, uuid, keys[i], NULL); - if (line && *line) - add_history(line); - g_free(line); - } - g_strfreev(keys); - g_key_file_free(kf); - g_free(filename); -} - -static void -save_history_cmds(const char *uuid) -{ - nm_auto_unref_keyfile GKeyFile *kf = NULL; - gs_free_error GError *error = NULL; - gs_free char * filename = NULL; - gs_free char * data = NULL; - HIST_ENTRY ** hist; - gsize len; - gsize i; - - hist = history_list(); - if (!hist) - return; - - filename = g_build_filename(g_get_home_dir(), NMCLI_EDITOR_HISTORY, NULL); - - kf = g_key_file_new(); - - if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) { - if (!g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT) - && !g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) { - g_print("Warning: %s parse error: %s\n", filename, error->message); - return; - } - g_clear_error(&error); - } - - /* Remove previous history group and save new history entries */ - g_key_file_remove_group(kf, uuid, NULL); - for (i = 0; hist[i]; i++) { - char key[100]; - - nm_sprintf_buf(key, "%zd", i); - g_key_file_set_string(kf, uuid, key, hist[i]->line); - } - - /* Write history to file */ - data = g_key_file_to_data(kf, &len, NULL); - if (data) - g_file_set_contents(filename, data, len, NULL); -} - -/*****************************************************************************/ - -static void -editor_show_connection(NMConnection *connection, NmCli *nmc) -{ - nmc->nmc_config_mutable.print_output = NMC_PRINT_PRETTY; - nmc->nmc_config_mutable.multiline_output = TRUE; - nmc->nmc_config_mutable.escape_values = 0; - - nmc_connection_profile_details(connection, nmc); -} - -static void -editor_show_setting(NMSetting *setting, NmCli *nmc) -{ - g_print(_("['%s' setting values]\n"), nm_setting_get_name(setting)); - - nmc->nmc_config_mutable.print_output = NMC_PRINT_NORMAL; - nmc->nmc_config_mutable.multiline_output = TRUE; - nmc->nmc_config_mutable.escape_values = 0; - - setting_details(&nmc->nmc_config, setting, NULL); -} - -typedef enum { - NMC_EDITOR_MAIN_CMD_UNKNOWN = 0, - NMC_EDITOR_MAIN_CMD_GOTO, - NMC_EDITOR_MAIN_CMD_REMOVE, - NMC_EDITOR_MAIN_CMD_SET, - NMC_EDITOR_MAIN_CMD_DESCRIBE, - NMC_EDITOR_MAIN_CMD_PRINT, - NMC_EDITOR_MAIN_CMD_VERIFY, - NMC_EDITOR_MAIN_CMD_SAVE, - NMC_EDITOR_MAIN_CMD_ACTIVATE, - NMC_EDITOR_MAIN_CMD_BACK, - NMC_EDITOR_MAIN_CMD_HELP, - NMC_EDITOR_MAIN_CMD_NMCLI, - NMC_EDITOR_MAIN_CMD_QUIT, -} NmcEditorMainCmd; - -static void -_split_cmd(const char *cmd, char **out_arg0, const char **out_argr) -{ - gs_free char *arg0 = NULL; - const char * argr = NULL; - gsize l; - - NM_SET_OUT(out_arg0, NULL); - NM_SET_OUT(out_argr, NULL); - - if (!cmd) - return; - while (nm_utils_is_separator(cmd[0])) - cmd++; - if (!cmd[0]) - return; - - l = strcspn(cmd, " \t"); - arg0 = g_strndup(cmd, l); - cmd += l; - if (cmd[0]) { - while (nm_utils_is_separator(cmd[0])) - cmd++; - if (cmd[0]) - argr = cmd; - } - - NM_SET_OUT(out_arg0, g_steal_pointer(&arg0)); - NM_SET_OUT(out_argr, argr); -} - -static NmcEditorMainCmd -parse_editor_main_cmd(const char *cmd, char **cmd_arg) -{ - NmcEditorMainCmd editor_cmd = NMC_EDITOR_MAIN_CMD_UNKNOWN; - gs_free char * cmd_arg0 = NULL; - const char * cmd_argr; - - _split_cmd(cmd, &cmd_arg0, &cmd_argr); - if (!cmd_arg0) - goto fail; - - if (matches(cmd_arg0, "goto")) - editor_cmd = NMC_EDITOR_MAIN_CMD_GOTO; - else if (matches(cmd_arg0, "remove")) - editor_cmd = NMC_EDITOR_MAIN_CMD_REMOVE; - else if (matches(cmd_arg0, "set")) - editor_cmd = NMC_EDITOR_MAIN_CMD_SET; - else if (matches(cmd_arg0, "describe")) - editor_cmd = NMC_EDITOR_MAIN_CMD_DESCRIBE; - else if (matches(cmd_arg0, "print")) - editor_cmd = NMC_EDITOR_MAIN_CMD_PRINT; - else if (matches(cmd_arg0, "verify")) - editor_cmd = NMC_EDITOR_MAIN_CMD_VERIFY; - else if (matches(cmd_arg0, "save")) - editor_cmd = NMC_EDITOR_MAIN_CMD_SAVE; - else if (matches(cmd_arg0, "activate")) - editor_cmd = NMC_EDITOR_MAIN_CMD_ACTIVATE; - else if (matches(cmd_arg0, "back")) - editor_cmd = NMC_EDITOR_MAIN_CMD_BACK; - else if (matches(cmd_arg0, "help") || strcmp(cmd_arg0, "?") == 0) - editor_cmd = NMC_EDITOR_MAIN_CMD_HELP; - else if (matches(cmd_arg0, "quit")) - editor_cmd = NMC_EDITOR_MAIN_CMD_QUIT; - else if (matches(cmd_arg0, "nmcli")) - editor_cmd = NMC_EDITOR_MAIN_CMD_NMCLI; - else - goto fail; - - NM_SET_OUT(cmd_arg, g_strdup(cmd_argr)); - return editor_cmd; -fail: - NM_SET_OUT(cmd_arg, NULL); - return NMC_EDITOR_MAIN_CMD_UNKNOWN; -} - -static void -editor_main_usage(void) -{ - g_print("------------------------------------------------------------------------------\n"); - /* TRANSLATORS: do not translate command names and keywords before :: - * However, you should translate terms enclosed in <>. - */ - g_print(_("---[ Main menu ]---\n" - "goto [<setting> | <prop>] :: go to a setting or property\n" - "remove <setting>[.<prop>] | <prop> :: remove setting or reset property value\n" - "set [<setting>.<prop> <value>] :: set property value\n" - "describe [<setting>.<prop>] :: describe property\n" - "print [all | <setting>[.<prop>]] :: print the connection\n" - "verify [all | fix] :: verify the connection\n" - "save [persistent|temporary] :: save the connection\n" - "activate [<ifname>] [/<ap>|<nsp>] :: activate the connection\n" - "back :: go one level up (back)\n" - "help/? [<command>] :: print this help\n" - "nmcli <conf-option> <value> :: nmcli configuration\n" - "quit :: exit nmcli\n")); - g_print("------------------------------------------------------------------------------\n"); -} - -static void -editor_main_help(const char *command) -{ - if (!command) - editor_main_usage(); - else { - /* detailed command descriptions */ - NmcEditorMainCmd cmd = parse_editor_main_cmd(command, NULL); - - switch (cmd) { - case NMC_EDITOR_MAIN_CMD_GOTO: - g_print(_("goto <setting>[.<prop>] | <prop> :: enter setting/property for editing\n\n" - "This command enters into a setting or property for editing it.\n\n" - "Examples: nmcli> goto connection\n" - " nmcli connection> goto secondaries\n" - " nmcli> goto ipv4.addresses\n")); - break; - case NMC_EDITOR_MAIN_CMD_REMOVE: - g_print( - _("remove <setting>[.<prop>] :: remove setting or reset property value\n\n" - "This command removes an entire setting from the connection, or if a property\n" - "is given, resets that property to the default value.\n\n" - "Examples: nmcli> remove wifi-sec\n" - " nmcli> remove eth.mtu\n")); - break; - case NMC_EDITOR_MAIN_CMD_SET: - g_print(_("set [<setting>.<prop> <value>] :: set property value\n\n" - "This command sets property value.\n\n" - "Example: nmcli> set con.id My connection\n")); - break; - case NMC_EDITOR_MAIN_CMD_DESCRIBE: - g_print(_("describe [<setting>.<prop>] :: describe property\n\n" - "Shows property description. You can consult nm-settings(5) " - "manual page to see all NM settings and properties.\n")); - break; - case NMC_EDITOR_MAIN_CMD_PRINT: - g_print(_("print [all] :: print setting or connection values\n\n" - "Shows current property or the whole connection.\n\n" - "Example: nmcli ipv4> print all\n")); - break; - case NMC_EDITOR_MAIN_CMD_VERIFY: - g_print( - _("verify [all | fix] :: verify setting or connection validity\n\n" - "Verifies whether the setting or connection is valid and can be saved later.\n" - "It indicates invalid values on error. Some errors may be fixed automatically\n" - "by 'fix' option.\n\n" - "Examples: nmcli> verify\n" - " nmcli> verify fix\n" - " nmcli bond> verify\n")); - break; - case NMC_EDITOR_MAIN_CMD_SAVE: - g_print( - _("save [persistent|temporary] :: save the connection\n\n" - "Sends the connection profile to NetworkManager that either will save it\n" - "persistently, or will only keep it in memory. 'save' without an argument\n" - "means 'save persistent'.\n" - "Note that once you save the profile persistently those settings are saved\n" - "across reboot or restart. Subsequent changes can also be temporary or\n" - "persistent, but any temporary changes will not persist across reboot or\n" - "restart. If you want to fully remove the persistent connection, the connection\n" - "profile must be deleted.\n")); - break; - case NMC_EDITOR_MAIN_CMD_ACTIVATE: - g_print(_("activate [<ifname>] [/<ap>|<nsp>] :: activate the connection\n\n" - "Activates the connection.\n\n" - "Available options:\n" - "<ifname> - device the connection will be activated on\n" - "/<ap>|<nsp> - AP (Wi-Fi) or NSP (WiMAX) (prepend with / when <ifname> is " - "not specified)\n")); - break; - case NMC_EDITOR_MAIN_CMD_BACK: - g_print(_("back :: go to upper menu level\n\n")); - break; - case NMC_EDITOR_MAIN_CMD_HELP: - g_print(_("help/? [<command>] :: help for the nmcli commands\n\n")); - break; - case NMC_EDITOR_MAIN_CMD_NMCLI: - g_print(_("nmcli [<conf-option> <value>] :: nmcli configuration\n\n" - "Configures nmcli. The following options are available:\n" - "status-line yes | no [default: no]\n" - "save-confirmation yes | no [default: yes]\n" - "show-secrets yes | no [default: no]\n" - "prompt-color <color> | <0-8> [default: 0]\n" - "%s" /* color table description */ - "\n" - "Examples: nmcli> nmcli status-line yes\n" - " nmcli> nmcli save-confirmation no\n" - " nmcli> nmcli prompt-color 3\n"), - " 0 = normal\n" - " 1 = \33[30mblack\33[0m\n" - " 2 = \33[31mred\33[0m\n" - " 3 = \33[32mgreen\33[0m\n" - " 4 = \33[33myellow\33[0m\n" - " 5 = \33[34mblue\33[0m\n" - " 6 = \33[35mmagenta\33[0m\n" - " 7 = \33[36mcyan\33[0m\n" - " 8 = \33[37mwhite\33[0m\n"); - break; - case NMC_EDITOR_MAIN_CMD_QUIT: - g_print(_("quit :: exit nmcli\n\n" - "This command exits nmcli. When the connection being edited " - "is not saved, the user is asked to confirm the action.\n")); - break; - default: - g_print(_("Unknown command: '%s'\n"), command); - break; - } - } -} - -typedef enum { - NMC_EDITOR_SUB_CMD_UNKNOWN = 0, - NMC_EDITOR_SUB_CMD_SET, - NMC_EDITOR_SUB_CMD_ADD, - NMC_EDITOR_SUB_CMD_CHANGE, - NMC_EDITOR_SUB_CMD_REMOVE, - NMC_EDITOR_SUB_CMD_DESCRIBE, - NMC_EDITOR_SUB_CMD_PRINT, - NMC_EDITOR_SUB_CMD_BACK, - NMC_EDITOR_SUB_CMD_HELP, - NMC_EDITOR_SUB_CMD_QUIT -} NmcEditorSubCmd; - -static NmcEditorSubCmd -parse_editor_sub_cmd(const char *cmd, char **cmd_arg) -{ - NmcEditorSubCmd editor_cmd = NMC_EDITOR_SUB_CMD_UNKNOWN; - gs_free char * cmd_arg0 = NULL; - const char * cmd_argr; - - _split_cmd(cmd, &cmd_arg0, &cmd_argr); - if (!cmd_arg0) - goto fail; - - if (matches(cmd_arg0, "set")) - editor_cmd = NMC_EDITOR_SUB_CMD_SET; - else if (matches(cmd_arg0, "add")) - editor_cmd = NMC_EDITOR_SUB_CMD_ADD; - else if (matches(cmd_arg0, "change")) - editor_cmd = NMC_EDITOR_SUB_CMD_CHANGE; - else if (matches(cmd_arg0, "remove")) - editor_cmd = NMC_EDITOR_SUB_CMD_REMOVE; - else if (matches(cmd_arg0, "describe")) - editor_cmd = NMC_EDITOR_SUB_CMD_DESCRIBE; - else if (matches(cmd_arg0, "print")) - editor_cmd = NMC_EDITOR_SUB_CMD_PRINT; - else if (matches(cmd_arg0, "back")) - editor_cmd = NMC_EDITOR_SUB_CMD_BACK; - else if (matches(cmd_arg0, "help") || strcmp(cmd_arg0, "?") == 0) - editor_cmd = NMC_EDITOR_SUB_CMD_HELP; - else if (matches(cmd_arg0, "quit")) - editor_cmd = NMC_EDITOR_SUB_CMD_QUIT; - else - goto fail; - - NM_SET_OUT(cmd_arg, g_strdup(cmd_argr)); - return editor_cmd; -fail: - NM_SET_OUT(cmd_arg, NULL); - return NMC_EDITOR_SUB_CMD_UNKNOWN; -} - -static void -editor_sub_help(void) -{ - g_print("------------------------------------------------------------------------------\n"); - /* TRANSLATORS: do not translate command names and keywords before :: - * However, you should translate terms enclosed in <>. - */ - g_print(_("---[ Property menu ]---\n" - "set [<value>] :: set new value\n" - "add [<value>] :: add new option to the property\n" - "change :: change current value\n" - "remove [<index> | <option>] :: delete the value\n" - "describe :: describe property\n" - "print [setting | connection] :: print property (setting/connection) value(s)\n" - "back :: go to upper level\n" - "help/? [<command>] :: print this help or command description\n" - "quit :: exit nmcli\n")); - g_print("------------------------------------------------------------------------------\n"); -} - -static void -editor_sub_usage(const char *command) -{ - if (!command) - editor_sub_help(); - else { - /* detailed command descriptions */ - NmcEditorSubCmd cmdsub = parse_editor_sub_cmd(command, NULL); - - switch (cmdsub) { - case NMC_EDITOR_SUB_CMD_SET: - g_print(_("set [<value>] :: set new value\n\n" - "This command sets provided <value> to this property\n")); - break; - case NMC_EDITOR_SUB_CMD_ADD: - g_print(_("add [<value>] :: append new value to the property\n\n" - "This command adds provided <value> to this property, if " - "the property is of a container type. For single-valued " - "properties the property value is replaced (same as 'set').\n")); - break; - case NMC_EDITOR_SUB_CMD_CHANGE: - g_print(_("change :: change current value\n\n" - "Displays current value and allows editing it.\n")); - break; - case NMC_EDITOR_SUB_CMD_REMOVE: - g_print(_( - "remove [<value>|<index>|<option name>] :: delete the value\n\n" - "Removes the property value. For single-valued properties, this sets the\n" - "property back to its default value. For container-type properties, this removes\n" - "all the values of that property or you can specify an argument to remove just\n" - "a single item or option. The argument is either a value or index of the item to\n" - "remove, or an option name (for properties with named options).\n\n" - "Examples: nmcli ipv4.dns> remove 8.8.8.8\n" - " nmcli ipv4.dns> remove 2\n" - " nmcli bond.options> remove downdelay\n\n")); - break; - case NMC_EDITOR_SUB_CMD_DESCRIBE: - g_print(_("describe :: describe property\n\n" - "Shows property description. You can consult nm-settings(5) " - "manual page to see all NM settings and properties.\n")); - break; - case NMC_EDITOR_SUB_CMD_PRINT: - g_print(_("print [property|setting|connection] :: print property (setting, " - "connection) value(s)\n\n" - "Shows property value. Providing an argument you can also display " - "values for the whole setting or connection.\n")); - break; - case NMC_EDITOR_SUB_CMD_BACK: - g_print(_("back :: go to upper menu level\n\n")); - break; - case NMC_EDITOR_SUB_CMD_HELP: - g_print(_("help/? [<command>] :: help for nmcli commands\n\n")); - break; - case NMC_EDITOR_SUB_CMD_QUIT: - g_print(_("quit :: exit nmcli\n\n" - "This command exits nmcli. When the connection being edited " - "is not saved, the user is asked to confirm the action.\n")); - break; - default: - g_print(_("Unknown command: '%s'\n"), command); - break; - } - } -} - -/*****************************************************************************/ - -typedef struct { - NMDevice * device; - NMActiveConnection *ac; - guint monitor_id; - NmCli * nmc; -} MonitorACInfo; - -static gboolean nmc_editor_cb_called; -static GError * nmc_editor_error; -static MonitorACInfo *nmc_editor_monitor_ac; - -static void -editor_connection_changed_cb(NMConnection *connection, gboolean *changed) -{ - *changed = TRUE; -} - -/* - * Store 'error' to shared 'nmc_editor_error' and monitoring info to - * 'nmc_editor_monitor_ac' and signal the condition so that - * the 'editor-thread' thread could process that. - */ -static void -set_info_and_signal_editor_thread(GError *error, MonitorACInfo *monitor_ac_info) -{ - nmc_editor_cb_called = TRUE; - nmc_editor_error = error ? g_error_copy(error) : NULL; - nmc_editor_monitor_ac = monitor_ac_info; -} - -static void -add_connection_editor_cb(GObject *client, GAsyncResult *result, gpointer user_data) -{ - gs_unref_object NMRemoteConnection *connection = NULL; - gs_free_error GError *error = NULL; - - connection = nm_client_add_connection2_finish(NM_CLIENT(client), result, NULL, &error); - set_info_and_signal_editor_thread(error, NULL); -} - -static void -update_connection_editor_cb(GObject *connection, GAsyncResult *result, gpointer user_data) -{ - GError *error = NULL; - - nm_remote_connection_commit_changes_finish(NM_REMOTE_CONNECTION(connection), result, &error); - set_info_and_signal_editor_thread(error, NULL); - g_clear_error(&error); -} - -static gboolean -progress_activation_editor_cb(gpointer user_data) -{ - MonitorACInfo * info = (MonitorACInfo *) user_data; - NMDevice * device = info->device; - NMActiveConnection * ac = info->ac; - NMActiveConnectionState ac_state; - NMDeviceState dev_state; - - if (!device || !ac) - goto finish; - - ac_state = nm_active_connection_get_state(ac); - dev_state = nm_device_get_state(device); - - nmc_terminal_show_progress(gettext(nmc_device_state_to_string_with_external(device))); - - if (ac_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED - || dev_state == NM_DEVICE_STATE_ACTIVATED) { - nmc_terminal_erase_line(); - g_print(_("Connection successfully activated (D-Bus active path: %s)\n"), - nm_object_get_path(NM_OBJECT(ac))); - goto finish; - } else if (ac_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED - || dev_state == NM_DEVICE_STATE_FAILED) { - nmc_terminal_erase_line(); - g_print(_("Error: Connection activation failed.\n")); - goto finish; - } - - if (info->nmc->secret_agent) { - NMRemoteConnection *connection; - - connection = nm_active_connection_get_connection(ac); - nm_secret_agent_simple_enable(info->nmc->secret_agent, - nm_object_get_path(NM_OBJECT(connection))); - } - - return G_SOURCE_CONTINUE; - -finish: - nm_g_object_unref(device); - nm_g_object_unref(ac); - info->monitor_id = 0; - return G_SOURCE_REMOVE; -} - -static void -activate_connection_editor_cb(GObject *client, GAsyncResult *result, gpointer user_data) -{ - ActivateConnectionInfo *info = (ActivateConnectionInfo *) user_data; - NMDevice * device = info->device; - const GPtrArray * ac_devs; - MonitorACInfo * monitor_ac_info = NULL; - NMActiveConnection * active; - GError * error = NULL; - - active = nm_client_activate_connection_finish(NM_CLIENT(client), result, &error); - - if (!error) { - if (!device) { - ac_devs = nm_active_connection_get_devices(active); - device = ac_devs->len > 0 ? g_ptr_array_index(ac_devs, 0) : NULL; - } - if (device) { - monitor_ac_info = g_malloc0(sizeof(MonitorACInfo)); - monitor_ac_info->device = g_object_ref(device); - monitor_ac_info->ac = active; - monitor_ac_info->monitor_id = - g_timeout_add(120, progress_activation_editor_cb, monitor_ac_info); - monitor_ac_info->nmc = info->nmc; - } else - g_object_unref(active); - } - - nm_g_object_unref(info->device); - g_free(info); - - set_info_and_signal_editor_thread(error, monitor_ac_info); - g_clear_error(&error); -} - -/*****************************************************************************/ - -static void -print_property_description(NMSetting *setting, const char *prop_name) -{ - char *desc; - - desc = nmc_setting_get_property_desc(setting, prop_name); - if (desc) { - g_print("\n=== [%s] ===\n%s\n", prop_name, desc); - g_free(desc); - } -} - -static void -print_setting_description(NMSetting *setting) -{ - /* Show description of all properties */ - char **all_props; - int i; - - all_props = nmc_setting_get_valid_properties(setting); - g_print(("<<< %s >>>\n"), nm_setting_get_name(setting)); - for (i = 0; all_props && all_props[i]; i++) - print_property_description(setting, all_props[i]); - g_strfreev(all_props); -} - -static void -editor_show_status_line(NMConnection *connection, gboolean dirty, gboolean temp) -{ - NMSettingConnection *s_con; - const char * con_type, *con_id, *con_uuid; - - s_con = nm_connection_get_setting_connection(connection); - g_assert(s_con); - con_type = nm_setting_connection_get_connection_type(s_con); - con_id = nm_connection_get_id(connection); - con_uuid = nm_connection_get_uuid(connection); - - /* TRANSLATORS: status line in nmcli connection editor */ - g_print(_("[ Type: %s | Name: %s | UUID: %s | Dirty: %s | Temp: %s ]\n"), - con_type, - con_id, - con_uuid, - dirty ? _("yes") : _("no"), - temp ? _("yes") : _("no")); -} - -static gboolean -refresh_remote_connection(GWeakRef *weak, NMRemoteConnection **remote) -{ - gboolean previous; - - g_return_val_if_fail(remote, FALSE); - - previous = (*remote != NULL); - if (*remote) - g_object_unref(*remote); - *remote = g_weak_ref_get(weak); - - return (previous && !*remote); -} - -static gboolean -is_connection_dirty(NMConnection *connection, NMRemoteConnection *remote) -{ - return !nm_connection_compare(connection, - remote ? NM_CONNECTION(remote) : NULL, - NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS - | NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP); -} - -static gboolean -confirm_quit(const NmcConfig *nmc_config) -{ - gs_free char *answer = NULL; - - answer = nmc_readline(nmc_config, - _("The connection is not saved. " - "Do you really want to quit? %s"), - prompt_yes_no(FALSE, NULL)); - nm_strstrip(answer); - return (answer && matches(answer, WORD_YES)); -} - -/* - * Submenu for detailed property editing - * Return: TRUE - continue; FALSE - should quit - */ -static gboolean -property_edit_submenu(NmCli * nmc, - NMConnection * connection, - NMRemoteConnection **rem_con, - GWeakRef * rem_con_weak, - NMSetting * curr_setting, - const char * prop_name) -{ - NmcEditorSubCmd cmdsub; - gboolean set_result; - GError * tmp_err = NULL; - gs_free char * prompt = NULL; - gboolean temp_changes; - - /* Set global variable for use in TAB completion */ - nmc_tab_completion.property = prop_name; - - prompt = nmc_colorize(&nmc->nmc_config, - NM_META_COLOR_PROMPT, - "nmcli %s.%s> ", - nm_setting_get_name(curr_setting), - prop_name); - - for (;;) { - gs_free char *cmd_property_user = NULL; - gs_free char *cmd_property_arg = NULL; - gs_free char *prop_val_user = NULL; - gboolean removed; - gboolean dirty; - - /* Get the remote connection again, it may have disappeared */ - removed = refresh_remote_connection(rem_con_weak, rem_con); - if (removed) { - g_print(_("The connection profile has been removed from another client. " - "You may type 'save' in the main menu to restore it.\n")); - } - - /* Connection is dirty? (not saved or differs from the saved) */ - dirty = is_connection_dirty(connection, *rem_con); - temp_changes = *rem_con ? nm_remote_connection_get_unsaved(*rem_con) : TRUE; - if (nmc->editor_status_line) - editor_show_status_line(connection, dirty, temp_changes); - - cmd_property_user = nmc_readline(&nmc->nmc_config, "%s", prompt); - if (!cmd_property_user || !*cmd_property_user) - continue; - g_strstrip(cmd_property_user); - cmdsub = parse_editor_sub_cmd(cmd_property_user, &cmd_property_arg); - - switch (cmdsub) { - case NMC_EDITOR_SUB_CMD_SET: - case NMC_EDITOR_SUB_CMD_ADD: - /* list, arrays,...: SET replaces the whole property value - * ADD adds the new value(s) - * single values: : both SET and ADD sets the new value - */ - if (!cmd_property_arg) { - gs_strfreev char **to_free = NULL; - const char *const *avals; - - avals = nmc_setting_get_property_allowed_values(curr_setting, prop_name, &to_free); - if (avals) { - gs_free char *avals_str = NULL; - - avals_str = nmc_util_strv_for_display(avals, FALSE); - g_print(_("Allowed values for '%s' property: %s\n"), prop_name, avals_str); - } - prop_val_user = nmc_readline(&nmc->nmc_config, _("Enter '%s' value: "), prop_name); - } else - prop_val_user = g_strdup(cmd_property_arg); - - set_result = nmc_setting_set_property(nmc->client, - curr_setting, - prop_name, - (cmdsub == NMC_EDITOR_SUB_CMD_SET) - ? NM_META_ACCESSOR_MODIFIER_SET - : NM_META_ACCESSOR_MODIFIER_ADD, - prop_val_user, - &tmp_err); - if (!set_result) { - g_print(_("Error: failed to set '%s' property: %s\n"), prop_name, tmp_err->message); - g_clear_error(&tmp_err); - } - break; - - case NMC_EDITOR_SUB_CMD_CHANGE: - rl_startup_hook = nmc_rl_set_deftext; - nmc_rl_pre_input_deftext = - nmc_setting_get_property_parsable(curr_setting, prop_name, NULL); - prop_val_user = nmc_readline(&nmc->nmc_config, _("Edit '%s' value: "), prop_name); - - if (!nmc_setting_set_property(nmc->client, - curr_setting, - prop_name, - NM_META_ACCESSOR_MODIFIER_SET, - prop_val_user, - &tmp_err)) { - g_print(_("Error: failed to set '%s' property: %s\n"), prop_name, tmp_err->message); - g_clear_error(&tmp_err); - } - break; - - case NMC_EDITOR_SUB_CMD_REMOVE: - if (!nmc_setting_set_property(nmc->client, - curr_setting, - prop_name, - (cmd_property_arg ? NM_META_ACCESSOR_MODIFIER_DEL - : NM_META_ACCESSOR_MODIFIER_SET), - cmd_property_arg, - &tmp_err)) { - g_print(_("Error: %s\n"), tmp_err->message); - g_clear_error(&tmp_err); - } - break; - - case NMC_EDITOR_SUB_CMD_DESCRIBE: - /* Show property description */ - print_property_description(curr_setting, prop_name); - break; - - case NMC_EDITOR_SUB_CMD_PRINT: - /* Print current connection settings/properties */ - if (cmd_property_arg) { - if (matches(cmd_property_arg, "setting")) - editor_show_setting(curr_setting, nmc); - else if (matches(cmd_property_arg, "connection") - || matches(cmd_property_arg, "all")) - editor_show_connection(connection, nmc); - else - g_print(_("Unknown command argument: '%s'\n"), cmd_property_arg); - } else { - gs_free char *prop_val = NULL; - - prop_val = nmc_setting_get_property(curr_setting, prop_name, NULL); - g_print("%s: %s\n", prop_name, prop_val); - } - break; - - case NMC_EDITOR_SUB_CMD_BACK: - /* Set global variable for use in TAB completion */ - nmc_tab_completion.property = NULL; - return TRUE; - - case NMC_EDITOR_SUB_CMD_HELP: - editor_sub_usage(cmd_property_arg); - break; - - case NMC_EDITOR_SUB_CMD_QUIT: - if (is_connection_dirty(connection, *rem_con)) { - if (confirm_quit(&nmc->nmc_config)) - return FALSE; - } else - return FALSE; - break; - - case NMC_EDITOR_SUB_CMD_UNKNOWN: - default: - g_print(_("Unknown command: '%s'\n"), cmd_property_user); - break; - } - } -} - -/* - * Split 'str' in the following format: [[[setting.]property] [value]] - * and return the components in 'setting', 'property' and 'value' - * Use g_free() to deallocate the returned strings. - */ -static void -split_editor_main_cmd_args(const char *str, char **setting, char **property, char **value) -{ - gs_free char *cmd_arg0 = NULL; - const char * cmd_argr; - const char * s; - - NM_SET_OUT(setting, NULL); - NM_SET_OUT(property, NULL); - NM_SET_OUT(value, NULL); - - _split_cmd(str, &cmd_arg0, &cmd_argr); - if (!cmd_arg0) - return; - - NM_SET_OUT(value, g_strdup(cmd_argr)); - s = strchr(cmd_arg0, '.'); - if (s && s > cmd_arg0) { - NM_SET_OUT(setting, g_strndup(cmd_arg0, s - cmd_arg0)); - NM_SET_OUT(property, g_strdup(&s[1])); - } else { - NM_SET_OUT(property, g_steal_pointer(&cmd_arg0)); - } -} - -static NMSetting * -create_setting_by_name(const char * name, - const NMMetaSettingValidPartItem *const *valid_settings_main, - const NMMetaSettingValidPartItem *const *valid_settings_slave) -{ - const char *setting_name; - NMSetting * setting = NULL; - - /* Get a valid setting name */ - setting_name = check_valid_name(name, valid_settings_main, valid_settings_slave, NULL); - - if (setting_name) { - setting = nm_meta_setting_info_editor_new_setting( - nm_meta_setting_info_editor_find_by_name(setting_name, FALSE), - NM_META_ACCESSOR_SETTING_INIT_TYPE_CLI); - } - return setting; -} - -static const char * -ask_check_setting(const NmcConfig * nmc_config, - const char * arg, - const NMMetaSettingValidPartItem *const *valid_settings_main, - const NMMetaSettingValidPartItem *const *valid_settings_slave, - const char * valid_settings_str) -{ - gs_free char *setting_name_user = NULL; - const char * setting_name; - GError * err = NULL; - - if (!arg) { - g_print(_("Available settings: %s\n"), valid_settings_str); - setting_name_user = nmc_readline(nmc_config, EDITOR_PROMPT_SETTING); - } else - setting_name_user = g_strdup(arg); - - nm_strstrip(setting_name_user); - - if (!(setting_name = check_valid_name(setting_name_user, - valid_settings_main, - valid_settings_slave, - &err))) { - g_print(_("Error: invalid setting name; %s\n"), err->message); - g_clear_error(&err); - } - return setting_name; -} - -static const char * -ask_check_property(const NmcConfig *nmc_config, - const char * arg, - const char ** valid_props, - const char * valid_props_str) -{ - gs_free_error GError *tmp_err = NULL; - gs_free char * prop_name_user = NULL; - const char * prop_name; - - if (!arg) { - g_print(_("Available properties: %s\n"), valid_props_str); - prop_name_user = nmc_readline(nmc_config, EDITOR_PROMPT_PROPERTY); - nm_strstrip(prop_name_user); - } else - prop_name_user = g_strdup(arg); - - prop_name = nmc_string_is_valid(prop_name_user, valid_props, &tmp_err); - if (!prop_name) - g_print(_("Error: property %s\n"), tmp_err->message); - - return prop_name; -} - -/* Copy timestamp from src do dst */ -static void -update_connection_timestamp(NMConnection *src, NMConnection *dst) -{ - NMSettingConnection *s_con_src, *s_con_dst; - - s_con_src = nm_connection_get_setting_connection(src); - s_con_dst = nm_connection_get_setting_connection(dst); - if (s_con_src && s_con_dst) { - guint64 timestamp = nm_setting_connection_get_timestamp(s_con_src); - - g_object_set(s_con_dst, NM_SETTING_CONNECTION_TIMESTAMP, timestamp, NULL); - } -} - -static gboolean -confirm_connection_saving(const NmcConfig *nmc_config, NMConnection *local, NMConnection *remote) -{ - NMSettingConnection *s_con_loc, *s_con_rem; - gboolean ac_local, ac_remote; - gboolean confirmed = TRUE; - - s_con_loc = nm_connection_get_setting_connection(local); - g_assert(s_con_loc); - ac_local = nm_setting_connection_get_autoconnect(s_con_loc); - - if (remote) { - s_con_rem = nm_connection_get_setting_connection(remote); - g_assert(s_con_rem); - ac_remote = nm_setting_connection_get_autoconnect(s_con_rem); - } else - ac_remote = FALSE; - - if (ac_local && !ac_remote) { - gs_free char *answer = NULL; - - answer = nmc_readline(nmc_config, - _("Saving the connection with 'autoconnect=yes'. " - "That might result in an immediate activation of the connection.\n" - "Do you still want to save? %s"), - prompt_yes_no(TRUE, NULL)); - nm_strstrip(answer); - confirmed = (!answer || matches(answer, WORD_YES)); - } - return confirmed; -} - -typedef struct { - guint level; - char * main_prompt; - NMSetting *curr_setting; - char ** valid_props; - char * valid_props_str; -} NmcEditorMenuContext; - -static void -menu_switch_to_level0(const NmcConfig * nmc_config, - NmcEditorMenuContext *menu_ctx, - const char * prompt) -{ - menu_ctx->level = 0; - g_free(menu_ctx->main_prompt); - menu_ctx->main_prompt = nmc_colorize(nmc_config, NM_META_COLOR_PROMPT, "%s", prompt); - menu_ctx->curr_setting = NULL; - g_strfreev(menu_ctx->valid_props); - menu_ctx->valid_props = NULL; - g_free(menu_ctx->valid_props_str); - menu_ctx->valid_props_str = NULL; -} - -static void -menu_switch_to_level1(const NmcConfig * nmc_config, - NmcEditorMenuContext *menu_ctx, - NMSetting * setting, - const char * setting_name) -{ - menu_ctx->level = 1; - g_free(menu_ctx->main_prompt); - menu_ctx->main_prompt = - nmc_colorize(nmc_config, NM_META_COLOR_PROMPT, "nmcli %s> ", setting_name); - menu_ctx->curr_setting = setting; - g_strfreev(menu_ctx->valid_props); - menu_ctx->valid_props = nmc_setting_get_valid_properties(menu_ctx->curr_setting); - g_free(menu_ctx->valid_props_str); - menu_ctx->valid_props_str = g_strjoinv(", ", menu_ctx->valid_props); -} - -static gboolean -editor_save_timeout(gpointer user_data) -{ - gboolean *timeout = user_data; - - *timeout = TRUE; - - return G_SOURCE_REMOVE; -} - -static gboolean -editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_type) -{ - gs_unref_object NMRemoteConnection * rem_con = NULL; - NMSettingConnection * s_con; - NMRemoteConnection * con_tmp; - GWeakRef weak = {{NULL}}; - gboolean removed; - NmcEditorMainCmd cmd; - gboolean cmd_loop = TRUE; - const NMMetaSettingValidPartItem *const *valid_settings_main; - const NMMetaSettingValidPartItem *const *valid_settings_slave; - gs_free char * valid_settings_str = NULL; - const char * s_type = NULL; - gboolean temp_changes; - GError * err1 = NULL; - NmcEditorMenuContext menu_ctx = {0}; - - s_con = nm_connection_get_setting_connection(connection); - if (s_con) - s_type = nm_setting_connection_get_slave_type(s_con); - - valid_settings_main = get_valid_settings_array(connection_type); - valid_settings_slave = nm_meta_setting_info_valid_parts_for_slave_type(s_type, NULL); - - valid_settings_str = get_valid_options_string(valid_settings_main, valid_settings_slave); - g_print(_("You may edit the following settings: %s\n"), valid_settings_str); - - menu_ctx.main_prompt = nmc_colorize(&nmc->nmc_config, NM_META_COLOR_PROMPT, BASE_PROMPT); - - /* Get remote connection */ - con_tmp = nm_client_get_connection_by_uuid(nmc->client, nm_connection_get_uuid(connection)); - g_weak_ref_init(&weak, con_tmp); - rem_con = g_weak_ref_get(&weak); - - while (cmd_loop) { - gs_free char *cmd_user = NULL; - gs_free char *cmd_arg = NULL; - gs_free char *cmd_arg_s = NULL; - gs_free char *cmd_arg_p = NULL; - gs_free char *cmd_arg_v = NULL; - gboolean dirty; - - /* Connection is dirty? (not saved or differs from the saved) */ - dirty = is_connection_dirty(connection, rem_con); - temp_changes = rem_con ? nm_remote_connection_get_unsaved(rem_con) : TRUE; - if (nmc->editor_status_line) - editor_show_status_line(connection, dirty, temp_changes); - - cmd_user = nmc_readline(&nmc->nmc_config, "%s", menu_ctx.main_prompt); - - /* Get the remote connection again, it may have disappeared */ - removed = refresh_remote_connection(&weak, &rem_con); - if (removed) { - g_print(_("The connection profile has been removed from another client. " - "You may type 'save' to restore it.\n")); - } - - if (!cmd_user || !*cmd_user) - continue; - - g_strstrip(cmd_user); - - cmd = parse_editor_main_cmd(cmd_user, &cmd_arg); - - split_editor_main_cmd_args(cmd_arg, &cmd_arg_s, &cmd_arg_p, &cmd_arg_v); - switch (cmd) { - case NMC_EDITOR_MAIN_CMD_SET: - /* Set property value */ - if (!cmd_arg) { - if (menu_ctx.level == 1) { - gs_strfreev char **avals_to_free = NULL; - gs_free char * prop_val_user = NULL; - const char * prop_name; - const char *const *avals; - GError * tmp_err = NULL; - - prop_name = ask_check_property(&nmc->nmc_config, - cmd_arg, - (const char **) menu_ctx.valid_props, - menu_ctx.valid_props_str); - if (!prop_name) - break; - - avals = nmc_setting_get_property_allowed_values(menu_ctx.curr_setting, - prop_name, - &avals_to_free); - if (avals) { - gs_free char *avals_str = NULL; - - avals_str = nmc_util_strv_for_display(avals, FALSE); - g_print(_("Allowed values for '%s' property: %s\n"), prop_name, avals_str); - } - prop_val_user = - nmc_readline(&nmc->nmc_config, _("Enter '%s' value: "), prop_name); - - if (!nmc_setting_set_property(nmc->client, - menu_ctx.curr_setting, - prop_name, - NM_META_ACCESSOR_MODIFIER_ADD, - prop_val_user, - &tmp_err)) { - g_print(_("Error: failed to set '%s' property: %s\n"), - prop_name, - tmp_err->message); - g_clear_error(&tmp_err); - } - } else { - g_print(_("Error: no setting selected; valid are [%s]\n"), valid_settings_str); - g_print(_("use 'goto <setting>' first, or 'set <setting>.<property>'\n")); - } - } else { - gs_free char * prop_name = NULL; - gs_unref_object NMSetting *ss_created = NULL; - NMSetting * ss = NULL; - GError * tmp_err = NULL; - - if (cmd_arg_s) { - /* setting provided as "setting.property" */ - ss = is_setting_valid(connection, - valid_settings_main, - valid_settings_slave, - cmd_arg_s); - if (!ss) { - ss_created = create_setting_by_name(cmd_arg_s, - valid_settings_main, - valid_settings_slave); - ss = ss_created; - if (!ss) { - g_print(_("Error: invalid setting argument '%s'; valid are [%s]\n"), - cmd_arg_s, - valid_settings_str); - break; - } - } - } else { - if (menu_ctx.curr_setting) - ss = menu_ctx.curr_setting; - else { - g_print(_("Error: missing setting for '%s' property\n"), cmd_arg_p); - break; - } - } - - prop_name = is_property_valid(ss, cmd_arg_p, &tmp_err); - if (!prop_name) { - g_print(_("Error: invalid property: %s\n"), tmp_err->message); - g_clear_error(&tmp_err); - break; - } - - /* Ask for value */ - if (!cmd_arg_v) { - gs_strfreev char **avals_to_free = NULL; - const char *const *avals; - - avals = nmc_setting_get_property_allowed_values(ss, prop_name, &avals_to_free); - if (avals) { - gs_free char *avals_str = NULL; - - avals_str = nmc_util_strv_for_display(avals, FALSE); - g_print(_("Allowed values for '%s' property: %s\n"), prop_name, avals_str); - } - cmd_arg_v = nmc_readline(&nmc->nmc_config, _("Enter '%s' value: "), prop_name); - } - - /* setting a value in edit mode "appends". That seems unexpected behavior. */ - if (!nmc_setting_set_property(nmc->client, - ss, - prop_name, - cmd_arg_v ? NM_META_ACCESSOR_MODIFIER_ADD - : NM_META_ACCESSOR_MODIFIER_SET, - cmd_arg_v, - &tmp_err)) { - g_print(_("Error: failed to set '%s' property: %s\n"), - prop_name, - tmp_err->message); - g_clear_error(&tmp_err); - } - - if (ss_created) - nm_connection_add_setting(connection, g_steal_pointer(&ss_created)); - } - break; - - case NMC_EDITOR_MAIN_CMD_GOTO: - /* cmd_arg_s != NULL means 'setting.property' argument */ - if (menu_ctx.level == 0 || cmd_arg_s) { - /* in top level - no setting selected yet */ - const char *setting_name; - NMSetting * setting; - const char *user_arg = cmd_arg_s ?: cmd_arg_p; - - setting_name = ask_check_setting(&nmc->nmc_config, - user_arg, - valid_settings_main, - valid_settings_slave, - valid_settings_str); - if (!setting_name) - break; - - setting = nm_connection_get_setting_by_name(connection, setting_name); - if (!setting) { - const NMMetaSettingInfoEditor *setting_info; - - setting_info = nm_meta_setting_info_editor_find_by_name(setting_name, FALSE); - if (!setting_info) { - g_print(_("Error: unknown setting '%s'\n"), setting_name); - break; - } - - setting = nm_meta_setting_info_editor_new_setting( - setting_info, - NM_META_ACCESSOR_SETTING_INIT_TYPE_CLI); - - if (NM_IS_SETTING_WIRELESS(setting)) - nmc_setting_wireless_connect_handlers(NM_SETTING_WIRELESS(setting)); - else if (NM_IS_SETTING_IP4_CONFIG(setting)) - nmc_setting_ip4_connect_handlers(NM_SETTING_IP_CONFIG(setting)); - else if (NM_IS_SETTING_IP6_CONFIG(setting)) - nmc_setting_ip6_connect_handlers(NM_SETTING_IP_CONFIG(setting)); - else if (NM_IS_SETTING_PROXY(setting)) - nmc_setting_proxy_connect_handlers(NM_SETTING_PROXY(setting)); - - nm_connection_add_setting(connection, setting); - } - /* Set global variable for use in TAB completion */ - nmc_tab_completion.setting = setting; - - /* Switch to level 1 */ - menu_switch_to_level1(&nmc->nmc_config, &menu_ctx, setting, setting_name); - - if (!cmd_arg_s) { - g_print(_("You may edit the following properties: %s\n"), - menu_ctx.valid_props_str); - break; - } - } - if (menu_ctx.level == 1 || cmd_arg_s) { - /* level 1 - setting selected */ - const char *prop_name; - - prop_name = ask_check_property(&nmc->nmc_config, - cmd_arg_p, - (const char **) menu_ctx.valid_props, - menu_ctx.valid_props_str); - if (!prop_name) - break; - - /* submenu - level 2 - editing properties */ - cmd_loop = property_edit_submenu(nmc, - connection, - &rem_con, - &weak, - menu_ctx.curr_setting, - prop_name); - } - break; - - case NMC_EDITOR_MAIN_CMD_REMOVE: - /* Remove setting from connection, or delete value of a property */ - if (!cmd_arg) { - if (menu_ctx.level == 1) { - GError * tmp_err = NULL; - const char *prop_name; - - prop_name = ask_check_property(&nmc->nmc_config, - cmd_arg, - (const char **) menu_ctx.valid_props, - menu_ctx.valid_props_str); - if (!prop_name) - break; - - if (!nmc_setting_set_property(nmc->client, - menu_ctx.curr_setting, - prop_name, - NM_META_ACCESSOR_MODIFIER_SET, - NULL, - &tmp_err)) { - g_print(_("Error: failed to remove value of '%s': %s\n"), - prop_name, - tmp_err->message); - g_clear_error(&tmp_err); - } - } else - g_print(_("Error: no argument given; valid are [%s]\n"), valid_settings_str); - } else { - NMSetting *ss = NULL; - gboolean descr_all; - char * user_s; - - /* cmd_arg_s != NULL means argument is "setting.property" */ - descr_all = !cmd_arg_s && !menu_ctx.curr_setting; - user_s = descr_all ? cmd_arg_p : cmd_arg_s; - if (user_s) { - ss = is_setting_valid(connection, - valid_settings_main, - valid_settings_slave, - user_s); - if (!ss) { - if (check_valid_name(user_s, - valid_settings_main, - valid_settings_slave, - NULL)) { - g_print(_("Setting '%s' is not present in the connection.\n"), user_s); - } else { - g_print(_("Error: invalid setting argument '%s'; valid are [%s]\n"), - user_s, - valid_settings_str); - } - break; - } - } else - ss = menu_ctx.curr_setting; - - if (descr_all) { - gs_free_error GError *local = NULL; - - /* Remove setting from the connection */ - if (!connection_remove_setting(connection, ss, &local)) - g_print("%s\n", local->message); - - if (ss == menu_ctx.curr_setting) { - /* If we removed the setting we are in, go up */ - menu_switch_to_level0(&nmc->nmc_config, &menu_ctx, BASE_PROMPT); - nmc_tab_completion.setting = NULL; /* for TAB completion */ - } - } else { - gs_free char *prop_name = NULL; - gs_free_error GError *tmp_err = NULL; - - prop_name = is_property_valid(ss, cmd_arg_p, &tmp_err); - if (prop_name) { - if (!nmc_setting_set_property(nmc->client, - ss, - prop_name, - NM_META_ACCESSOR_MODIFIER_SET, - NULL, - &tmp_err)) { - g_print(_("Error: failed to remove value of '%s': %s\n"), - prop_name, - tmp_err->message); - } - } else { - NMSetting *s_tmp; - - /* If the string is not a property, try it as a setting */ - s_tmp = is_setting_valid(connection, - valid_settings_main, - valid_settings_slave, - cmd_arg_p); - if (s_tmp) { - gs_free_error GError *local = NULL; - - /* Remove setting from the connection */ - if (!connection_remove_setting(connection, s_tmp, &local)) - g_print("%s\n", local->message); - - /* coverity[copy_paste_error] - suppress Coverity COPY_PASTE_ERROR defect */ - if (ss == menu_ctx.curr_setting) { - /* If we removed the setting we are in, go up */ - menu_switch_to_level0(&nmc->nmc_config, &menu_ctx, BASE_PROMPT); - nmc_tab_completion.setting = NULL; /* for TAB completion */ - } - } else { - g_print(_("Error: %s properties, nor it is a setting name.\n"), - tmp_err->message); - } - } - } - } - break; - - case NMC_EDITOR_MAIN_CMD_DESCRIBE: - /* Print property description */ - if (!cmd_arg) { - if (menu_ctx.level == 1) { - const char *prop_name; - - prop_name = ask_check_property(&nmc->nmc_config, - cmd_arg, - (const char **) menu_ctx.valid_props, - menu_ctx.valid_props_str); - if (!prop_name) - break; - - /* Show property description */ - print_property_description(menu_ctx.curr_setting, prop_name); - } else { - g_print(_("Error: no setting selected; valid are [%s]\n"), valid_settings_str); - g_print(_("use 'goto <setting>' first, or 'describe <setting>.<property>'\n")); - } - } else { - gs_unref_object NMSetting *ss_free = NULL; - NMSetting * ss = NULL; - gboolean descr_all; - char * user_s; - - /* cmd_arg_s != NULL means argument is "setting.property" */ - descr_all = !cmd_arg_s && !menu_ctx.curr_setting; - user_s = descr_all ? cmd_arg_p : cmd_arg_s; - if (user_s) { - ss = is_setting_valid(connection, - valid_settings_main, - valid_settings_slave, - user_s); - if (!ss) { - ss = create_setting_by_name(user_s, - valid_settings_main, - valid_settings_slave); - if (!ss) { - g_print(_("Error: invalid setting argument '%s'; valid are [%s]\n"), - user_s, - valid_settings_str); - break; - } - ss_free = ss; - } - } else - ss = menu_ctx.curr_setting; - - if (!ss) { - g_print(_("Error: no setting selected; valid are [%s]\n"), valid_settings_str); - g_print(_("use 'goto <setting>' first, or 'describe <setting>.<property>'\n")); - } else if (descr_all) { - /* Show description for all properties */ - print_setting_description(ss); - } else { - gs_free_error GError *tmp_err = NULL; - gs_free char * prop_name = NULL; - - prop_name = is_property_valid(ss, cmd_arg_p, &tmp_err); - if (prop_name) { - /* Show property description */ - print_property_description(ss, prop_name); - } else { - /* If the string is not a property, try it as a setting */ - NMSetting *s_tmp; - - s_tmp = is_setting_valid(connection, - valid_settings_main, - valid_settings_slave, - cmd_arg_p); - if (s_tmp) - print_setting_description(s_tmp); - else { - g_print(_("Error: invalid property: %s, " - "neither a valid setting name.\n"), - tmp_err->message); - } - } - } - } - break; - - case NMC_EDITOR_MAIN_CMD_PRINT: - /* Print current connection settings/properties */ - if (cmd_arg) { - if (nm_streq(cmd_arg, "all")) - editor_show_connection(connection, nmc); - else { - NMSetting *ss = NULL; - gboolean whole_setting; - char * user_s; - - /* cmd_arg_s != NULL means argument is "setting.property" */ - whole_setting = !cmd_arg_s && !menu_ctx.curr_setting; - user_s = whole_setting ? cmd_arg_p : cmd_arg_s; - if (user_s) { - const char *s_name; - - s_name = check_valid_name(user_s, - valid_settings_main, - valid_settings_slave, - NULL); - if (!s_name) { - g_print(_("Error: unknown setting: '%s'\n"), user_s); - break; - } - ss = nm_connection_get_setting_by_name(connection, s_name); - if (!ss) { - g_print(_("Error: '%s' setting not present in the connection\n"), - s_name); - break; - } - } else - ss = menu_ctx.curr_setting; - - if (whole_setting) { - /* Print the whole setting */ - editor_show_setting(ss, nmc); - } else { - gs_free char *prop_name = NULL; - GError * err = NULL; - - prop_name = is_property_valid(ss, cmd_arg_p, &err); - if (prop_name) { - /* Print one property */ - gs_free char *prop_val = NULL; - - prop_val = nmc_setting_get_property(ss, prop_name, NULL); - g_print("%s.%s: %s\n", nm_setting_get_name(ss), prop_name, prop_val); - } else { - /* If the string is not a property, try it as a setting */ - NMSetting *s_tmp; - s_tmp = is_setting_valid(connection, - valid_settings_main, - valid_settings_slave, - cmd_arg_p); - if (s_tmp) { - /* Print the whole setting */ - editor_show_setting(s_tmp, nmc); - } else - g_print(_("Error: invalid property: %s%s\n"), - err->message, - cmd_arg_s ? "" : _(", neither a valid setting name")); - g_clear_error(&err); - } - } - } - } else { - if (menu_ctx.curr_setting) - editor_show_setting(menu_ctx.curr_setting, nmc); - else - editor_show_connection(connection, nmc); - } - break; - - case NMC_EDITOR_MAIN_CMD_VERIFY: - /* Verify current setting or the whole connection */ - if (cmd_arg && strcmp(cmd_arg, "all") && strcmp(cmd_arg, "fix")) { - g_print(_("Invalid verify option: %s\n"), cmd_arg); - break; - } - - if (menu_ctx.curr_setting && (!cmd_arg || strcmp(cmd_arg, "all") != 0)) { - gs_free_error GError *tmp_err = NULL; - - nm_setting_verify(menu_ctx.curr_setting, NULL, &tmp_err); - g_print(_("Verify setting '%s': %s\n"), - nm_setting_get_name(menu_ctx.curr_setting), - tmp_err ? tmp_err->message : "OK"); - } else { - gs_free_error GError *tmp_err = NULL; - gboolean fixed = TRUE; - gboolean modified; - gboolean valid; - - valid = nm_connection_verify(connection, &tmp_err); - if (!valid && nm_streq0(cmd_arg, "fix")) { - /* Try to fix normalizable errors */ - g_clear_error(&tmp_err); - fixed = nm_connection_normalize(connection, NULL, &modified, &tmp_err); - } - g_print(_("Verify connection: %s\n"), tmp_err ? tmp_err->message : "OK"); - if (!fixed) - g_print(_("The error cannot be fixed automatically.\n")); - } - break; - - case NMC_EDITOR_MAIN_CMD_SAVE: - /* Save the connection */ - if (nm_connection_verify(connection, &err1)) { - gboolean temporary = FALSE; - gboolean connection_changed; - nm_auto_unref_gsource GSource *source = NULL; - gboolean timeout = FALSE; - gulong handler_id = 0; - - /* parse argument */ - if (cmd_arg) { - if (matches(cmd_arg, "temporary")) - temporary = TRUE; - else if (matches(cmd_arg, "persistent")) - temporary = FALSE; - else { - g_print(_("Error: invalid argument '%s'\n"), cmd_arg); - break; - } - } - - /* Ask for save confirmation if the connection changes to autoconnect=yes */ - if (nmc->editor_save_confirmation) { - if (!confirm_connection_saving(&nmc->nmc_config, - connection, - NM_CONNECTION(rem_con))) - break; - } - - if (!rem_con) { - add_connection(nmc->client, - connection, - temporary, - add_connection_editor_cb, - NULL); - connection_changed = TRUE; - } else { - /* Save/update already saved (existing) connection */ - nm_connection_replace_settings_from_connection(NM_CONNECTION(rem_con), - connection); - update_connection(rem_con, temporary, update_connection_editor_cb, NULL); - - handler_id = g_signal_connect(rem_con, - NM_CONNECTION_CHANGED, - G_CALLBACK(editor_connection_changed_cb), - &connection_changed); - connection_changed = FALSE; - } - - source = g_timeout_source_new(10 * NM_UTILS_MSEC_PER_SEC); - g_source_set_callback(source, editor_save_timeout, &timeout, NULL); - g_source_attach(source, g_main_loop_get_context(loop)); - - while (!nmc_editor_cb_called && !timeout) - g_main_context_iteration(NULL, TRUE); - - if (!nmc_editor_error) { - while (!connection_changed && !timeout) - g_main_context_iteration(NULL, TRUE); - } - - if (handler_id) - g_signal_handler_disconnect(rem_con, handler_id); - g_source_destroy(source); - - if (nmc_editor_error) { - g_print(_("Error: Failed to save '%s' (%s) connection: %s\n"), - nm_connection_get_id(connection), - nm_connection_get_uuid(connection), - nmc_editor_error->message); - g_error_free(nmc_editor_error); - } else if (timeout) { - g_print(_("Error: Timeout saving '%s' (%s) connection\n"), - nm_connection_get_id(connection), - nm_connection_get_uuid(connection)); - } else { - g_print(!rem_con ? _("Connection '%s' (%s) successfully saved.\n") - : _("Connection '%s' (%s) successfully updated.\n"), - nm_connection_get_id(connection), - nm_connection_get_uuid(connection)); - - con_tmp = nm_client_get_connection_by_uuid(nmc->client, - nm_connection_get_uuid(connection)); - g_weak_ref_set(&weak, con_tmp); - refresh_remote_connection(&weak, &rem_con); - - /* Replace local connection with the remote one to be sure they are equal. - * This mitigates problems with plugins not preserving some properties or - * adding ipv{4,6} settings when not present. - */ - if (con_tmp) { - gs_free char *s_name = NULL; - - if (menu_ctx.curr_setting) - s_name = g_strdup(nm_setting_get_name(menu_ctx.curr_setting)); - - /* Update settings and secrets in the local connection */ - nm_connection_replace_settings_from_connection(connection, - NM_CONNECTION(con_tmp)); - update_secrets_in_connection(con_tmp, connection); - - /* Also update setting for menu context and TAB-completion */ - menu_ctx.curr_setting = - s_name ? nm_connection_get_setting_by_name(connection, s_name) : NULL; - nmc_tab_completion.setting = menu_ctx.curr_setting; - } - } - - nmc_editor_cb_called = FALSE; - nmc_editor_error = NULL; - } else { - g_print(_("Error: connection verification failed: %s\n"), - err1 ? err1->message : _("(unknown error)")); - g_print(_("You may try running 'verify fix' to fix errors.\n")); - } - - g_clear_error(&err1); - break; - - case NMC_EDITOR_MAIN_CMD_ACTIVATE: - { - GError * tmp_err = NULL; - const char *ifname = cmd_arg_p; - const char *ap_nsp = cmd_arg_v; - - /* When only AP/NSP is specified it is prepended with '/' */ - if (!cmd_arg_v) { - if (ifname && ifname[0] == '/') { - ap_nsp = ifname + 1; - ifname = NULL; - } - } else - ap_nsp = ap_nsp && ap_nsp[0] == '/' ? ap_nsp + 1 : ap_nsp; - - if (is_connection_dirty(connection, rem_con)) { - /* TRANSLATORS: do not translate 'save', leave it as it is */ - g_print(_("Error: connection is not saved. Type 'save' first.\n")); - break; - } - if (!nm_connection_verify(NM_CONNECTION(rem_con), &tmp_err)) { - g_print(_("Error: connection is not valid: %s\n"), tmp_err->message); - g_clear_error(&tmp_err); - break; - } - - nmc->nowait_flag = FALSE; - nmc->should_wait++; - nmc->nmc_config_mutable.print_output = NMC_PRINT_PRETTY; - if (!nmc_activate_connection(nmc, - NM_CONNECTION(rem_con), - ifname, - ap_nsp, - ap_nsp, - NULL, - activate_connection_editor_cb, - &tmp_err)) { - g_print(_("Error: Cannot activate connection: %s.\n"), tmp_err->message); - g_clear_error(&tmp_err); - break; - } - - while (!nmc_editor_cb_called) - g_main_context_iteration(NULL, TRUE); - - if (nmc_editor_error) { - g_print(_("Error: Failed to activate '%s' (%s) connection: %s\n"), - nm_connection_get_id(connection), - nm_connection_get_uuid(connection), - nmc_editor_error->message); - g_error_free(nmc_editor_error); - } else { - nmc_readline(&nmc->nmc_config, - _("Monitoring connection activation (press any key to continue)\n")); - } - - if (nmc_editor_monitor_ac) { - if (nmc_editor_monitor_ac->monitor_id) - g_source_remove(nmc_editor_monitor_ac->monitor_id); - g_free(nmc_editor_monitor_ac); - } - nmc_editor_cb_called = FALSE; - nmc_editor_error = NULL; - nmc_editor_monitor_ac = NULL; - - /* Update timestamp in local connection */ - update_connection_timestamp(NM_CONNECTION(rem_con), connection); - - } break; - - case NMC_EDITOR_MAIN_CMD_BACK: - /* Go back (up) an the menu */ - if (menu_ctx.level == 1) { - menu_switch_to_level0(&nmc->nmc_config, &menu_ctx, BASE_PROMPT); - nmc_tab_completion.setting = NULL; /* for TAB completion */ - } - break; - - case NMC_EDITOR_MAIN_CMD_HELP: - /* Print command help */ - editor_main_help(cmd_arg); - break; - - case NMC_EDITOR_MAIN_CMD_NMCLI: - if (cmd_arg_p && matches(cmd_arg_p, "status-line")) { - GError * tmp_err = NULL; - gboolean bb; - if (!nmc_string_to_bool(cmd_arg_v ? g_strstrip(cmd_arg_v) : "", &bb, &tmp_err)) { - g_print(_("Error: status-line: %s\n"), tmp_err->message); - g_clear_error(&tmp_err); - } else - nmc->editor_status_line = bb; - } else if (cmd_arg_p && matches(cmd_arg_p, "save-confirmation")) { - GError * tmp_err = NULL; - gboolean bb; - if (!nmc_string_to_bool(cmd_arg_v ? g_strstrip(cmd_arg_v) : "", &bb, &tmp_err)) { - g_print(_("Error: save-confirmation: %s\n"), tmp_err->message); - g_clear_error(&tmp_err); - } else - nmc->editor_save_confirmation = bb; - } else if (cmd_arg_p && matches(cmd_arg_p, "show-secrets")) { - GError * tmp_err = NULL; - gboolean bb; - if (!nmc_string_to_bool(cmd_arg_v ? g_strstrip(cmd_arg_v) : "", &bb, &tmp_err)) { - g_print(_("Error: show-secrets: %s\n"), tmp_err->message); - g_clear_error(&tmp_err); - } else - nmc->nmc_config_mutable.show_secrets = bb; - } else if (cmd_arg_p && matches(cmd_arg_p, "prompt-color")) { - g_debug("Ignoring erroneous --prompt-color argument. Use terminal-colors.d(5) to " - "set the prompt color.\n"); - } else if (!cmd_arg_p) { - g_print(_("Current nmcli configuration:\n")); - g_print("status-line: %s\n" - "save-confirmation: %s\n" - "show-secrets: %s\n", - nmc->editor_status_line ? "yes" : "no", - nmc->editor_save_confirmation ? "yes" : "no", - nmc->nmc_config.show_secrets ? "yes" : "no"); - } else - g_print(_("Invalid configuration option '%s'; allowed [%s]\n"), - cmd_arg_v ?: "", - "status-line, save-confirmation, show-secrets"); - - break; - - case NMC_EDITOR_MAIN_CMD_QUIT: - if (is_connection_dirty(connection, rem_con)) { - if (confirm_quit(&nmc->nmc_config)) - cmd_loop = FALSE; /* quit command loop */ - } else - cmd_loop = FALSE; /* quit command loop */ - break; - - case NMC_EDITOR_MAIN_CMD_UNKNOWN: - default: - g_print(_("Unknown command: '%s'\n"), cmd_user); - break; - } - } - - g_free(menu_ctx.main_prompt); - g_strfreev(menu_ctx.valid_props); - g_free(menu_ctx.valid_props_str); - g_weak_ref_clear(&weak); - - quit(); - - /* Save history file */ - save_history_cmds(nm_connection_get_uuid(connection)); - - return TRUE; -} - -static const char * -get_ethernet_device_name(NmCli *nmc) -{ - const GPtrArray *devices; - guint i; - - devices = nm_client_get_devices(nmc->client); - for (i = 0; i < devices->len; i++) { - NMDevice *dev = g_ptr_array_index(devices, i); - if (NM_IS_DEVICE_ETHERNET(dev)) - return nm_device_get_iface(dev); - } - return NULL; -} - -static void -editor_init_new_connection(NmCli *nmc, NMConnection *connection, const char *slave_type) -{ - NMSetting * setting, *base_setting; - NMSettingConnection *s_con; - const char * con_type; - - s_con = nm_connection_get_setting_connection(connection); - g_assert(s_con); - con_type = nm_setting_connection_get_connection_type(s_con); - - /* Initialize new connection according to its type using sensible defaults. */ - - nmc_setting_connection_connect_handlers(s_con, connection); - - if (slave_type) { - const char *dev_ifname = get_ethernet_device_name(nmc); - - /* For bond/team/bridge slaves add 'wired' setting */ - setting = nm_setting_wired_new(); - nm_connection_add_setting(connection, setting); - - g_object_set(s_con, - NM_SETTING_CONNECTION_TYPE, - NM_SETTING_WIRED_SETTING_NAME, - NM_SETTING_CONNECTION_MASTER, - dev_ifname ?: "eth0", - NM_SETTING_CONNECTION_SLAVE_TYPE, - slave_type, - NULL); - } else { - const NMMetaSettingInfoEditor *setting_info; - - /* Add a "base" setting to the connection by default */ - setting_info = nm_meta_setting_info_editor_find_by_name(con_type, FALSE); - if (!setting_info) - return; - base_setting = - nm_meta_setting_info_editor_new_setting(setting_info, - NM_META_ACCESSOR_SETTING_INIT_TYPE_CLI); - nm_connection_add_setting(connection, base_setting); - - set_default_interface_name(nmc, s_con); - - /* Set sensible initial VLAN values */ - if (g_strcmp0(con_type, NM_SETTING_VLAN_SETTING_NAME) == 0) { - const char *dev_ifname = get_ethernet_device_name(nmc); - - g_object_set(NM_SETTING_VLAN(base_setting), - NM_SETTING_VLAN_PARENT, - dev_ifname ?: "eth0", - NULL); - } - - setting = nm_meta_setting_info_editor_new_setting( - &nm_meta_setting_infos_editor[NM_META_SETTING_TYPE_IP4_CONFIG], - NM_META_ACCESSOR_SETTING_INIT_TYPE_CLI); - nm_connection_add_setting(connection, setting); - - setting = nm_meta_setting_info_editor_new_setting( - &nm_meta_setting_infos_editor[NM_META_SETTING_TYPE_IP6_CONFIG], - NM_META_ACCESSOR_SETTING_INIT_TYPE_CLI); - nm_connection_add_setting(connection, setting); - - setting = nm_meta_setting_info_editor_new_setting( - &nm_meta_setting_infos_editor[NM_META_SETTING_TYPE_PROXY], - NM_META_ACCESSOR_SETTING_INIT_TYPE_CLI); - nm_connection_add_setting(connection, setting); - } -} - -static void -editor_init_existing_connection(NMConnection *connection) -{ - NMSettingIPConfig * s_ip4, *s_ip6; - NMSettingProxy * s_proxy; - NMSettingWireless * s_wireless; - NMSettingConnection *s_con; - - /* FIXME: this approach of connecting handlers to do something is fundamentally - * flawed. See the comment in nmc_setting_ip6_connect_handlers(). */ - - s_ip4 = nm_connection_get_setting_ip4_config(connection); - s_ip6 = nm_connection_get_setting_ip6_config(connection); - s_proxy = nm_connection_get_setting_proxy(connection); - s_wireless = nm_connection_get_setting_wireless(connection); - s_con = nm_connection_get_setting_connection(connection); - - if (s_ip4) - nmc_setting_ip4_connect_handlers(s_ip4); - if (s_ip6) - nmc_setting_ip6_connect_handlers(s_ip6); - if (s_proxy) - nmc_setting_proxy_connect_handlers(s_proxy); - if (s_wireless) - nmc_setting_wireless_connect_handlers(s_wireless); - if (s_con) - nmc_setting_connection_connect_handlers(s_con, connection); -} - -static void -nmc_complete_connection_type(const char *prefix) -{ - guint i; - - for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { - const NMMetaSettingInfoEditor *setting_info = &nm_meta_setting_infos_editor[i]; - - if (!*prefix || matches(prefix, setting_info->general->setting_name)) - g_print("%s\n", setting_info->general->setting_name); - if (setting_info->alias && (!*prefix || matches(prefix, setting_info->alias))) - g_print("%s\n", setting_info->alias); - } -} - -static void -do_connection_edit(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - const GPtrArray *connections; - gs_unref_object NMConnection *connection = NULL; - NMSettingConnection * s_con; - const char * connection_type; - const char * type = NULL; - const char * con_name = NULL; - const char * con = NULL; - const char * con_id = NULL; - const char * con_uuid = NULL; - const char * con_path = NULL; - const char * con_filename = NULL; - const char * selector = NULL; - gs_free_error GError *error = NULL; - GError * err1 = NULL; - nmc_arg_t exp_args[] = {{"type", TRUE, &type, FALSE}, - {"con-name", TRUE, &con_name, FALSE}, - {"id", TRUE, &con_id, FALSE}, - {"uuid", TRUE, &con_uuid, FALSE}, - {"path", TRUE, &con_path, FALSE}, - {"filename", TRUE, &con_filename, FALSE}, - {NULL}}; - - next_arg(nmc, &argc, &argv, NULL); - if (argc == 1 && nmc->complete) - nmc_complete_strings(*argv, "type", "con-name", "id", "uuid", "path", "filename"); - - nmc->return_value = NMC_RESULT_SUCCESS; - - if (argc == 1) - con = *argv; - else { - if (!nmc_parse_args(exp_args, TRUE, &argc, &argv, &error)) { - g_string_assign(nmc->return_text, error->message); - nmc->return_value = error->code; - return; - } - } - - /* Setup some readline completion stuff */ - /* Set a pointer to an alternative function to create matches */ - rl_attempted_completion_function = nmcli_editor_tab_completion; - /* Use ' ' and '.' as word break characters */ - rl_completer_word_break_characters = ". "; - - connections = nm_client_get_connections(nmc->client); - - if (!con) { - if (con_id && !con_uuid && !con_path && !con_filename) { - con = con_id; - selector = "id"; - } else if (con_uuid && !con_id && !con_path && !con_filename) { - con = con_uuid; - selector = "uuid"; - } else if (con_path && !con_id && !con_uuid && !con_filename) { - con = con_path; - selector = "path"; - } else if (con_filename && !con_path && !con_id && !con_uuid) { - con = con_filename; - selector = "filename"; - } else if (!con_path && !con_id && !con_uuid && !con_filename) { - /* no-op */ - } else { - g_string_printf( - nmc->return_text, - _("Error: only one of 'id', 'filename', uuid, or 'path' can be provided.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - } - - if (con) { - /* Existing connection */ - NMConnection *found_con; - - found_con = nmc_find_connection(connections, selector, con, NULL, nmc->complete); - if (nmc->complete) - return; - - if (!found_con) { - g_string_printf(nmc->return_text, _("Error: Unknown connection '%s'."), con); - nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; - return; - } - - /* Duplicate the connection and use that so that we need not - * differentiate existing vs. new later - */ - connection = nm_simple_connection_new_clone(found_con); - - /* Merge secrets into the connection */ - update_secrets_in_connection(NM_REMOTE_CONNECTION(found_con), connection); - - s_con = nm_connection_get_setting_connection(connection); - connection_type = nm_setting_connection_get_connection_type(s_con); - - if (type) - g_print(_("Warning: editing existing connection '%s'; 'type' argument is ignored\n"), - nm_connection_get_id(connection)); - if (con_name) - g_print( - _("Warning: editing existing connection '%s'; 'con-name' argument is ignored\n"), - nm_connection_get_id(connection)); - - /* Load previously saved history commands for the connection */ - load_history_cmds(nm_connection_get_uuid(connection)); - - editor_init_existing_connection(connection); - } else { - const char * slave_type = NULL; - gs_free char *uuid = NULL; - gs_free char *default_name = NULL; - gs_free char *tmp_str = NULL; - - /* New connection */ - if (nmc->complete) { - if (type && argc == 0) - nmc_complete_connection_type(type); - return; - } - - connection_type = check_valid_name_toplevel(type, &slave_type, &err1); - tmp_str = get_valid_options_string_toplevel(); - - while (!connection_type) { - gs_free char *type_ask = NULL; - - if (!type) - g_print(_("Valid connection types: %s\n"), tmp_str); - else - g_print(_("Error: invalid connection type; %s\n"), err1->message); - g_clear_error(&err1); - - type_ask = nmc_readline(&nmc->nmc_config, EDITOR_PROMPT_CON_TYPE); - type = type_ask = nm_strstrip(type_ask); - connection_type = check_valid_name_toplevel(type_ask, &slave_type, &err1); - } - nm_clear_g_free(&tmp_str); - - connection = nm_simple_connection_new(); - - s_con = (NMSettingConnection *) nm_setting_connection_new(); - uuid = nm_utils_uuid_generate(); - if (con_name) - default_name = g_strdup(con_name); - else { - default_name = - nmc_unique_connection_name(connections, - get_name_alias_toplevel(connection_type, NULL)); - } - - g_object_set(s_con, - NM_SETTING_CONNECTION_ID, - default_name, - NM_SETTING_CONNECTION_UUID, - uuid, - NM_SETTING_CONNECTION_TYPE, - connection_type, - NULL); - nm_connection_add_setting(connection, NM_SETTING(s_con)); - - /* Initialize the new connection so that it is valid from the start */ - editor_init_new_connection(nmc, connection, slave_type); - } - - /* nmcli runs the editor */ - nmc->nmc_config_mutable.in_editor = TRUE; - - g_print("\n"); - g_print(_("===| nmcli interactive connection editor |===")); - g_print("\n\n"); - if (con) - g_print(_("Editing existing '%s' connection: '%s'"), connection_type, con); - else - g_print(_("Adding a new '%s' connection"), connection_type); - g_print("\n\n"); - /* TRANSLATORS: do not translate 'help', leave it as it is */ - g_print(_("Type 'help' or '?' for available commands.")); - g_print("\n"); - /* TRANSLATORS: do not translate 'print', leave it as it is */ - g_print(_("Type 'print' to show all the connection properties.")); - g_print("\n"); - /* TRANSLATORS: do not translate 'describe', leave it as it is */ - g_print(_("Type 'describe [<setting>.<prop>]' for detailed property description.")); - g_print("\n\n"); - - nmc_tab_completion.nmc = nmc; - nmc_tab_completion.con_type = g_strdup(connection_type); - nmc_tab_completion.connection = connection; - - /* Run menu loop */ - editor_menu_main(nmc, connection, connection_type); - - nmc_tab_completion.nmc = NULL; - nm_clear_g_free(&nmc_tab_completion.con_type); - nmc_tab_completion.connection = NULL; - - return; -} - -static void -modify_connection_cb(GObject *connection, GAsyncResult *result, gpointer user_data) -{ - NmCli * nmc = user_data; - gs_free_error GError *error = NULL; - - if (!nm_remote_connection_commit_changes_finish(NM_REMOTE_CONNECTION(connection), - result, - &error)) { - g_string_printf(nmc->return_text, - _("Error: Failed to modify connection '%s': %s"), - nm_connection_get_id(NM_CONNECTION(connection)), - error->message); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - } else { - if (nmc->nmc_config.print_output == NMC_PRINT_PRETTY) { - g_print(_("Connection '%s' (%s) successfully modified.\n"), - nm_connection_get_id(NM_CONNECTION(connection)), - nm_connection_get_uuid(NM_CONNECTION(connection))); - } - } - quit(); -} - -static void -do_connection_modify(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - NMConnection * connection = NULL; - NMRemoteConnection *rc = NULL; - gs_free_error GError *error = NULL; - gboolean temporary = FALSE; - - if (next_arg(nmc, &argc, &argv, "--temporary", NULL) > 0) { - temporary = TRUE; - next_arg(nmc, &argc, &argv, NULL); - } - - connection = get_connection(nmc, &argc, &argv, NULL, NULL, NULL, &error); - if (!connection) { - g_string_printf(nmc->return_text, _("Error: %s."), error->message); - nmc->return_value = error->code; - return; - } - - rc = nm_client_get_connection_by_uuid(nmc->client, nm_connection_get_uuid(connection)); - if (!rc) { - g_string_printf(nmc->return_text, - _("Error: Unknown connection '%s'."), - nm_connection_get_uuid(connection)); - nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; - return; - } - - if (!nmc_process_connection_properties(nmc, NM_CONNECTION(rc), &argc, &argv, TRUE, &error)) { - g_string_assign(nmc->return_text, error->message); - nmc->return_value = error->code; - return; - } - - if (nmc->complete) - return; - - update_connection(rc, temporary, modify_connection_cb, nmc); - nmc->should_wait++; -} - -static void -clone_connection_cb(GObject *client, GAsyncResult *result, gpointer user_data) -{ - nm_auto_free_add_connection_info AddConnectionInfo *info = user_data; - NmCli * nmc = info->nmc; - gs_unref_object NMRemoteConnection *connection = NULL; - gs_free_error GError *error = NULL; - - connection = nm_client_add_connection2_finish(NM_CLIENT(client), result, NULL, &error); - if (error) { - g_string_printf(nmc->return_text, - _("Error: Failed to add '%s' connection: %s"), - info->new_id, - error->message); - nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; - } else { - g_print(_("%s (%s) cloned as %s (%s).\n"), - info->orig_id, - info->orig_uuid, - nm_connection_get_id(NM_CONNECTION(connection)), - nm_connection_get_uuid(NM_CONNECTION(connection))); - } - - quit(); -} - -static void -do_connection_clone(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - NMConnection * connection = NULL; - gs_unref_object NMConnection *new_connection = NULL; - const char * new_name; - gs_free char * new_name_free = NULL; - gs_free char * uuid = NULL; - gboolean temporary = FALSE; - gs_strfreev char ** arg_arr = NULL; - int arg_num; - const char *const ** argv_ptr; - int * argc_ptr; - GError * error = NULL; - - if (next_arg(nmc, &argc, &argv, "--temporary", NULL) > 0) { - temporary = TRUE; - next_arg(nmc, &argc, &argv, NULL); - } - - argv_ptr = &argv; - argc_ptr = &argc; - - if (argc == 0 && nmc->ask) { - gs_free char *line = NULL; - - /* nmc_do_cmd() should not call this with argc=0. */ - g_assert(!nmc->complete); - - line = nmc_readline(&nmc->nmc_config, PROMPT_CONNECTION); - nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num); - argv_ptr = (const char *const **) &arg_arr; - argc_ptr = &arg_num; - } - - connection = get_connection(nmc, argc_ptr, argv_ptr, NULL, NULL, NULL, &error); - if (!connection) { - g_string_printf(nmc->return_text, _("Error: %s."), error->message); - nmc->return_value = error->code; - return; - } - - if (nmc->complete) - return; - - if (argv[0]) - new_name = *argv; - else if (nmc->ask) { - new_name = new_name_free = nmc_readline(&nmc->nmc_config, _("New connection name: ")); - } else { - g_string_printf(nmc->return_text, _("Error: <new name> argument is missing.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - if (next_arg(nmc->ask ? NULL : nmc, argc_ptr, argv_ptr, NULL) == 0) { - g_string_printf(nmc->return_text, _("Error: unknown extra argument: '%s'."), *argv); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - new_connection = nm_simple_connection_new_clone(connection); - - uuid = nm_utils_uuid_generate(); - g_object_set(nm_connection_get_setting_connection(new_connection), - NM_SETTING_CONNECTION_ID, - new_name, - NM_SETTING_CONNECTION_UUID, - uuid, - NULL); - - update_secrets_in_connection(NM_REMOTE_CONNECTION(connection), new_connection); - - add_connection(nmc->client, - new_connection, - temporary, - clone_connection_cb, - _add_connection_info_new(nmc, connection, new_connection)); - nmc->should_wait++; -} - -static void -delete_cb(GObject *con, GAsyncResult *result, gpointer user_data) -{ - ConnectionCbInfo *info = (ConnectionCbInfo *) user_data; - GError * error = NULL; - - if (!nm_remote_connection_delete_finish(NM_REMOTE_CONNECTION(con), result, &error)) { - if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - return; - g_string_printf(info->nmc->return_text, _("Error: not all connections deleted.")); - g_printerr(_("Error: Connection deletion failed: %s\n"), error->message); - g_error_free(error); - info->nmc->return_value = NMC_RESULT_ERROR_CON_DEL; - connection_cb_info_finish(info, con); - } else { - if (info->nmc->nowait_flag) - connection_cb_info_finish(info, con); - } -} - -static void -do_connection_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - NMConnection * connection; - ConnectionCbInfo * info = NULL; - gs_strfreev char ** arg_arr = NULL; - const char *const * arg_ptr; - guint i; - int arg_num; - nm_auto_free_gstring GString *invalid_cons = NULL; - gs_unref_ptrarray GPtrArray *found_cons = NULL; - GError * error = NULL; - - if (nmc->timeout == -1) - nmc->timeout = 10; - - next_arg(nmc, &argc, &argv, NULL); - arg_ptr = argv; - arg_num = argc; - - if (argc == 0) { - if (nmc->ask) { - gs_free char *line = NULL; - - /* nmc_do_cmd() should not call this with argc=0. */ - g_assert(!nmc->complete); - - line = nmc_readline(&nmc->nmc_config, PROMPT_CONNECTIONS); - nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num); - arg_ptr = (const char *const *) arg_arr; - } - if (arg_num == 0) { - g_string_printf(nmc->return_text, _("Error: No connection specified.")); - nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; - goto finish; - } - } - - while (arg_num > 0) { - const char *cur_selector, *cur_value; - - connection = - get_connection(nmc, &arg_num, &arg_ptr, &cur_selector, &cur_value, &found_cons, &error); - if (!connection) { - if (!nmc->complete) - g_printerr(_("Error: %s.\n"), error->message); - g_string_printf(nmc->return_text, _("Error: not all connections found.")); - nmc->return_value = error->code; - g_clear_error(&error); - - if (nmc->return_value != NMC_RESULT_ERROR_NOT_FOUND) - goto finish; - - if (!invalid_cons) - invalid_cons = g_string_new(NULL); - if (cur_selector) - g_string_append_printf(invalid_cons, "%s '%s', ", cur_selector, cur_value); - else - g_string_append_printf(invalid_cons, "'%s', ", cur_value); - } - } - - if (!found_cons) { - if (!invalid_cons) { - g_string_printf(nmc->return_text, _("Error: No connection specified.")); - nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; - } - goto finish; - } - - if (nmc->complete) - goto finish; - - info = g_slice_new0(ConnectionCbInfo); - info->nmc = nmc; - info->obj_list = g_ptr_array_sized_new(found_cons->len); - for (i = 0; i < found_cons->len; i++) { - connection = found_cons->pdata[i]; - g_ptr_array_add(info->obj_list, g_object_ref(connection)); - } - info->timeout_id = g_timeout_add_seconds(nmc->timeout, connection_op_timeout_cb, info); - info->cancellable = g_cancellable_new(); - - nmc->nowait_flag = (nmc->timeout == 0); - nmc->should_wait++; - - g_signal_connect(nmc->client, - NM_CLIENT_CONNECTION_REMOVED, - G_CALLBACK(connection_removed_cb), - info); - - for (i = 0; i < found_cons->len; i++) { - nm_remote_connection_delete_async(NM_REMOTE_CONNECTION(found_cons->pdata[i]), - info->cancellable, - delete_cb, - info); - } - -finish: - if (invalid_cons) { - g_string_truncate(invalid_cons, invalid_cons->len - 2); /* truncate trailing ", " */ - g_string_printf(nmc->return_text, - _("Error: cannot delete unknown connection(s): %s."), - invalid_cons->str); - nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; - } -} - -static void -connection_changed(NMConnection *connection, NmCli *nmc) -{ - g_print(_("%s: connection profile changed\n"), nm_connection_get_id(connection)); -} - -static void -connection_watch(NmCli *nmc, NMConnection *connection) -{ - nmc->should_wait++; - g_signal_connect(connection, NM_CONNECTION_CHANGED, G_CALLBACK(connection_changed), nmc); -} - -static void -connection_unwatch(NmCli *nmc, NMConnection *connection) -{ - if (g_signal_handlers_disconnect_by_func(connection, G_CALLBACK(connection_changed), nmc)) - nmc->should_wait--; - - /* Terminate if all the watched connections disappeared. */ - if (!nmc->should_wait) - quit(); -} - -static void -connection_added(NMClient *client, NMRemoteConnection *con, NmCli *nmc) -{ - NMConnection *connection = NM_CONNECTION(con); - - g_print(_("%s: connection profile created\n"), nm_connection_get_id(connection)); - connection_watch(nmc, connection); -} - -static void -connection_removed(NMClient *client, NMRemoteConnection *con, NmCli *nmc) -{ - NMConnection *connection = NM_CONNECTION(con); - - g_print(_("%s: connection profile removed\n"), nm_connection_get_id(connection)); - connection_unwatch(nmc, connection); -} - -static void -do_connection_monitor(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - GError * error = NULL; - guint i; - gs_unref_ptrarray GPtrArray *found_cons = NULL; - const GPtrArray * connections = NULL; - - next_arg(nmc, &argc, &argv, NULL); - if (argc == 0) { - /* No connections specified. Monitor all. */ - - /* nmc_do_cmd() should not call this with argc=0. */ - g_assert(!nmc->complete); - - connections = nm_client_get_connections(nmc->client); - } else { - while (argc > 0) { - if (!get_connection(nmc, &argc, &argv, NULL, NULL, &found_cons, &error)) { - if (!nmc->complete) - g_printerr(_("Error: %s.\n"), error->message); - g_string_printf(nmc->return_text, _("Error: not all connections found.")); - nmc->return_value = error->code; - return; - } - - if (nmc->complete) - continue; - - connections = found_cons; - } - } - - if (nmc->complete) - return; - - for (i = 0; i < connections->len; i++) - connection_watch(nmc, connections->pdata[i]); - - if (argc == 0) { - /* We'll watch the connection additions too, never exit. */ - nmc->should_wait++; - g_signal_connect(nmc->client, - NM_CLIENT_CONNECTION_ADDED, - G_CALLBACK(connection_added), - nmc); - } - - g_signal_connect(nmc->client, - NM_CLIENT_CONNECTION_REMOVED, - G_CALLBACK(connection_removed), - nmc); -} - -static void -connection_reload_cb(GObject *source, GAsyncResult *result, gpointer user_data) -{ - NmCli * nmc = user_data; - gs_free_error GError *error = NULL; - gs_unref_variant GVariant *ret = NULL; - - ret = nm_dbus_call_finish(result, &error); - if (error) { - g_string_printf(nmc->return_text, - _("Error: failed to reload connections: %s."), - nmc_error_get_simple_message(error)); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - } - - quit(); -} - -static void -do_connection_reload(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - next_arg(nmc, &argc, &argv, NULL); - if (nmc->complete) - return; - - nmc->should_wait++; - nm_dbus_call(G_BUS_TYPE_SYSTEM, - NM_DBUS_SERVICE, - NM_DBUS_PATH_SETTINGS, - NM_DBUS_INTERFACE_SETTINGS, - "ReloadConnections", - g_variant_new("()"), - G_VARIANT_TYPE("(b)"), - NULL, - (nmc->timeout == -1 ? 90 : nmc->timeout) * 1000, - connection_reload_cb, - nmc); -} - -static void -do_connection_load(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - GError * error = NULL; - gs_free const char **filenames = NULL; - gs_strfreev char ** failures = NULL; - int i; - - next_arg(nmc, &argc, &argv, NULL); - if (argc == 0) { - g_string_printf(nmc->return_text, _("Error: No connection specified.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - if (nmc->complete) { - nmc->return_value = NMC_RESULT_COMPLETE_FILE; - return; - } - - filenames = (const char **) nm_utils_strv_dup(argv, argc, FALSE); - - nm_client_load_connections(nmc->client, (char **) filenames, &failures, NULL, &error); - if (error) { - g_string_printf(nmc->return_text, - _("Error: failed to load connection: %s."), - nmc_error_get_simple_message(error)); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - g_error_free(error); - } - - if (failures) { - for (i = 0; failures[i]; i++) - g_printerr(_("Could not load file '%s'\n"), failures[i]); - } -} - -#define PROMPT_IMPORT_FILE N_("File to import: ") - -static void -do_connection_import(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - gs_free_error GError *error = NULL; - const char * type = NULL, *filename = NULL; - gs_free char * type_ask = NULL; - gs_free char * filename_ask = NULL; - gs_unref_object NMConnection *connection = NULL; - NMVpnEditorPlugin * plugin; - gs_free char * service_type = NULL; - gboolean temporary = FALSE; - - /* Check --temporary */ - if (next_arg(nmc, &argc, &argv, "--temporary", NULL) > 0) { - temporary = TRUE; - next_arg(nmc, &argc, &argv, NULL); - } - - if (argc == 0) { - /* nmc_do_cmd() should not call this with argc=0. */ - g_assert(!nmc->complete); - - if (nmc->ask) { - type_ask = - nmc_readline(&nmc->nmc_config, "%s: ", gettext(NM_META_TEXT_PROMPT_VPN_TYPE)); - type = nm_strstrip(type_ask); - filename_ask = nmc_readline(&nmc->nmc_config, gettext(PROMPT_IMPORT_FILE)); - filename = nm_strstrip(filename_ask); - } else { - g_string_printf(nmc->return_text, _("Error: No arguments provided.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - } - - while (argc > 0) { - if (argc == 1 && nmc->complete) { - nmc_complete_strings(*argv, type ? NULL : "type", filename ? NULL : "file"); - } - - if (strcmp(*argv, "type") == 0) { - argc--; - argv++; - if (!argc) { - g_string_printf(nmc->return_text, _("Error: %s argument is missing."), *(argv - 1)); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - if (argc == 1 && nmc->complete) { - nmc_complete_strings(*argv, "wireguard"); - complete_option(nmc, - (const NMMetaAbstractInfo *) nm_meta_property_info_vpn_service_type, - *argv, - NULL); - } - - if (!type) - type = *argv; - else - g_printerr(_("Warning: 'type' already specified, ignoring extra one.\n")); - - } else if (strcmp(*argv, "file") == 0) { - argc--; - argv++; - if (!argc) { - g_string_printf(nmc->return_text, _("Error: %s argument is missing."), *(argv - 1)); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - if (argc == 1 && nmc->complete) - nmc->return_value = NMC_RESULT_COMPLETE_FILE; - if (!filename) - filename = *argv; - else - g_printerr(_("Warning: 'file' already specified, ignoring extra one.\n")); - } else { - g_string_printf(nmc->return_text, _("Error: invalid extra argument '%s'."), *argv); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - next_arg(nmc, &argc, &argv, NULL); - } - - if (nmc->complete) - return; - - if (!type) { - g_string_printf(nmc->return_text, _("Error: 'type' argument is required.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - if (!filename) { - g_string_printf(nmc->return_text, _("Error: 'file' argument is required.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - return; - } - - if (nm_streq(type, "wireguard")) - connection = nm_vpn_wireguard_import(filename, &error); - else { - service_type = nm_vpn_plugin_info_list_find_service_type(nm_vpn_get_plugin_infos(), type); - if (!service_type) { - g_string_printf(nmc->return_text, _("Error: failed to find VPN plugin for %s."), type); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - return; - } - - /* Import VPN configuration */ - plugin = nm_vpn_get_editor_plugin(service_type, &error); - if (!plugin) { - g_string_printf(nmc->return_text, - _("Error: failed to load VPN plugin: %s."), - error->message); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - return; - } - - connection = nm_vpn_editor_plugin_import(plugin, filename, &error); - } - - if (!connection) { - g_string_printf(nmc->return_text, - _("Error: failed to import '%s': %s."), - filename, - error->message); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - return; - } - - add_connection(nmc->client, - connection, - temporary, - add_connection_cb, - _add_connection_info_new(nmc, NULL, connection)); - nmc->should_wait++; -} - -static void -do_connection_export(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - NMConnection * connection = NULL; - const char * out_name = NULL; - gs_free char * out_name_ask = NULL; - const char * path = NULL; - const char * type = NULL; - NMVpnEditorPlugin *plugin; - gs_free_error GError *error = NULL; - char tmpfile[] = "/tmp/nmcli-export-temp-XXXXXX"; - gs_strfreev char ** arg_arr = NULL; - int arg_num; - const char *const ** argv_ptr; - int * argc_ptr; - - next_arg(nmc, &argc, &argv, NULL); - argv_ptr = &argv; - argc_ptr = &argc; - - if (argc == 0 && nmc->ask) { - gs_free char *line = NULL; - - /* nmc_do_cmd() should not call this with argc=0. */ - g_assert(!nmc->complete); - - line = nmc_readline(&nmc->nmc_config, PROMPT_VPN_CONNECTION); - nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num); - argv_ptr = (const char *const **) &arg_arr; - argc_ptr = &arg_num; - } - - connection = get_connection(nmc, argc_ptr, argv_ptr, NULL, NULL, NULL, &error); - if (!connection) { - g_string_printf(nmc->return_text, _("Error: %s."), error->message); - nmc->return_value = error->code; - goto finish; - } - - if (nmc->complete) - return; - - out_name = *argv; - - if (next_arg(nmc->ask ? NULL : nmc, argc_ptr, argv_ptr, NULL) == 0) { - g_string_printf(nmc->return_text, _("Error: unknown extra argument: '%s'."), *argv); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - - if (!out_name && nmc->ask) { - out_name = out_name_ask = nmc_readline(&nmc->nmc_config, _("Output file name: ")); - } - - type = nm_connection_get_connection_type(connection); - if (g_strcmp0(type, NM_SETTING_VPN_SETTING_NAME) != 0) { - g_string_printf(nmc->return_text, _("Error: the connection is not VPN.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - type = nm_setting_vpn_get_service_type(nm_connection_get_setting_vpn(connection)); - - /* Export VPN configuration */ - plugin = nm_vpn_get_editor_plugin(type, &error); - if (!plugin) { - g_string_printf(nmc->return_text, - _("Error: failed to load VPN plugin: %s."), - error->message); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - goto finish; - } - - if (out_name) - path = out_name; - else { - nm_auto_close int fd = -1; - - fd = g_mkstemp_full(tmpfile, O_RDWR | O_CLOEXEC, 0600); - if (fd == -1) { - g_string_printf(nmc->return_text, - _("Error: failed to create temporary file %s."), - tmpfile); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - goto finish; - } - path = tmpfile; - } - - if (!nm_vpn_editor_plugin_export(plugin, path, connection, &error)) { - g_string_printf(nmc->return_text, - _("Error: failed to export '%s': %s."), - nm_connection_get_id(connection), - error->message); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - goto finish; - } - - /* No output file -> copy data to stdout */ - if (!out_name) { - gs_free char *contents = NULL; - gsize len = 0; - - if (!g_file_get_contents(path, &contents, &len, &error)) { - g_string_printf(nmc->return_text, - _("Error: failed to read temporary file '%s': %s."), - path, - error->message); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - goto finish; - } - g_print("%s", contents); - } - -finish: - if (!out_name && path) - unlink(path); -} - -static char * -gen_func_connection_names(const char *text, int state) -{ - guint i; - const GPtrArray *connections; - const char ** connection_names; - char * ret; - - connections = nm_client_get_connections(nm_cli_global_readline->client); - if (connections->len == 0) - return NULL; - - connection_names = g_new(const char *, connections->len + 1); - for (i = 0; i < connections->len; i++) - connection_names[i] = nm_connection_get_id(NM_CONNECTION(connections->pdata[i])); - connection_names[i] = NULL; - - ret = nmc_rl_gen_func_basic(text, state, connection_names); - - g_free(connection_names); - return ret; -} - -static char * -gen_func_active_connection_names(const char *text, int state) -{ - guint i; - const GPtrArray *acs; - const char ** connections; - char * ret; - - if (!nm_cli_global_readline->client) - return NULL; - - acs = nm_client_get_active_connections(nm_cli_global_readline->client); - if (!acs || acs->len == 0) - return NULL; - - connections = g_new(const char *, acs->len + 1); - for (i = 0; i < acs->len; i++) - connections[i] = nm_active_connection_get_id(acs->pdata[i]); - connections[i] = NULL; - - ret = nmc_rl_gen_func_basic(text, state, connections); - - g_free(connections); - return ret; -} - -static char ** -nmcli_con_tab_completion(const char *text, int start, int end) -{ - char ** match_array = NULL; - rl_compentry_func_t * generator_func = NULL; - const NMMetaAbstractInfo *info; - - /* Disable readline's default filename completion */ - rl_attempted_completion_over = 1; - - if (g_strcmp0(rl_prompt, PROMPT_CONNECTION) == 0) { - /* Disable appending space after completion */ - rl_completion_append_character = '\0'; - - if (!is_single_word(rl_line_buffer)) - return NULL; - - generator_func = gen_func_connection_names; - } else if (g_strcmp0(rl_prompt, PROMPT_CONNECTIONS) == 0) { - generator_func = gen_func_connection_names; - } else if (g_strcmp0(rl_prompt, PROMPT_ACTIVE_CONNECTIONS) == 0) { - generator_func = gen_func_active_connection_names; - } else if (rl_prompt && g_str_has_prefix(rl_prompt, NM_META_TEXT_PROMPT_VPN_TYPE)) { - info = (const NMMetaAbstractInfo *) nm_meta_property_info_vpn_service_type; - nmc_tab_completion.words = _meta_abstract_complete(info, text); - generator_func = _meta_abstract_generator; - } else if (g_strcmp0(rl_prompt, PROMPT_IMPORT_FILE) == 0) { - rl_attempted_completion_over = 0; - rl_complete_with_tilde_expansion = 1; - } else if (g_strcmp0(rl_prompt, PROMPT_VPN_CONNECTION) == 0) { - generator_func = gen_vpn_ids; - } - - if (generator_func) - match_array = rl_completion_matches(text, generator_func); - - nm_clear_pointer(&nmc_tab_completion.words, g_strfreev); - return match_array; -} - -void -nmc_command_func_connection(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) -{ - static const NMCCommand cmds[] = { - {"show", do_connections_show, usage_connection_show, TRUE, TRUE}, - {"up", do_connection_up, usage_connection_up, TRUE, TRUE}, - {"down", do_connection_down, usage_connection_down, TRUE, TRUE}, - {"add", do_connection_add, usage_connection_add, TRUE, TRUE}, - {"edit", do_connection_edit, usage_connection_edit, TRUE, TRUE}, - {"delete", do_connection_delete, usage_connection_delete, TRUE, TRUE}, - {"reload", do_connection_reload, usage_connection_reload, FALSE, FALSE}, - {"load", do_connection_load, usage_connection_load, TRUE, TRUE}, - {"modify", do_connection_modify, usage_connection_modify, TRUE, TRUE}, - {"clone", do_connection_clone, usage_connection_clone, TRUE, TRUE}, - {"import", do_connection_import, usage_connection_import, TRUE, TRUE}, - {"export", do_connection_export, usage_connection_export, TRUE, TRUE}, - {"monitor", do_connection_monitor, usage_connection_monitor, TRUE, TRUE}, - {NULL, do_connections_show, usage, TRUE, TRUE}, - }; - - next_arg(nmc, &argc, &argv, NULL); - - nmc_start_polkit_agent_start_try(nmc); - - /* Set completion function for 'nmcli con' */ - rl_attempted_completion_function = nmcli_con_tab_completion; - - nmc_do_cmd(nmc, cmds, *argv, argc, argv); -} - -void -monitor_connections(NmCli *nmc) -{ - do_connection_monitor(NULL, nmc, 0, NULL); -} |