From 0018d1f3cf71d680d7b6bceda55a5717244d8b26 Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Tue, 16 Aug 2022 18:24:19 +0200 Subject: New upstream version 1.39.90 --- src/nmcli/common.c | 163 +++++++- src/nmcli/connections.c | 475 ++++++++++++++++------- src/nmcli/devices.c | 407 ++++++++++++++----- src/nmcli/general.c | 5 +- src/nmcli/generate-docs-nm-settings-nmcli.xml | 18 +- src/nmcli/generate-docs-nm-settings-nmcli.xml.in | 18 +- src/nmcli/meson.build | 4 +- src/nmcli/nmcli.c | 11 +- src/nmcli/nmcli.h | 160 +++++--- 9 files changed, 961 insertions(+), 300 deletions(-) (limited to 'src/nmcli') diff --git a/src/nmcli/common.c b/src/nmcli/common.c index f42f76e4..ff4c15c3 100644 --- a/src/nmcli/common.c +++ b/src/nmcli/common.c @@ -16,6 +16,8 @@ #include #include #endif +#include + #include "libnm-client-aux-extern/nm-libnm-aux.h" #include "libnmc-base/nm-vpn-helpers.h" @@ -469,7 +471,8 @@ nmc_find_connection(const GPtrArray *connections, goto found; } - if (NM_IN_STRSET(filter_type, NULL, "filename")) { + if (NM_IS_REMOTE_CONNECTION(connections->pdata[i]) + && NM_IN_STRSET(filter_type, NULL, "filename")) { v = nm_remote_connection_get_filename(NM_REMOTE_CONNECTION(connections->pdata[i])); if (complete && (filter_type || *filter_val)) nmc_complete_strings(filter_val, v); @@ -536,8 +539,6 @@ nmc_find_active_connection(const GPtrArray *active_cons, NMActiveConnection *candidate = g_ptr_array_index(active_cons, i); const char *v, *v_num; - con = nm_active_connection_get_connection(candidate); - /* When filter_type is NULL, compare connection ID (filter_val) * against all types. Otherwise, only compare against the specific * type. If 'path' or 'apath' filter types are specified, comparison @@ -559,6 +560,8 @@ nmc_find_active_connection(const GPtrArray *active_cons, goto found; } + con = nm_active_connection_get_connection(candidate); + if (NM_IN_STRSET(filter_type, NULL, "path")) { v = con ? nm_connection_get_path(NM_CONNECTION(con)) : NULL; v_num = nm_utils_dbus_path_get_last_component(v); @@ -569,7 +572,7 @@ nmc_find_active_connection(const GPtrArray *active_cons, } if (NM_IN_STRSET(filter_type, NULL, "filename")) { - v = nm_remote_connection_get_filename(con); + v = con ? nm_remote_connection_get_filename(con) : NULL; if (complete && (filter_type || *filter_val)) nmc_complete_strings(filter_val, v); if (nm_streq0(filter_val, v)) @@ -926,7 +929,8 @@ read_again: } } else if (!rl_string) { /* Ctrl-D, exit */ - nmc_exit(); + if (g_main_loop_is_running(loop)) + nmc_exit(); } /* Return NULL, not empty string */ @@ -1010,7 +1014,7 @@ nmc_readline_echo(const NmcConfig *nmc_config, gboolean echo_on, const char *pro #if HAVE_READLINE_HISTORY nm_auto_free HISTORY_STATE *saved_history = NULL; HISTORY_STATE passwd_history = { - 0, + 0, }; #else int start, curpos; @@ -1273,12 +1277,157 @@ got_client(GObject *source_object, GAsyncResult *res, gpointer user_data) nm_g_slice_free(call); } +typedef struct { + GString *str; + char buf[512]; + CmdCall *call; +} CmdStdinData; + +static void read_offline_connection_next(GInputStream *stream, CmdStdinData *data); + +static void +read_offline_connection_chunk(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM(source_object); + CmdStdinData *data = user_data; + CmdCall *call = data->call; + gs_unref_object GTask *task = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gs_free char *base_dir = NULL; + GError *error = NULL; + gssize bytes_read; + NMConnection *connection; + NmCli *nmc; + + bytes_read = g_input_stream_read_finish(stream, res, &error); + if (bytes_read > 0) { + /* We need to read more. */ + g_string_append_len(data->str, data->buf, bytes_read); + read_offline_connection_next(stream, data); + return; + } + + /* End reached. */ + + task = g_steal_pointer(&call->task); + nmc = g_task_get_task_data(task); + nmc->should_wait--; + + if (bytes_read == -1) { + g_task_return_error(task, error); + goto finish; + } + + keyfile = g_key_file_new(); + if (!g_key_file_load_from_data(keyfile, + data->str->str, + data->str->len, + G_KEY_FILE_NONE, + &error)) { + g_task_return_error(task, error); + goto finish; + } + + base_dir = g_get_current_dir(); + connection = + nm_keyfile_read(keyfile, base_dir, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + if (!connection) { + g_task_return_error(task, error); + goto finish; + } + + g_ptr_array_add(nmc->offline_connections, connection); + call->cmd->func(call->cmd, nmc, call->argc, (const char *const *) call->argv); + g_task_return_boolean(task, TRUE); + +finish: + g_strfreev(call->argv); + nm_g_slice_free(call); + g_string_free(data->str, TRUE); + nm_g_slice_free(data); +} + +static void +read_offline_connection_next(GInputStream *stream, CmdStdinData *data) +{ + g_input_stream_read_async(stream, + data->buf, + sizeof(data->buf), + G_PRIORITY_DEFAULT, + NULL, + read_offline_connection_chunk, + data); +} + +static void +read_offline_connection(CmdCall *call) +{ + gs_unref_object GInputStream *stream = NULL; + CmdStdinData *data; + + stream = g_unix_input_stream_new(STDIN_FILENO, TRUE); + data = g_slice_new(CmdStdinData); + data->call = call; + data->str = g_string_new_len(NULL, sizeof(data->buf)); + + read_offline_connection_next(stream, data); +} + +static NMConnection * +dummy_offline_connection(void) +{ + NMConnection *connection; + + connection = nm_simple_connection_new(); + nm_connection_add_setting(connection, nm_setting_connection_new()); + return connection; +} + static void call_cmd(NmCli *nmc, GTask *task, const NMCCommand *cmd, int argc, const char *const *argv) { CmdCall *call; - if (nmc->client || !cmd->needs_client) { + if (nmc->offline) { + if (!cmd->supports_offline) { + g_task_return_new_error(task, + NMCLI_ERROR, + NMC_RESULT_ERROR_USER_INPUT, + _("Error: command doesn't support --offline mode.")); + g_object_unref(task); + return; + } + + if (!nmc->offline_connections) + nmc->offline_connections = g_ptr_array_new_full(1, g_object_unref); + + if (cmd->needs_offline_conn) { + g_return_if_fail(nmc->offline_connections->len == 0); + + if (nmc->complete) { + g_ptr_array_add(nmc->offline_connections, dummy_offline_connection()); + cmd->func(cmd, nmc, argc, argv); + g_task_return_boolean(task, TRUE); + g_object_unref(task); + return; + } + + nmc->should_wait++; + call = g_slice_new(CmdCall); + *call = (CmdCall){ + .cmd = cmd, + .argc = argc, + .argv = nm_strv_dup(argv, argc, TRUE), + .task = task, + }; + read_offline_connection(call); + return; + } else { + cmd->func(cmd, nmc, argc, argv); + g_task_return_boolean(task, TRUE); + g_object_unref(task); + } + } else if (nmc->client || !cmd->needs_client) { /* Check whether NetworkManager is running */ if (cmd->needs_nm_running && !nm_client_get_nm_running(nmc->client)) { g_task_return_new_error(task, diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index d093823b..8c6ebd98 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (C) 2010 - 2018 Red Hat, Inc. + * Copyright (C) 2010 - 2022 Red Hat, Inc. */ #include "libnm-client-aux-extern/nm-default-client.h" @@ -18,6 +18,7 @@ #include #endif #include +#include #include "libnm-glib-aux/nm-dbus-aux.h" #include "libnmc-base/nm-client-utils.h" @@ -51,6 +52,7 @@ typedef struct _OptionInfo { NMConnection *connection, const struct _OptionInfo *option, const char *value, + gboolean allow_reset, GError **error); CompEntryFunc generator_func; } OptionInfo; @@ -137,6 +139,131 @@ NM_AUTO_DEFINE_FCN(AddConnectionInfo *, /*****************************************************************************/ +static guint progress_id = 0; /* ID of event source for displaying progress */ + +static void +quit(void) +{ + if (nm_clear_g_source(&progress_id)) + nmc_terminal_erase_line(); + g_main_loop_quit(loop); +} + +typedef struct { + char *data; + gsize written; + gsize length; + NmCli *nmc; +} PrintConnData; + +static void print_connection_chunk(GOutputStream *stream, PrintConnData *print_conn_data); + +static void +print_connection_done(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GOutputStream *stream = G_OUTPUT_STREAM(source_object); + PrintConnData *print_conn_data = user_data; + NmCli *nmc = print_conn_data->nmc; + GError *error = NULL; + gssize written; + + written = g_output_stream_write_finish(stream, res, &error); + if (written == -1) { + g_string_printf(nmc->return_text, + _("Error: Error writting connection: %s"), + error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + nmc->should_wait--; + quit(); + return; + } + + print_conn_data->written += written; + if (print_conn_data->written != print_conn_data->length) { + g_return_if_fail(written); + g_return_if_fail(print_conn_data->written < print_conn_data->length); + + print_connection_chunk(stream, print_conn_data); + return; + } + + g_free(print_conn_data->data); + g_slice_free(PrintConnData, print_conn_data); + + nmc->should_wait--; + quit(); +} + +static void +print_connection_chunk(GOutputStream *stream, PrintConnData *print_conn_data) +{ + g_output_stream_write_async(stream, + print_conn_data->data + print_conn_data->written, + print_conn_data->length - print_conn_data->written, + G_PRIORITY_DEFAULT, + NULL, + print_connection_done, + print_conn_data); +} + +static void +nmc_print_connection_and_quit(NmCli *nmc, NMConnection *connection) +{ + gs_free_error GError *error = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gs_unref_object GOutputStream *stream = NULL; + PrintConnData *print_conn_data; + + if (!nm_connection_normalize(connection, NULL, NULL, &error)) + goto error; + + keyfile = nm_keyfile_write(connection, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + if (!keyfile) + goto error; + + stream = g_unix_output_stream_new(STDOUT_FILENO, FALSE); + print_conn_data = g_slice_new(PrintConnData); + print_conn_data->data = g_key_file_to_data(keyfile, &print_conn_data->length, NULL); + print_conn_data->written = 0; + print_conn_data->nmc = nmc; + print_connection_chunk(stream, print_conn_data); + return; + +error: + g_string_printf(nmc->return_text, _("Error: Error writting connection: %s"), error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + nmc->should_wait--; + quit(); +} + +static const GPtrArray * +nmc_get_connections(const NmCli *nmc) +{ + if (nmc->offline) { + g_return_val_if_fail(!nmc->client, nmc->offline_connections); + return nmc->offline_connections; + } else { + g_return_val_if_fail(nmc->client, NULL); + return nm_client_get_connections(nmc->client); + } +} + +static const GPtrArray * +nmc_get_active_connections(const NmCli *nmc) +{ + static const GPtrArray offline_active_connections = {.len = 0}; + + if (nmc->offline) { + g_return_val_if_fail(!nmc->client, &offline_active_connections); + return &offline_active_connections; + } else { + g_return_val_if_fail(nmc->client, &offline_active_connections); + return nm_client_get_active_connections(nmc->client); + } +} + +/*****************************************************************************/ + /* 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". */ @@ -942,8 +1069,6 @@ const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[] = { #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; @@ -1305,14 +1430,6 @@ usage_connection_migrate(void) "such as \"keyfile\" (default) or \"ifcfg-rh\".\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) { @@ -1952,7 +2069,7 @@ con_show_get_items(NmCli *nmc, gboolean active_only, gboolean show_active_fields row_hash = g_hash_table_new(nm_direct_hash, NULL); - arr = nm_client_get_connections(nmc->client); + arr = nmc_get_connections(nmc); for (i = 0; i < arr->len; i++) { /* Note: libnm will not expose connection that are invisible * to the user but currently inactive. @@ -1971,7 +2088,7 @@ con_show_get_items(NmCli *nmc, gboolean active_only, gboolean show_active_fields _metagen_con_show_row_data_new_for_connection(c, show_active_fields)); } - arr = nm_client_get_active_connections(nmc->client); + arr = nmc_get_active_connections(nmc); for (i = 0; i < arr->len; i++) { NMActiveConnection *ac = arr->pdata[i]; @@ -2119,6 +2236,11 @@ get_connection(NmCli *nmc, NM_SET_OUT(out_selector, NULL); NM_SET_OUT(out_value, NULL); + if (nmc->offline_connections && nmc->offline_connections->len) + return nmc->offline_connections->pdata[0]; + + g_return_val_if_fail(!nmc->offline, NULL); + if (*argc == 0) { g_set_error_literal(error, NMCLI_ERROR, @@ -2259,7 +2381,7 @@ do_connections_show(const NMCCommand *cmd, NmCli *nmc, int argc, const char *con } else { gboolean new_line = FALSE; gboolean without_fields = (nmc->required_fields == NULL); - const GPtrArray *active_cons = nm_client_get_active_connections(nmc->client); + const GPtrArray *active_cons = nmc_get_active_connections(nmc); /* multiline mode is default for 'connection show ' */ if (!nmc->mode_specified) @@ -2315,7 +2437,7 @@ do_connections_show(const NMCCommand *cmd, NmCli *nmc, int argc, const char *con } /* Try to find connection by id, uuid or path first */ - connections = nm_client_get_connections(nmc->client); + connections = nmc_get_connections(nmc); con = nmc_find_connection(connections, selector, *argv, @@ -2452,7 +2574,7 @@ get_default_active_connection(NmCli *nmc, NMDevice **device) g_return_val_if_fail(device, NULL); g_return_val_if_fail(*device == NULL, NULL); - connections = nm_client_get_active_connections(nmc->client); + connections = nmc_get_active_connections(nmc); for (i = 0; i < connections->len; i++) { NMActiveConnection *candidate = g_ptr_array_index(connections, i); const GPtrArray *devices; @@ -3276,7 +3398,7 @@ do_connection_down(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons } /* Get active connections */ - active_cons = nm_client_get_active_connections(nmc->client); + active_cons = nmc_get_active_connections(nmc); while (arg_num > 0) { const char *selector = NULL; @@ -3926,7 +4048,7 @@ set_default_interface_name(NmCli *nmc, NMSettingConnection *s_con) const GPtrArray *connections; gs_free char *ifname = NULL; - connections = nm_client_get_connections(nmc->client); + connections = nmc_get_connections(nmc); ifname = unique_master_iface_ifname(connections, default_name); g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL); } @@ -4052,11 +4174,14 @@ enable_options(const char *setting_name, const char *property, const char *const 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); + if (opts) { + if (!bi->base.property_alias || !g_strv_contains(opts, bi->base.property_alias)) + continue; + } + + _dynamic_options_set((const NMMetaAbstractInfo *) bi, + PROPERTY_INF_FLAG_ENABLED | PROPERTY_INF_FLAG_DISABLED, + PROPERTY_INF_FLAG_ENABLED); } return; } @@ -4064,11 +4189,14 @@ enable_options(const char *setting_name, const char *property, const char *const 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); + if (opts) { + if (!property_info->property_alias || !g_strv_contains(opts, property_info->property_alias)) + return; + } + + _dynamic_options_set((const NMMetaAbstractInfo *) property_info, + PROPERTY_INF_FLAG_ENABLED | PROPERTY_INF_FLAG_DISABLED, + PROPERTY_INF_FLAG_ENABLED); } /* @@ -4254,7 +4382,7 @@ set_option(NmCli *nmc, NULL, NULL); if (option && option->check_and_set) { - return option->check_and_set(nmc, connection, option, value, error); + return option->check_and_set(nmc, connection, option, value, allow_reset, error); } else if (value || allow_reset) { return set_property(nmc->client, connection, @@ -4380,21 +4508,63 @@ gen_func_bond_lacp_rate(const char *text, int state) /*****************************************************************************/ +static gboolean +enable_type_settings_and_options(NmCli *nmc, NMConnection *con, GError **error) +{ + const NMMetaSettingValidPartItem *const *type_settings; + const NMMetaSettingValidPartItem *const *slv_settings; + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection(con); + g_return_val_if_fail(s_con, FALSE); + + if (nm_setting_connection_get_slave_type(s_con)) + enable_options(NM_SETTING_CONNECTION_SETTING_NAME, NM_SETTING_CONNECTION_MASTER, NULL); + + if (NM_IN_STRSET(nm_setting_connection_get_connection_type(s_con), + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BOND_SETTING_NAME, + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_DUMMY_SETTING_NAME, + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + NM_SETTING_OVS_PATCH_SETTING_NAME, + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_TEAM_SETTING_NAME, + NM_SETTING_VETH_SETTING_NAME, + NM_SETTING_VRF_SETTING_NAME, + NM_SETTING_WIREGUARD_SETTING_NAME)) { + enable_options(NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME, + NULL); + } + + if (!con_settings(con, &type_settings, &slv_settings, error)) + return FALSE; + + ensure_settings(con, slv_settings); + ensure_settings(con, type_settings); + + /* For some software connection types we generate the interface name for the user. */ + set_default_interface_name(nmc, s_con); + + return TRUE; +} + static gboolean set_connection_type(NmCli *nmc, NMConnection *con, const OptionInfo *option, const char *value, + gboolean allow_reset, 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; + GError *local = NULL; + const char *slave_type = NULL; value = check_valid_name_toplevel(value, &slave_type, &local); if (!value) { + if (!allow_reset) + return TRUE; g_set_error(error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -4414,16 +4584,6 @@ set_connection_type(NmCli *nmc, 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 (NM_IN_STRSET(value, - NM_SETTING_BOND_SETTING_NAME, - NM_SETTING_TEAM_SETTING_NAME, - NM_SETTING_BRIDGE_SETTING_NAME, - NM_SETTING_VLAN_SETTING_NAME)) { - disable_options(NM_SETTING_CONNECTION_SETTING_NAME, NM_SETTING_CONNECTION_INTERFACE_NAME); } if (!set_property(nmc->client, @@ -4435,13 +4595,7 @@ set_connection_type(NmCli *nmc, 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; + return enable_type_settings_and_options(nmc, con, error); } static gboolean @@ -4449,12 +4603,15 @@ set_connection_iface(NmCli *nmc, NMConnection *con, const OptionInfo *option, const char *value, + gboolean allow_reset, GError **error) { if (value) { /* Special value of '*' means no specific interface name */ if (nm_streq(value, "*")) value = NULL; + } else if (!allow_reset) { + return TRUE; } return set_property(nmc->client, @@ -4471,6 +4628,7 @@ set_connection_master(NmCli *nmc, NMConnection *con, const OptionInfo *option, const char *value, + gboolean allow_reset, GError **error) { const GPtrArray *connections; @@ -4481,6 +4639,8 @@ set_connection_master(NmCli *nmc, g_return_val_if_fail(s_con, FALSE); if (!value) { + if (!allow_reset) + return TRUE; g_set_error_literal(error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -4489,7 +4649,7 @@ set_connection_master(NmCli *nmc, } slave_type = nm_setting_connection_get_slave_type(s_con); - connections = nm_client_get_connections(nmc->client); + connections = nmc_get_connections(nmc); value = normalized_master_for_slave(connections, value, slave_type, &slave_type); if (!set_property(nmc->client, @@ -4516,10 +4676,10 @@ set_bond_option(NmCli *nmc, NMConnection *con, const OptionInfo *option, const char *value, + gboolean allow_reset, GError **error) { NMSettingBond *s_bond; - gboolean success; gs_free char *name = NULL; char *p; @@ -4533,26 +4693,25 @@ set_bond_option(NmCli *nmc, } 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 (allow_reset) { + nm_setting_bond_remove_option(s_bond, name); + return TRUE; + } + } else { + if (!_nm_meta_setting_bond_add_option(NM_SETTING(s_bond), name, value, error)) + 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")); - } + if (nm_streq(name, NM_SETTING_BOND_OPTION_MODE)) { + value = nm_setting_bond_get_option_by_name(s_bond, name); + if (nm_streq(value, "active-backup")) { + enable_options(NM_SETTING_BOND_SETTING_NAME, + NM_SETTING_BOND_OPTIONS, + NM_MAKE_STRV("primary")); } } - return success; + return TRUE; } static gboolean @@ -4560,6 +4719,7 @@ set_bond_monitoring_mode(NmCli *nmc, NMConnection *con, const OptionInfo *option, const char *value, + gboolean allow_reset, GError **error) { NMSettingBond *s_bond; @@ -4600,6 +4760,7 @@ set_bluetooth_type(NmCli *nmc, NMConnection *con, const OptionInfo *option, const char *value, + gboolean allow_reset, GError **error) { NMSetting *setting; @@ -4648,6 +4809,7 @@ set_ip4_address(NmCli *nmc, NMConnection *con, const OptionInfo *option, const char *value, + gboolean allow_reset, GError **error) { NMSettingIPConfig *s_ip4; @@ -4675,6 +4837,7 @@ set_ip6_address(NmCli *nmc, NMConnection *con, const OptionInfo *option, const char *value, + gboolean allow_reset, GError **error) { NMSettingIPConfig *s_ip6; @@ -5265,12 +5428,9 @@ connection_warnings(NmCli *nmc, NMConnection *connection) if (deprecated) g_printerr(_("Warning: %s.\n"), deprecated); - connections = nm_client_get_connections(nmc->client); - if (!connections) - return; - - id = nm_connection_get_id(connection); - found = 0; + connections = nmc_get_connections(nmc); + id = nm_connection_get_id(connection); + found = 0; for (i = 0; i < connections->len; i++) { NMConnection *candidate = NM_CONNECTION(connections->pdata[i]); @@ -5348,15 +5508,6 @@ add_connection(NMClient *client, 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) { @@ -5448,17 +5599,30 @@ ask_option(NmCli *nmc, NMConnection *connection, const NMMetaAbstractInfo *abstr GError *error = NULL; gs_free char *prompt = NULL; gboolean multi; + const char *setting_name, *property_name; const char *opt_prompt, *opt_def_hint; + gs_free char *def_hint = NULL; + gs_free char *property_val = NULL; NMMetaPropertyInfFlags inf_flags; + NMSetting *setting; _meta_abstract_get(abstract_info, NULL, - NULL, - NULL, + &setting_name, + &property_name, NULL, &inf_flags, &opt_prompt, &opt_def_hint); + + if (!opt_def_hint) { + setting = nm_connection_get_setting_by_name(connection, setting_name); + if (setting) + property_val = nmc_setting_get_property_parsable(setting, property_name, NULL); + if (property_val) + opt_def_hint = def_hint = g_strdup_printf("[%s]", property_val); + } + prompt = g_strjoin("", gettext(opt_prompt), opt_def_hint ? " " : "", opt_def_hint ?: "", ": ", NULL); @@ -5469,8 +5633,6 @@ ask_option(NmCli *nmc, NMConnection *connection, const NMMetaAbstractInfo *abstr again: value = nmc_readline(&nmc->nmc_config, "%s", prompt); - if (multi && !value) - return; if (!set_option(nmc, connection, abstract_info, value, FALSE, &error)) { g_printerr("%s\n", error->message); @@ -5490,7 +5652,9 @@ connection_get_base_meta_setting_type(NMConnection *connection) const NMMetaSettingInfoEditor *editor; connection_type = nm_connection_get_connection_type(connection); - nm_assert(connection_type); + if (!connection_type) + return NM_META_SETTING_TYPE_UNKNOWN; + 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); @@ -5546,10 +5710,15 @@ questionnaire_mandatory(NmCli *nmc, NMConnection *connection) NMMetaSettingType s, base; /* First ask connection properties */ - questionnaire_mandatory_ask_setting(nmc, connection, NM_META_SETTING_TYPE_CONNECTION); + while (1) { + base = connection_get_base_meta_setting_type(connection); + if (base != NM_META_SETTING_TYPE_UNKNOWN) + break; + enable_options(NM_SETTING_CONNECTION_SETTING_NAME, NM_SETTING_CONNECTION_TYPE, NULL); + 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 */ @@ -5564,16 +5733,14 @@ want_provide_opt_args(const NmcConfig *nmc_config, const char *type, guint num) { gs_free char *answer = NULL; + /* Don't ask to ask. */ + if (num == 1) + return TRUE; + /* 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)); + g_print(_("There are %d optional settings for %s.\n"), (int) num, type); + answer = + nmc_readline(nmc_config, _("Do you want to provide them? %s"), prompt_yes_no(TRUE, NULL)); nm_strstrip(answer); return !answer || matches(answer, WORD_YES); } @@ -5661,6 +5828,20 @@ again: return TRUE; } +static void +nmc_add_connection(NmCli *nmc, NMConnection *connection, gboolean temporary) +{ + if (nmc->offline) { + nmc_print_connection_and_quit(nmc, connection); + } else { + add_connection(nmc->client, + connection, + temporary, + add_connection_cb, + _add_connection_info_new(nmc, NULL, connection)); + } +} + static void do_connection_add(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) { @@ -5722,6 +5903,12 @@ read_properties: if (nmc->complete) goto finish; + if (!enable_type_settings_and_options(nmc, connection, &error)) { + g_string_assign(nmc->return_text, error->message); + nmc->return_value = error->code; + goto finish; + } + /* Now ask user for the rest of the mandatory options. */ if (nmc->ask) questionnaire_mandatory(nmc, connection); @@ -5748,7 +5935,7 @@ read_properties: gs_free char *default_name = NULL; const GPtrArray *connections; - connections = nm_client_get_connections(nmc->client); + connections = nmc_get_connections(nmc); 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)); @@ -5757,9 +5944,6 @@ read_properties: } } - /* 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 { @@ -5813,11 +5997,7 @@ read_properties: } } - add_connection(nmc->client, - connection, - !save_bool, - add_connection_cb, - _add_connection_info_new(nmc, NULL, connection)); + nmc_add_connection(nmc, connection, !save_bool); nmc->should_wait++; finish: @@ -5839,7 +6019,7 @@ uuid_display_hook(char **array, int len, int max_len) char *tmp; const char *id; for (i = 1; i <= len; i++) { - connections = nm_client_get_connections(nmc_tab_completion.nmc->client); + connections = nmc_get_connections(nmc_tab_completion.nmc); con = nmc_find_connection(connections, "uuid", array[i], NULL, FALSE); id = con ? nm_connection_get_id(con) : NULL; if (id) { @@ -6173,7 +6353,7 @@ gen_vpn_uuids(const char *text, int state) const char **uuids; char *ret; - connections = nm_client_get_connections(nm_cli_global_readline->client); + connections = nmc_get_connections(nm_cli_global_readline); if (connections->len < 1) return NULL; @@ -6190,7 +6370,7 @@ gen_vpn_ids(const char *text, int state) const char **ids; char *ret; - connections = nm_client_get_connections(nm_cli_global_readline->client); + connections = nmc_get_connections(nm_cli_global_readline); if (connections->len < 1) return NULL; @@ -8336,7 +8516,11 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty /* 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); + nm_remote_connection_commit_changes_async(rem_con, + !temporary, + NULL, + update_connection_editor_cb, + NULL); handler_id = g_signal_connect(rem_con, NM_CONNECTION_CHANGED, @@ -8721,12 +8905,12 @@ do_connection_edit(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons 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}}; + {"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) @@ -8750,7 +8934,7 @@ do_connection_edit(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons /* Use ' ' and '.' as word break characters */ rl_completer_word_break_characters = ". "; - connections = nm_client_get_connections(nmc->client); + connections = nmc_get_connections(nmc); if (!con) { if (con_id && !con_uuid && !con_path && !con_filename) { @@ -8931,11 +9115,24 @@ modify_connection_cb(GObject *connection, GAsyncResult *result, gpointer user_da quit(); } +static void +nmc_update_connection(NmCli *nmc, NMConnection *connection, gboolean temporary) +{ + if (nmc->offline) { + nmc_print_connection_and_quit(nmc, connection); + } else { + nm_remote_connection_commit_changes_async(NM_REMOTE_CONNECTION(connection), + !temporary, + NULL, + modify_connection_cb, + nmc); + } +} + 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; @@ -8951,25 +9148,19 @@ do_connection_modify(const NMCCommand *cmd, NmCli *nmc, int argc, const char *co 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; + /* Don't insist on having argument if we're running in offline mode. */ + if (!nmc->offline || argc > 0) { + if (!nmc_process_connection_properties(nmc, connection, &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_update_connection(nmc, connection, temporary); nmc->should_wait++; } @@ -9267,7 +9458,7 @@ do_connection_monitor(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c /* nmc_do_cmd() should not call this with argc=0. */ g_return_if_fail(!nmc->complete); - connections = nm_client_get_connections(nmc->client); + connections = nmc_get_connections(nmc); } else { while (argc > 0) { if (!get_connection(nmc, &argc, &argv, NULL, NULL, &found_cons, &error)) { @@ -9502,7 +9693,7 @@ do_connection_import(const NMCCommand *cmd, NmCli *nmc, int argc, const char *co } if (nm_streq(type, "wireguard")) - connection = nm_vpn_wireguard_import(filename, &error); + connection = nm_conn_wireguard_import(filename, &error); else { service_type = nm_vpn_plugin_info_list_find_service_type(nm_vpn_get_plugin_infos(), type); if (!service_type) { @@ -9768,7 +9959,7 @@ do_connection_migrate(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c if (!found_cons) { /* No connections specified explicitly? Fine, add all. */ found_cons = g_ptr_array_new(); - connections = nm_client_get_connections(nmc->client); + connections = nmc_get_connections(nmc); for (i = 0; i < connections->len; i++) { connection = connections->pdata[i]; g_ptr_array_add(found_cons, connection); @@ -9815,7 +10006,7 @@ gen_func_connection_names(const char *text, int state) const char **connection_names; char *ret; - connections = nm_client_get_connections(nm_cli_global_readline->client); + connections = nmc_get_connections(nm_cli_global_readline); if (connections->len == 0) return NULL; @@ -9841,7 +10032,7 @@ gen_func_active_connection_names(const char *text, int state) if (!nm_cli_global_readline->client) return NULL; - acs = nm_client_get_active_connections(nm_cli_global_readline->client); + acs = nmc_get_active_connections(nm_cli_global_readline); if (!acs || acs->len == 0) return NULL; @@ -9905,12 +10096,12 @@ nmc_command_func_connection(const NMCCommand *cmd, NmCli *nmc, int argc, const c {"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}, + {"add", do_connection_add, usage_connection_add, TRUE, 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}, + {"modify", do_connection_modify, usage_connection_modify, TRUE, TRUE, 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}, diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index be51731f..43bd3724 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (C) 2010 - 2018 Red Hat, Inc. + * Copyright (C) 2010 - 2022 Red Hat, Inc. */ #include "libnm-client-aux-extern/nm-default-client.h" @@ -1042,6 +1042,18 @@ usage_device_lldp(void) "used to list neighbors for a particular interface.\n\n")); } +static void +usage_device_checkpoint(void) +{ + g_printerr(_("Usage: nmcli device checkpoint { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [--timeout ] -- COMMAND...\n" + "\n" + "Runs the command with a configuration checkpoint taken and asks for a\n" + "confirmation when finished. When the confirmation is not given, the\n" + "checkpoint is automatically restored after timeout.\n\n")); +} + static void quit(void) { @@ -1109,59 +1121,72 @@ nmc_complete_device(NMClient *client, const char *prefix, gboolean wifi_only) complete_device(devices, prefix, wifi_only); } -static GSList * -get_device_list(NmCli *nmc, int argc, const char *const *argv) +static void destroy_queue_element(gpointer data); + +static GPtrArray * +get_device_list(NmCli *nmc, int *argc, const char *const **argv) { - int arg_num = argc; + int arg_num; + const char *const *arg_ptr; + gs_strfreev char **arg_arr = NULL; - const char *const *arg_ptr = argv; NMDevice **devices; - GSList *queue = NULL; + GPtrArray *queue = NULL; NMDevice *device; int i; - if (argc == 0) { - if (nmc->ask) { - gs_free char *line = NULL; + if (*argc == 0 && nmc->ask) { + gs_free char *line = NULL; - line = nmc_readline(&nmc->nmc_config, PROMPT_INTERFACES); - nmc_string_to_arg_array(line, NULL, FALSE, &arg_arr, &arg_num); - arg_ptr = (const char *const *) arg_arr; - } - if (arg_num == 0) { - g_string_printf(nmc->return_text, _("Error: No interface specified.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto error; - } + line = nmc_readline(&nmc->nmc_config, PROMPT_INTERFACES); + nmc_string_to_arg_array(line, NULL, FALSE, &arg_arr, &arg_num); + arg_ptr = (const char *const *) arg_arr; + + argc = &arg_num; + argv = &arg_ptr; + } + + if (*argc == 0) { + g_string_printf(nmc->return_text, _("Error: No interface specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; } devices = nmc_get_devices_sorted(nmc->client); - while (arg_num > 0) { - if (arg_num == 1 && nmc->complete) - complete_device(devices, *arg_ptr, FALSE); + while (*argc > 0) { + if (strcmp(**argv, "--") == 0) { + (*argc)--; + (*argv)++; + break; + } + + if (*argc == 1 && nmc->complete) + complete_device(devices, **argv, FALSE); device = NULL; for (i = 0; devices[i]; i++) { - if (!g_strcmp0(nm_device_get_iface(devices[i]), *arg_ptr)) { + if (!g_strcmp0(nm_device_get_iface(devices[i]), **argv)) { device = devices[i]; break; } } if (device) { - if (!g_slist_find(queue, device)) - queue = g_slist_prepend(queue, device); + if (!queue) + queue = g_ptr_array_new_with_free_func(destroy_queue_element); + if (!g_ptr_array_find(queue, device, NULL)) + g_ptr_array_add(queue, g_object_ref(device)); else - g_printerr(_("Warning: argument '%s' is duplicated.\n"), *arg_ptr); + g_printerr(_("Warning: argument '%s' is duplicated.\n"), **argv); } else { if (!nmc->complete) - g_printerr(_("Error: Device '%s' not found.\n"), *arg_ptr); + g_printerr(_("Error: Device '%s' not found.\n"), **argv); g_string_printf(nmc->return_text, _("Error: not all devices found.")); nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; } /* Take next argument */ - next_arg(nmc->ask ? NULL : nmc, &arg_num, &arg_ptr, NULL); + next_arg(nmc->ask ? NULL : nmc, argc, argv, NULL); } g_free(devices); @@ -2305,7 +2330,7 @@ do_device_connect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const typedef struct { NmCli *nmc; - GSList *queue; + GPtrArray *queue; guint timeout_id; gboolean cmd_disconnect; GCancellable *cancellable; @@ -2329,7 +2354,7 @@ device_removed_cb(NMClient *client, NMDevice *device, DeviceCbInfo *info) /* Success: device has been removed. * It can also happen when disconnecting a software device. */ - if (!g_slist_find(info->queue, device)) + if (!g_ptr_array_find(info->queue, device, NULL)) return; if (info->cmd_disconnect) @@ -2342,7 +2367,7 @@ device_removed_cb(NMClient *client, NMDevice *device, DeviceCbInfo *info) static void disconnect_state_cb(NMDevice *device, GParamSpec *pspec, DeviceCbInfo *info) { - if (!g_slist_find(info->queue, device)) + if (!g_ptr_array_find(info->queue, device, NULL)) return; if (nm_device_get_state(device) <= NM_DEVICE_STATE_DISCONNECTED) { @@ -2368,22 +2393,16 @@ static void device_cb_info_finish(DeviceCbInfo *info, NMDevice *device) { if (device) { - GSList *elem = g_slist_find(info->queue, device); - if (!elem) + if (!g_ptr_array_remove(info->queue, device)) + return; + if (info->queue->len) return; - info->queue = g_slist_delete_link(info->queue, elem); - destroy_queue_element(device); - } else { - g_slist_free_full(info->queue, destroy_queue_element); - info->queue = NULL; } - if (info->queue) - return; - if (info->timeout_id) g_source_remove(info->timeout_id); + g_ptr_array_free(info->queue, TRUE); g_signal_handlers_disconnect_by_func(info->nmc->client, device_removed_cb, info); nm_clear_g_cancellable(&info->cancellable); @@ -2448,9 +2467,11 @@ do_device_reapply(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const nmc->nowait_flag = (nmc->timeout == 0); nmc->should_wait++; - info = g_slice_new0(DeviceCbInfo); - info->nmc = nmc; - info->queue = g_slist_prepend(info->queue, g_object_ref(device)); + info = g_slice_new0(DeviceCbInfo); + info->nmc = nmc; + + info->queue = g_ptr_array_new_with_free_func(destroy_queue_element); + g_ptr_array_add(info->queue, g_object_ref(device)); /* Now reapply the connection to the device */ nm_device_reapply_async(device, NULL, 0, 0, NULL, reapply_device_cb, info); @@ -2615,23 +2636,29 @@ disconnect_device_cb(GObject *object, GAsyncResult *result, gpointer user_data) static void do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) { - NMDevice *device; - DeviceCbInfo *info = NULL; - GSList *queue, *iter; + NMDevice *device; + DeviceCbInfo *info = NULL; + gs_unref_ptrarray GPtrArray *queue = NULL; + guint i; /* Set default timeout for disconnect operation. */ if (nmc->timeout == -1) nmc->timeout = 10; next_arg(nmc, &argc, &argv, NULL); - queue = get_device_list(nmc, argc, argv); + queue = get_device_list(nmc, &argc, &argv); + if (argc) { + g_string_printf(nmc->return_text, _("Error: invalid extra argument '%s'."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } if (!queue) return; if (nmc->complete) - goto out; - queue = g_slist_reverse(queue); + return; info = g_slice_new0(DeviceCbInfo); + info->queue = g_steal_pointer(&queue); info->nmc = nmc; info->cmd_disconnect = TRUE; info->cancellable = g_cancellable_new(); @@ -2643,18 +2670,12 @@ do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c nmc->nowait_flag = (nmc->timeout == 0); nmc->should_wait++; - for (iter = queue; iter; iter = g_slist_next(iter)) { - device = iter->data; + for (i = 0; i < info->queue->len; i++) { + device = info->queue->pdata[i]; - info->queue = g_slist_prepend(info->queue, g_object_ref(device)); g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(disconnect_state_cb), info); - - /* Now disconnect the device */ nm_device_disconnect_async(device, info->cancellable, disconnect_device_cb, info); } - -out: - g_slist_free(queue); } static void @@ -2683,41 +2704,38 @@ delete_device_cb(GObject *object, GAsyncResult *result, gpointer user_data) static void do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) { - NMDevice *device; - DeviceCbInfo *info = NULL; - GSList *queue, *iter; + DeviceCbInfo *info = NULL; + gs_unref_ptrarray GPtrArray *queue = NULL; + guint i; /* Set default timeout for delete operation. */ if (nmc->timeout == -1) nmc->timeout = 10; next_arg(nmc, &argc, &argv, NULL); - queue = get_device_list(nmc, argc, argv); + queue = get_device_list(nmc, &argc, &argv); + if (argc) { + g_string_printf(nmc->return_text, _("Error: invalid extra argument '%s'."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } if (!queue) return; if (nmc->complete) - goto out; - queue = g_slist_reverse(queue); + return; - info = g_slice_new0(DeviceCbInfo); - info->nmc = nmc; + info = g_slice_new0(DeviceCbInfo); + info->queue = g_steal_pointer(&queue); + info->nmc = nmc; if (nmc->timeout > 0) info->timeout_id = g_timeout_add_seconds(nmc->timeout, device_op_timeout_cb, info); nmc->nowait_flag = (nmc->timeout == 0); nmc->should_wait++; - for (iter = queue; iter; iter = g_slist_next(iter)) { - device = iter->data; - - info->queue = g_slist_prepend(info->queue, g_object_ref(device)); - - /* Now delete the device */ - nm_device_delete_async(device, NULL, delete_device_cb, info); + for (i = 0; i < info->queue->len; i++) { + nm_device_delete_async(info->queue->pdata[i], NULL, delete_device_cb, info); } - -out: - g_slist_free(queue); } static void @@ -2886,31 +2904,33 @@ device_removed(NMClient *client, NMDevice *device, NmCli *nmc) static void do_devices_monitor(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) { + const GPtrArray *devices; + gs_unref_ptrarray GPtrArray *devices_free = NULL; + guint i; + if (nmc->complete) return; next_arg(nmc, &argc, &argv, NULL); - if (argc == 0) { + if (argc > 0) { + devices = devices_free = get_device_list(nmc, &argc, &argv); + if (argc) { + g_string_printf(nmc->return_text, _("Error: invalid extra argument '%s'."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } + } else { /* No devices specified. Monitor all. */ - const GPtrArray *devices = nm_client_get_devices(nmc->client); - int i; - - for (i = 0; i < devices->len; i++) - device_watch(nmc, g_ptr_array_index(devices, i)); + devices = nm_client_get_devices(nmc->client); /* We'll watch the device additions too, never exit. */ nmc->should_wait++; g_signal_connect(nmc->client, NM_CLIENT_DEVICE_ADDED, G_CALLBACK(device_added), nmc); - } else { - GSList *queue = get_device_list(nmc, argc, argv); - GSList *iter; - - /* Monitor the specified devices. */ - for (iter = queue; iter; iter = g_slist_next(iter)) - device_watch(nmc, NM_DEVICE(iter->data)); - g_slist_free(queue); } + for (i = 0; i < devices->len; i++) + device_watch(nmc, g_ptr_array_index(devices, i)); + g_signal_connect(nmc->client, NM_CLIENT_DEVICE_REMOVED, G_CALLBACK(device_removed), nmc); } @@ -5002,6 +5022,214 @@ do_device_lldp(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *a nmc_do_cmd(nmc, device_lldp_cmds, *argv, argc, argv); } +/*****************************************************************************/ + +typedef struct { + NmCli *nmc; + NMCheckpoint *checkpoint; + char **argv; + guint removed_id; + guint child_id; + gboolean removed; +} CheckpointCbInfo; + +static void +free_checkpoint_info(CheckpointCbInfo *info) +{ + g_clear_object(&info->checkpoint); + g_strfreev(info->argv); + g_slice_free(CheckpointCbInfo, info); +} + +static void +checkpoints_changed_cb(GObject *object, GParamSpec *pspec, CheckpointCbInfo *info) +{ + const GPtrArray *checkpoints; + guint i; + + checkpoints = nm_client_get_checkpoints(info->nmc->client); + for (i = 0; i < checkpoints->len; i++) { + if (checkpoints->pdata[i] == info->checkpoint) { + /* Our checkpoint still exists. */ + return; + } + } + + g_string_printf(info->nmc->return_text, _("Checkpoint was removed.")); + info->nmc->return_value = NMC_RESULT_ERROR_TIMEOUT_EXPIRED; + + info->removed = TRUE; + + if (!info->child_id) { + /* The command is done, we're in the confirmation prompt. */ + g_print("%s\n", _("No")); + g_main_loop_quit(loop); + } +} + +static void +checkpoint_destroy_cb(GObject *object, GAsyncResult *result, void *user_data) +{ + NmCli *nmc = (NmCli *) user_data; + gs_free_error GError *error = NULL; + + if (!nm_client_checkpoint_destroy_finish(nmc->client, result, &error)) { + g_string_printf(nmc->return_text, + _("Error: Destroying a checkpoint failed: %s"), + error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + } + + g_main_loop_quit(loop); +} + +static void +child_watch_cb(GPid pid, gint wait_status, gpointer user_data) +{ + CheckpointCbInfo *info = (CheckpointCbInfo *) user_data; + NmCli *nmc = info->nmc; + char *line; + + info->child_id = 0; + if (info->removed) { + g_main_loop_quit(loop); + goto out; + } + + while (g_main_loop_is_running(loop)) { + line = nmc_readline(&nmc->nmc_config, "Type \"%s\" to commit the changes: ", _("Yes")); + if (g_strcmp0(line, _("Yes")) == 0) { + g_signal_handler_disconnect(nmc->client, info->removed_id); + nm_client_checkpoint_destroy(nmc->client, + nm_object_get_path(NM_OBJECT(info->checkpoint)), + NULL, + checkpoint_destroy_cb, + nmc); + break; + } + } + nmc_cleanup_readline(); +out: + free_checkpoint_info(info); +} + +static void +checkpoint_create_cb(GObject *object, GAsyncResult *result, void *user_data) +{ + NMClient *client = NM_CLIENT(object); + CheckpointCbInfo *info = (CheckpointCbInfo *) user_data; + gs_free_error GError *error = NULL; + GPid pid; + + info->checkpoint = nm_client_checkpoint_create_finish(client, result, &error); + if (!info->checkpoint) { + g_string_printf(info->nmc->return_text, + _("Error: Creating a checkpoint failed: %s"), + error->message); + info->nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + g_main_loop_quit(loop); + goto err; + } + + if (!g_spawn_async(NULL, + info->argv, + NULL, + G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_SEARCH_PATH + | G_SPAWN_CHILD_INHERITS_STDIN | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + info, + &pid, + &error)) { + g_string_printf(info->nmc->return_text, _("Error: %s"), error->message); + info->nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + g_main_loop_quit(loop); + goto err; + } + + info->child_id = g_child_watch_add(pid, child_watch_cb, info); + info->removed_id = g_signal_connect(client, + "notify::" NM_CLIENT_CHECKPOINTS, + G_CALLBACK(checkpoints_changed_cb), + info); + + return; + +err: + free_checkpoint_info(info); +} + +static void +do_device_checkpoint(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) +{ + NMClient *client = nmc->client; + long unsigned int timeout = 15; + int option; + CheckpointCbInfo *info; + const GPtrArray *devices = NULL; + gs_unref_ptrarray GPtrArray *devices_free = NULL; + + while ((option = next_arg(nmc, &argc, &argv, "--timeout", NULL)) > 0) { + switch (option) { + case 1: /* --timeout */ + 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 (!nmc_string_to_uint(*argv, TRUE, 0, G_MAXUINT32, &timeout)) { + g_string_printf(nmc->return_text, _("Error: '%s' is not a valid timeout."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } + break; + default: + nm_assert_not_reached(); + break; + } + } + + if (argc) { + if (strcmp(*argv, "--") == 0) { + devices = nm_client_get_devices(client); + argc--; + argv++; + } else { + devices = devices_free = get_device_list(nmc, &argc, &argv); + if (!devices) { + g_string_printf(nmc->return_text, _("Error: not all devices found.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } + } + } + + if (argc == 0) { + g_string_printf(nmc->return_text, _("Error: Expected a command to run after '--'")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } + + if (nmc->complete) + return; + + info = g_slice_new0(CheckpointCbInfo); + info->nmc = nmc; + info->argv = nm_strv_dup(argv, argc, TRUE); + + nmc->should_wait++; + nm_client_checkpoint_create(client, + devices, + (guint32) timeout, + NM_CHECKPOINT_CREATE_FLAG_NONE, + NULL, + checkpoint_create_cb, + info); +} + +/*****************************************************************************/ + static gboolean is_single_word(const char *line) { @@ -5048,6 +5276,7 @@ void nmc_command_func_device(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) { static const NMCCommand cmds[] = { + {"checkpoint", do_device_checkpoint, usage_device_checkpoint, TRUE, TRUE}, {"connect", do_device_connect, usage_device_connect, TRUE, TRUE}, {"disconnect", do_devices_disconnect, usage_device_disconnect, TRUE, TRUE}, {"delete", do_devices_delete, usage_device_delete, TRUE, TRUE}, diff --git a/src/nmcli/general.c b/src/nmcli/general.c index d5a6788f..d116ab2c 100644 --- a/src/nmcli/general.c +++ b/src/nmcli/general.c @@ -300,11 +300,12 @@ static void usage_general(void) { g_printerr(_("Usage: nmcli general { COMMAND | help }\n\n" - "COMMAND := { status | hostname | permissions | logging }\n\n" + "COMMAND := { status | hostname | permissions | logging | reload }\n\n" " status\n\n" " hostname []\n\n" " permissions\n\n" - " logging [level ] [domains ]\n\n")); + " logging [level ] [domains ]\n\n" + " reload []\n\n")); } static void diff --git a/src/nmcli/generate-docs-nm-settings-nmcli.xml b/src/nmcli/generate-docs-nm-settings-nmcli.xml index 525b36b6..85df9ce5 100644 --- a/src/nmcli/generate-docs-nm-settings-nmcli.xml +++ b/src/nmcli/generate-docs-nm-settings-nmcli.xml @@ -29,7 +29,7 @@ + description="If specified, directs the device to only associate with the given access point. This capability is highly driver dependent and not supported by all devices. Note: this property does not control the BSSID used when creating an Ad-Hoc network and is unlikely to in the future. Locking a client profile to a certain BSSID will prevent roaming and also disable background scanning. That can be useful, if there is only one access point for the SSID." /> + + + description="List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting "ignore-auto-dns". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." /> + @@ -705,7 +711,7 @@ + description="List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting "ignore-auto-dns". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." /> + description="Configure method for creating the address for use with RFC4862 IPv6 Stateless Address Autoconfiguration. The permitted values are: NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64 (0), NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY (1). NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT_OR_EUI64 (2) or NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT (3). If the property is set to EUI64, the addresses will be generated using the interface tokens derived from hardware address. This makes the host part of the address to stay constant, making it possible to track host's presence when it changes networks. The address changes when the interface hardware is replaced. The value of stable-privacy enables use of cryptographically secure hash of a secret host-specific key along with the connection's stable-id and the network address as specified by RFC7217. This makes it impossible to use the address track host's presence, and makes the address stable when the network interface hardware is replaced. The special values "default" and "default-or-eui64" will fallback to the global connection default in as documented in NetworkManager.conf(5) manual. If the global default is not specified, the fallback value is "stable-privacy" or "eui64", respectively. For libnm, the property defaults to "default" since 1.40. Previously it defaulted to "stable-privacy". On D-Bus, the absence of an addr-gen-mode setting equals "default". For keyfile plugin, the absence of the setting on disk means "default-or-eui64" so that the property doesn't change on upgrade from older versions. Note that this setting is distinct from the Privacy Extensions as configured by "ip6-privacy" property and it does not affect the temporary addresses configured with this option." /> + + description="Whether to enable special handling of the IPv4 default route. If enabled, the IPv4 default route from wireguard.peer-routes will be placed to a dedicated routing-table and two policy routing rules will be added. The fwmark number is also used as routing-table for the default-route, and if fwmark is zero, an unused fwmark/table is chosen automatically. This corresponds to what wg-quick does with Table=auto and what WireGuard calls "Improved Rule-based Routing". Note that for this automatism to work, you usually don't want to set ipv4.gateway, because that will result in a conflicting default route. Leaving this at the default will enable this option automatically if ipv4.never-default is not set and there are any peers that use a default-route as allowed-ips. Since this automatism only makes sense if you also have a peer with an /0 allowed-ips, it is usually not necessary to enable this explicitly. However, you can disable it if you want to configure your own routing and rules." /> diff --git a/src/nmcli/generate-docs-nm-settings-nmcli.xml.in b/src/nmcli/generate-docs-nm-settings-nmcli.xml.in index 525b36b6..85df9ce5 100644 --- a/src/nmcli/generate-docs-nm-settings-nmcli.xml.in +++ b/src/nmcli/generate-docs-nm-settings-nmcli.xml.in @@ -29,7 +29,7 @@ + description="If specified, directs the device to only associate with the given access point. This capability is highly driver dependent and not supported by all devices. Note: this property does not control the BSSID used when creating an Ad-Hoc network and is unlikely to in the future. Locking a client profile to a certain BSSID will prevent roaming and also disable background scanning. That can be useful, if there is only one access point for the SSID." /> + + + description="List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting "ignore-auto-dns". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." /> + @@ -705,7 +711,7 @@ + description="List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting "ignore-auto-dns". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." /> + description="Configure method for creating the address for use with RFC4862 IPv6 Stateless Address Autoconfiguration. The permitted values are: NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64 (0), NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY (1). NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT_OR_EUI64 (2) or NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT (3). If the property is set to EUI64, the addresses will be generated using the interface tokens derived from hardware address. This makes the host part of the address to stay constant, making it possible to track host's presence when it changes networks. The address changes when the interface hardware is replaced. The value of stable-privacy enables use of cryptographically secure hash of a secret host-specific key along with the connection's stable-id and the network address as specified by RFC7217. This makes it impossible to use the address track host's presence, and makes the address stable when the network interface hardware is replaced. The special values "default" and "default-or-eui64" will fallback to the global connection default in as documented in NetworkManager.conf(5) manual. If the global default is not specified, the fallback value is "stable-privacy" or "eui64", respectively. For libnm, the property defaults to "default" since 1.40. Previously it defaulted to "stable-privacy". On D-Bus, the absence of an addr-gen-mode setting equals "default". For keyfile plugin, the absence of the setting on disk means "default-or-eui64" so that the property doesn't change on upgrade from older versions. Note that this setting is distinct from the Privacy Extensions as configured by "ip6-privacy" property and it does not affect the temporary addresses configured with this option." /> + + description="Whether to enable special handling of the IPv4 default route. If enabled, the IPv4 default route from wireguard.peer-routes will be placed to a dedicated routing-table and two policy routing rules will be added. The fwmark number is also used as routing-table for the default-route, and if fwmark is zero, an unused fwmark/table is chosen automatically. This corresponds to what wg-quick does with Table=auto and what WireGuard calls "Improved Rule-based Routing". Note that for this automatism to work, you usually don't want to set ipv4.gateway, because that will result in a conflicting default route. Leaving this at the default will enable this option automatically if ipv4.never-default is not set and there are any peers that use a default-route as allowed-ips. Since this automatism only makes sense if you also have a peer with an /0 allowed-ips, it is usually not necessary to enable this explicitly. However, you can disable it if you want to configure your own routing and rules." /> diff --git a/src/nmcli/meson.build b/src/nmcli/meson.build index fab7329e..a122e2af 100644 --- a/src/nmcli/meson.build +++ b/src/nmcli/meson.build @@ -2,12 +2,10 @@ if enable_nmcli -# The file is called "nmcli-completion" but should be installed with -# name "nmcli". Currently it gets renamed by "tools/meson-post-install.sh", -# but if we depend on meson 0.46.0, we could use "rename" option. install_data( 'nmcli-completion', install_dir: join_paths(nm_datadir, 'bash-completion', 'completions'), + rename: 'nmcli', ) executable( diff --git a/src/nmcli/nmcli.c b/src/nmcli/nmcli.c index 96b8ec4a..bc37a7f0 100644 --- a/src/nmcli/nmcli.c +++ b/src/nmcli/nmcli.c @@ -726,7 +726,7 @@ process_command_line(NmCli *nmc, int argc, char **argv_orig) {"monitor", nmc_command_func_monitor, NULL, TRUE, FALSE}, {"networking", nmc_command_func_networking, NULL, FALSE, FALSE}, {"radio", nmc_command_func_radio, NULL, FALSE, FALSE}, - {"connection", nmc_command_func_connection, NULL, FALSE, FALSE}, + {"connection", nmc_command_func_connection, NULL, FALSE, FALSE, TRUE}, {"device", nmc_command_func_device, NULL, FALSE, FALSE}, {"agent", nmc_command_func_agent, NULL, FALSE, FALSE}, {NULL, nmc_command_func_overview, usage, TRUE, TRUE}, @@ -761,15 +761,16 @@ process_command_line(NmCli *nmc, int argc, char **argv_orig) if (argc == 1 && nmc->complete) { nmc_complete_strings(argv[0], + "--overview", + "--offline", "--terse", "--pretty", "--mode", - "--overview", "--colors", "--escape", "--fields", - "--nocheck", "--get-values", + "--nocheck", "--wait", "--version", "--help"); @@ -783,6 +784,8 @@ process_command_line(NmCli *nmc, int argc, char **argv_orig) if (matches_arg(nmc, &argc, &argv, "-overview", NULL)) { nmc->nmc_config_mutable.overview = TRUE; + } else if (matches_arg(nmc, &argc, &argv, "-offline", NULL)) { + nmc->offline = TRUE; } else if (matches_arg(nmc, &argc, &argv, "-terse", NULL)) { if (nmc->nmc_config.print_output == NMC_PRINT_TERSE) { g_string_printf(nmc->return_text, @@ -1011,6 +1014,8 @@ nmc_cleanup(NmCli *nmc) nm_clear_g_free(&nmc->palette_buffer); + nm_clear_pointer(&nmc->offline_connections, g_ptr_array_unref); + nmc_polkit_agent_fini(nmc); } diff --git a/src/nmcli/nmcli.h b/src/nmcli/nmcli.h index 157aae99..f9b4cc7d 100644 --- a/src/nmcli/nmcli.h +++ b/src/nmcli/nmcli.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (C) 2010 - 2018 Red Hat, Inc. + * Copyright (C) 2010 - 2022 Red Hat, Inc. */ #ifndef NMC_NMCLI_H @@ -52,7 +52,11 @@ typedef enum { NMC_RESULT_COMPLETE_FILE = 65, } NMCResultCode; -typedef enum { NMC_PRINT_TERSE = 0, NMC_PRINT_NORMAL = 1, NMC_PRINT_PRETTY = 2 } NMCPrintOutput; +typedef enum { + NMC_PRINT_TERSE = 0, + NMC_PRINT_NORMAL = 1, + NMC_PRINT_PRETTY = 2, +} NMCPrintOutput; static inline NMMetaAccessorGetType nmc_print_output_to_accessor_get_type(NMCPrintOutput print_output) @@ -65,12 +69,17 @@ nmc_print_output_to_accessor_get_type(NMCPrintOutput print_output) /* === Output fields === */ typedef enum { - NMC_OF_FLAG_FIELD_NAMES = 0x00000001, /* Print field names instead of values */ - NMC_OF_FLAG_SECTION_PREFIX = - 0x00000002, /* Use the first value as section prefix for the other field names - just in multiline */ - NMC_OF_FLAG_MAIN_HEADER_ADD = - 0x00000004, /* Print main header in addition to values/field names */ - NMC_OF_FLAG_MAIN_HEADER_ONLY = 0x00000008, /* Print main header only */ + /* Print field names instead of values */ + NMC_OF_FLAG_FIELD_NAMES = 0x00000001, + + /* Use the first value as section prefix for the other field names - just in multiline */ + NMC_OF_FLAG_SECTION_PREFIX = 0x00000002, + + /* Print main header in addition to values/field names */ + NMC_OF_FLAG_MAIN_HEADER_ADD = 0x00000004, + + /* Print main header only */ + NMC_OF_FLAG_MAIN_HEADER_ONLY = 0x00000008, } NmcOfFlags; typedef struct { @@ -84,23 +93,45 @@ typedef struct _NmcMetaGenericInfo NmcMetaGenericInfo; struct _NmcOutputField { const NMMetaAbstractInfo *info; - int width; /* Width in screen columns */ - void *value; /* Value of current field - char* or char** (NULL-terminated array) */ - gboolean value_is_array; /* Whether value is char** instead of char* */ - gboolean free_value; /* Whether to free the value */ - NmcOfFlags flags; /* Flags - whether and how to print values/field names/headers */ - NMMetaColor color; /* Use this color to print value */ + + /* Width in screen columns */ + int width; + + /* Value of current field - char* or char** (NULL-terminated array) */ + void *value; + + /* Whether value is char** instead of char* */ + bool value_is_array : 1; + + /* Whether to free the value */ + bool free_value : 1; + + NmcOfFlags flags; /* Flags - whether and how to print values/field names/headers */ + NMMetaColor color; /* Use this color to print value */ }; typedef struct _NmcConfig { - NMCPrintOutput print_output; /* Output mode */ - bool use_colors; /* Whether to use colors for output: option '--color' */ - bool multiline_output; /* Multiline output instead of default tabular */ - bool escape_values; /* Whether to escape ':' and '\' in terse tabular mode */ - bool in_editor; /* Whether running the editor - nmcli con edit' */ - bool - show_secrets; /* Whether to display secrets (both input and output): option '--show-secrets' */ - bool overview; /* Overview mode (hide default values) */ + /* Output mode */ + NMCPrintOutput print_output; + + /* Whether to use colors for output: option '--color' */ + bool use_colors; + + /* Multiline output instead of default tabular */ + bool multiline_output : 1; + + /* Whether to escape ':' and '\' in terse tabular mode */ + bool escape_values : 1; + + /* Whether running the editor - nmcli con edit' */ + bool in_editor : 1; + + /* Whether to display secrets (both input and output): option '--show-secrets' */ + bool show_secrets : 1; + + /* Overview mode (hide default values) */ + bool overview : 1; + NmcColorPalette palette; } NmcConfig; @@ -109,40 +140,71 @@ typedef struct { } NmcPagerData; typedef struct _NmcOutputData { - GPtrArray * - output_data; /* GPtrArray of arrays of NmcOutputField structs - accumulates data for output */ + /* GPtrArray of arrays of NmcOutputField structs - accumulates data for output */ + GPtrArray *output_data; } NmcOutputData; /* NmCli - main structure */ typedef struct _NmCli { - NMClient *client; /* Pointer to NMClient of libnm */ + /* Pointer to NMClient of libnm */ + NMClient *client; + + /* Return code of nmcli */ + NMCResultCode return_value; - NMCResultCode return_value; /* Return code of nmcli */ - GString *return_text; /* Reason text */ + /* Reason text */ + GString *return_text; NmcPagerData pager_data; - int timeout; /* Operation timeout */ + /* Operation timeout */ + int timeout; - NMSecretAgentSimple *secret_agent; /* Secret agent */ - GHashTable *pwds_hash; /* Hash table with passwords in passwd-file */ - struct _NMPolkitListener *pk_listener; /* polkit agent listener */ + /* Secret agent */ + NMSecretAgentSimple *secret_agent; + + /* Hash table with passwords in passwd-file */ + GHashTable *pwds_hash; + + /* polkit agent listener */ + struct _NMPolkitListener *pk_listener; + + /* Semaphore indicating whether nmcli should not end or not yet */ + int should_wait; + + /* '--nowait' option; used for passing to callbacks */ + bool nowait_flag : 1; + + /* Whether tabular/multiline mode was specified via '--mode' option */ + bool mode_specified : 1; + + /* Communicate the connection data over stdin/stdout instead of talking to the daemon. */ + bool offline : 1; + + /* Ask for missing parameters: option '--ask' */ + bool ask : 1; + + /* Autocomplete the command line */ + bool complete : 1; + + /* Whether to display status line in connection editor */ + bool editor_status_line : 1; + + /* Whether to ask for confirmation on saving connections with 'autoconnect=yes' */ + bool editor_save_confirmation : 1; - int should_wait; /* Semaphore indicating whether nmcli should not end or not yet */ - gboolean nowait_flag; /* '--nowait' option; used for passing to callbacks */ - gboolean mode_specified; /* Whether tabular/multiline mode was specified via '--mode' option */ union { const NmcConfig nmc_config; NmcConfig nmc_config_mutable; }; - char *required_fields; /* Required fields in output: '--fields' option */ - gboolean ask; /* Ask for missing parameters: option '--ask' */ - gboolean complete; /* Autocomplete the command line */ - gboolean editor_status_line; /* Whether to display status line in connection editor */ - gboolean - editor_save_confirmation; /* Whether to ask for confirmation on saving connections with 'autoconnect=yes' */ - - char *palette_buffer; /* Buffer with sequences for terminal-colors.d(5)-based coloring. */ + + /* Required fields in output: '--fields' option */ + char *required_fields; + + /* Buffer with sequences for terminal-colors.d(5)-based coloring. */ + char *palette_buffer; + + GPtrArray *offline_connections; } NmCli; extern const NmCli *const nm_cli_global_readline; @@ -176,8 +238,18 @@ typedef struct _NMCCommand { const char *cmd; void (*func)(const struct _NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv); void (*usage)(void); - bool needs_client; - bool needs_nm_running; + + /* Ensure a client instance is there before calling the handler (unless --offline has been given). */ + bool needs_client : 1; + + /* Client instance exists *and* the service is actually present on the bus. */ + bool needs_nm_running : 1; + + /* Run the handler without a client even if the comand usually requires one if --offline option was used. */ + bool supports_offline : 1; + + /* With --online, read in a keyfile from standard input before dispatching the handler. */ + bool needs_offline_conn : 1; } NMCCommand; void nmc_command_func_agent(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv); -- cgit 1.3.0-6-gf8a5 From ab0efddcdb48d800e2f938da54cfe3074640792e Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Fri, 26 Aug 2022 20:36:29 +0200 Subject: New upstream version 1.40.0 --- src/nmcli/generate-docs-nm-settings-nmcli.xml | 6 +++--- src/nmcli/generate-docs-nm-settings-nmcli.xml.in | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/nmcli') diff --git a/src/nmcli/generate-docs-nm-settings-nmcli.xml b/src/nmcli/generate-docs-nm-settings-nmcli.xml index 85df9ce5..371081b0 100644 --- a/src/nmcli/generate-docs-nm-settings-nmcli.xml +++ b/src/nmcli/generate-docs-nm-settings-nmcli.xml @@ -420,7 +420,7 @@ + description="Whether to configure MPTCP endpoints and the address flags. If MPTCP is enabled in NetworkManager, it will configure the addresses of the interface as MPTCP endpoints. Note that IPv4 loopback addresses (127.0.0.0/8), IPv4 link local addresses (169.254.0.0/16), the IPv6 loopback address (::1), IPv6 link local addresses (fe80::/10), IPv6 unique local addresses (ULA, fc00::/7) and IPv6 privacy extension addresses (rfc3041, ipv6.ip6-privacy) will be excluded from being configured as endpoints. If "disabled" (0x1), MPTCP handling for the interface is disabled and no endpoints are registered. The "enabled" (0x2) flag means that MPTCP handling is enabled. This flag can also be implied from the presence of other flags. Even when enabled, MPTCP handling will by default still be disabled unless "/proc/sys/net/mptcp/enabled" sysctl is on. NetworkManager does not change the sysctl and this is up to the administrator or distribution. To configure endpoints even if the sysctl is disabled, "also-without-sysctl" (0x4) flag can be used. In that case, NetworkManager doesn't look at the sysctl and configures endpoints regardless. Even when enabled, NetworkManager will only configure MPTCP endpoints for a certain address family, if there is a unicast default route (0.0.0.0/0 or ::/0) in the main routing table. The flag "also-without-default-route" (0x8) can override that. When MPTCP handling is enabled then endpoints are configured with the specified address flags "signal" (0x10), "subflow" (0x20), "backup" (0x40), "fullmesh" (0x80). See ip-mptcp(8) manual for additional information about the flags. If the flags are zero (0x0), the global connection default from NetworkManager.conf is honored. If still unspecified, the fallback is "enabled,subflow". Note that this means that MPTCP is by default done depending on the "/proc/sys/net/mptcp/enabled" sysctl. NetworkManager does not change the MPTCP limits nor enable MPTCP via "/proc/sys/net/mptcp/enabled". That is a host configuration which the admin can change via sysctl and ip-mptcp. Strict reverse path filtering (rp_filter) breaks many MPTCP use cases, so when MPTCP handling for IPv4 addresses on the interface is enabled, NetworkManager would loosen the strict reverse path filtering (1) to the loose setting (2)." /> + description="The gateway associated with this configuration. This is only meaningful if "addresses" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if "never-default" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See "ip4-auto-default-route"." /> + description="The gateway associated with this configuration. This is only meaningful if "addresses" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if "never-default" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See "ip4-auto-default-route"." /> + description="Whether to configure MPTCP endpoints and the address flags. If MPTCP is enabled in NetworkManager, it will configure the addresses of the interface as MPTCP endpoints. Note that IPv4 loopback addresses (127.0.0.0/8), IPv4 link local addresses (169.254.0.0/16), the IPv6 loopback address (::1), IPv6 link local addresses (fe80::/10), IPv6 unique local addresses (ULA, fc00::/7) and IPv6 privacy extension addresses (rfc3041, ipv6.ip6-privacy) will be excluded from being configured as endpoints. If "disabled" (0x1), MPTCP handling for the interface is disabled and no endpoints are registered. The "enabled" (0x2) flag means that MPTCP handling is enabled. This flag can also be implied from the presence of other flags. Even when enabled, MPTCP handling will by default still be disabled unless "/proc/sys/net/mptcp/enabled" sysctl is on. NetworkManager does not change the sysctl and this is up to the administrator or distribution. To configure endpoints even if the sysctl is disabled, "also-without-sysctl" (0x4) flag can be used. In that case, NetworkManager doesn't look at the sysctl and configures endpoints regardless. Even when enabled, NetworkManager will only configure MPTCP endpoints for a certain address family, if there is a unicast default route (0.0.0.0/0 or ::/0) in the main routing table. The flag "also-without-default-route" (0x8) can override that. When MPTCP handling is enabled then endpoints are configured with the specified address flags "signal" (0x10), "subflow" (0x20), "backup" (0x40), "fullmesh" (0x80). See ip-mptcp(8) manual for additional information about the flags. If the flags are zero (0x0), the global connection default from NetworkManager.conf is honored. If still unspecified, the fallback is "enabled,subflow". Note that this means that MPTCP is by default done depending on the "/proc/sys/net/mptcp/enabled" sysctl. NetworkManager does not change the MPTCP limits nor enable MPTCP via "/proc/sys/net/mptcp/enabled". That is a host configuration which the admin can change via sysctl and ip-mptcp. Strict reverse path filtering (rp_filter) breaks many MPTCP use cases, so when MPTCP handling for IPv4 addresses on the interface is enabled, NetworkManager would loosen the strict reverse path filtering (1) to the loose setting (2)." /> + description="The gateway associated with this configuration. This is only meaningful if "addresses" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if "never-default" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See "ip4-auto-default-route"." /> + description="The gateway associated with this configuration. This is only meaningful if "addresses" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if "never-default" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See "ip4-auto-default-route"." />