diff --git a/NEWS b/NEWS
index 69c62edef2..14aea8d9f7 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,9 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
* Drop unused, internal systemd DHCPv4 client. This is long
replaced by nettools' n-dhcp4 implementation.
+* The nmcli command now supports --offline argument with "add" and
+ "modify" commands, allowing operation on keyfile-formatted connection
+ profiles without the service running (e.g. during system provisioning).
=============================================
NetworkManager-1.38
diff --git a/man/nmcli-examples.xml b/man/nmcli-examples.xml
index 5744ad12bb..0afa9797a3 100644
--- a/man/nmcli-examples.xml
+++ b/man/nmcli-examples.xml
@@ -614,6 +614,32 @@ Connection 'ethernet-4' (de89cdeb-a3e1-4d53-8fa0-c22546c775f4) successfully
$ nmcli connection add type bluetooth con-name "My Bluetooth Hotspot" autoconnect no ifname btnap0 bluetooth.type nap ipv4.method shared ipv6.method shared
+ Offline use
+$ nmcli --offline con add type ethernet '
+ conn.id eth0 \
+ conn.interface-name eth0 \
+ >/sysroot/etc/NetworkManager/system-connections/eth0.nmconnection
+
+ Creates a connection file in keyfile format without using the NetworkManager service.
+ This allows for use of familiar nmcli syntax in situations
+ where the service is not running, such as during system installation of image
+ provisioning and ensures the resulting file is correctly formatted.
+
+$ nmcli --offline con modify type ethernet '
+ conn.id eth0-ipv6 \
+ ipv4.method disabled \
+ </sysroot/etc/NetworkManager/system-connections/eth0.nmconnection \
+ >/sysroot/etc/NetworkManager/system-connections/eth0-ipv6.nmconnection
+
+ Read and write a connection file without using the NetworkManager service, modifying
+ some properties along the way.
+
+
+ This allows templating of the connection profiles using familiar
+ nmcli syntax in situations where the service is not running.
+
+
+
diff --git a/man/nmcli.xml b/man/nmcli.xml
index d4f27b0b37..ba89b29112 100644
--- a/man/nmcli.xml
+++ b/man/nmcli.xml
@@ -314,6 +314,23 @@
+
+
+
+
+
+
+ Work without a daemon. Makes connection add and
+ connection modify commands accept and produce connection
+ data via standard input/output. Ordinarily, nmcli would communicate with
+ the NetworkManager service.
+
+ The connection data format (keyfile) is described in
+ nm-settings-keyfile5
+ manual.
+
+
+
@@ -928,7 +945,7 @@
- ID
+ ID
option value
@@ -962,7 +979,14 @@
The connection is identified by its name, UUID or D-Bus path. If
ID is ambiguous, a keyword ,
- or can be used.
+ or can be used.
+ The ID is not used with the global
+ option.
+
+ When the global is used, the
+ command reads the connection from the standard input and prints the
+ modified connection to standard output instead of making the
+ the NetworkManager daemon act upon specified connection.
@@ -1096,6 +1120,10 @@
+
+ When the global is used, the
+ command prints the resulting connection to standard output instead of
+ actually adding the connection via the NetworkManager daemon.
diff --git a/src/nmcli/common.c b/src/nmcli/common.c
index f42f76e4c2..710914e211 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);
@@ -1273,12 +1276,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 ecf8e2e298..648583eb1d 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"
@@ -137,6 +138,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 +1068,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 +1429,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)
{
@@ -1951,7 +2067,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.
@@ -1970,7 +2086,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];
@@ -2118,6 +2234,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,
@@ -2258,7 +2379,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)
@@ -2314,7 +2435,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,
@@ -2451,7 +2572,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;
@@ -3275,7 +3396,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;
@@ -3925,7 +4046,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);
}
@@ -4488,7 +4609,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,
@@ -5264,12 +5385,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]);
@@ -5347,15 +5465,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)
{
@@ -5660,6 +5769,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)
{
@@ -5747,7 +5870,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));
@@ -5812,11 +5935,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:
@@ -5838,7 +5957,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) {
@@ -6172,7 +6291,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;
@@ -6189,7 +6308,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;
@@ -8335,7 +8454,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,
@@ -8749,7 +8872,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) {
@@ -8930,11 +9053,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;
@@ -8950,25 +9086,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++;
}
@@ -9266,7 +9396,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)) {
@@ -9767,7 +9897,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);
@@ -9814,7 +9944,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;
@@ -9840,7 +9970,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;
@@ -9904,12 +10034,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/nmcli.c b/src/nmcli/nmcli.c
index c62b6974ed..bc37a7f0f8 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},
@@ -762,6 +762,7 @@ 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",
@@ -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 31b26956d9..922e501418 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
@@ -135,6 +135,8 @@ typedef struct _NmCli {
bool nowait_flag : 1; /* '--nowait' option; used for passing to callbacks */
bool mode_specified : 1; /* Whether tabular/multiline mode was specified via '--mode' option */
+ bool offline : 1; /* Communicate the connection data over stdin/stdout
+ * instead of talking to the daemon. */
bool ask : 1; /* Ask for missing parameters: option '--ask' */
bool complete : 1; /* Autocomplete the command line */
bool editor_status_line : 1; /* Whether to display status line in connection editor */
@@ -148,6 +150,8 @@ typedef struct _NmCli {
char *required_fields; /* Required fields in output: '--fields' option */
char *palette_buffer; /* Buffer with sequences for terminal-colors.d(5)-based coloring. */
+
+ GPtrArray *offline_connections;
} NmCli;
extern const NmCli *const nm_cli_global_readline;
@@ -181,8 +185,15 @@ 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 : 1;
- bool needs_nm_running : 1;
+
+ bool needs_client : 1; /* Ensure a client instance is there before calling
+ * the handler (unless --offline has been given). */
+ bool needs_nm_running : 1; /* Client instance exists *and* the service is
+ * actually present on the bus. */
+ bool supports_offline : 1; /* Run the handler without a client even if the
+ * comand usually requires one if --offline option was used. */
+ bool needs_offline_conn : 1; /* With --online, read in a keyfile from
+ * standard input before dispatching the handler. */
} NMCCommand;
void nmc_command_func_agent(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv);