merge: branch 'ih/perm_unmanaged'

add API to manage/unmanage devices in a persistent way

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2358
This commit is contained in:
Íñigo Huguet 2026-03-06 10:23:58 +00:00
commit 0e2b679afb
16 changed files with 995 additions and 70 deletions

3
NEWS
View file

@ -50,6 +50,9 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
* Add support for GENEVE interface.
* The DHCPv4 internal client now ignores option 3 (Router) if the lease
contains option 121 (Classless Static Route), as recommended by RFC 3442.
* Allow persisting the managed state across reboots from nmcli and the D-Bus API.
* Allow changing the device's administrative state in the kernel at the same
time as a change to the managed state from nmcli and the D-Bus API.
=============================================
NetworkManager-1.56

View file

@ -175,6 +175,9 @@
property has a similar effect to configuring the device as unmanaged via
the keyfile.unmanaged-devices setting in NetworkManager.conf. Changes to
this value are not persistent and lost after NetworkManager restart.
DEPRECATED: 1.58: Use the SetManaged method instead, which supports
additional features like persisting the state to disk
-->
<property name="Managed" type="b" access="readwrite"/>
@ -391,6 +394,20 @@
-->
<method name="Delete"/>
<!--
SetManaged:
@managed:(<link linkend="NMDeviceManaged">NMDeviceManaged</link>) Whether the device is managed. Possible values are "no" (0), "yes" (1) and "reset" (2).
@flags: (<link linkend="NMDeviceManagedFlags">NMDeviceManagedFlags</link>) flags.
@since: 1.58
Set the managed state of the device. With the flags argument different
behaviors can be achieved, like storing the new managed state to disk.
-->
<method name="SetManaged">
<arg name="managed" type="u" direction="in"/>
<arg name="flags" type="u" direction="in"/>
</method>
<!--
StateChanged:
@new_state: (<link linkend="NMDeviceState">NMDeviceState</link>) The new state of the device.

View file

@ -1436,15 +1436,31 @@
</arg>
<arg>
<option>managed</option>
<group>
<arg choice='plain'>--permanent</arg>
<arg choice='plain'>--permanent-only</arg>
</group>
<group choice='req'>
<arg choice='plain'>yes</arg>
<arg choice='plain'>no</arg>
<arg choice='plain'>up</arg>
<arg choice='plain'>down</arg>
<arg choice='plain'>reset</arg>
</group>
</arg>
</term>
<listitem>
<para>Set device properties.</para>
<para>The <option>managed</option> property accepts a <option>--permanent</option>
option to persist the managed state to disk, and not only in runtime. With
<option>--permanent-only</option> only the permanent managed state is set, and not the
runtime managed state. The special values <option>up</option> and <option>down</option>
can be used to set the administrative state of the device at the same time as the runtime
managed state. The <option>reset</option> value clears the explicit managed setting, and
with <option>--permanent</option> or <option>--permanent-only</option> it also removes
the persisted managed setting.</para>
</listitem>
</varlistentry>

View file

@ -5,6 +5,7 @@ src/core/NetworkManagerUtils.c
src/core/devices/adsl/nm-device-adsl.c
src/core/devices/bluetooth/nm-bluez-manager.c
src/core/devices/bluetooth/nm-device-bt.c
src/core/devices/nm-device.c
src/core/devices/nm-device-6lowpan.c
src/core/devices/nm-device-bond.c
src/core/devices/nm-device-bridge.c

View file

@ -14940,6 +14940,241 @@ impl_device_get_applied_connection(NMDBusObject *obj,
/*****************************************************************************/
typedef struct {
NMDeviceManaged managed_state;
NMDeviceManagedFlags managed_flags;
} SetManagedData;
static gboolean
get_managed_match_by_mac(NMDevice *self, NMDeviceManagedFlags flags, gboolean *out, GError **error)
{
gboolean is_fake_hwaddr;
nm_assert(out);
if ((flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC)
&& (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME)) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"cannot match both by 'mac' and by 'interface-name'");
return FALSE;
}
nm_device_get_permanent_hw_address_full(self, TRUE, &is_fake_hwaddr);
if ((flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC) && is_fake_hwaddr) {
g_set_error_literal(
error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"cannot match by 'mac': the device doesn't have a permanent MAC address");
return FALSE;
}
if (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC)
*out = TRUE;
else if (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME)
*out = FALSE;
else
*out = !is_fake_hwaddr;
return TRUE;
}
/**
* set_managed:
* @self: the device
* @managed: the new managed state to set.
* @flags: flags to select different behaviors like storing to disk.
* @error: return location for a #GError, or %NULL
*
* Sets the managed state of the device. It can affect the runtime managed state
* if the %NM_DEVICE_MANAGED_FLAGS_RUNTIME is set, and to the value stored on disk
* (persistent across reboots) state if the %NM_DEVICE_MANAGED_FLAGS_PERMANENT is set.
*
* Returns: %TRUE if the managed state was set successfully, %FALSE otherwise.
*/
static gboolean
set_managed(NMDevice *self, NMDeviceManaged managed, NMDeviceManagedFlags flags, GError **error)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(
NM_IN_SET(managed, NM_DEVICE_MANAGED_NO, NM_DEVICE_MANAGED_YES, NM_DEVICE_MANAGED_RESET));
nm_assert((flags & ~NM_DEVICE_MANAGED_FLAGS_ALL) == 0);
if (!NM_FLAGS_ANY(flags, NM_DEVICE_MANAGED_FLAGS_PERMANENT | NM_DEVICE_MANAGED_FLAGS_RUNTIME)) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
_("set managed: no permanent or runtime was selected"));
return FALSE;
}
if (flags & NM_DEVICE_MANAGED_FLAGS_PERMANENT) {
NMTernary managed_to_disk, old = NM_TERNARY_DEFAULT;
gboolean by_mac;
managed_to_disk = managed == NM_DEVICE_MANAGED_RESET ? NM_TERNARY_DEFAULT : !!managed;
nm_config_get_device_managed(nm_manager_get_config(priv->manager), self, &old, NULL, error);
if (!get_managed_match_by_mac(self, flags, &by_mac, error))
return FALSE;
if (!nm_config_set_device_managed(nm_manager_get_config(priv->manager),
self,
managed_to_disk,
by_mac,
error))
return FALSE;
/* Update the unmanaged flags after the change on disk */
nm_device_set_unmanaged_by_user_conf(self);
if (managed_to_disk != NM_TERNARY_DEFAULT
&& managed_to_disk != !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_USER_CONF)) {
/* We failed to make the new state effective on disk. Maybe the new config
* collides with other config. Try to revert and return error. Otherwise,
* we would set the runtime state correctly, but get an unexpected state
* after a reboot. */
nm_config_set_device_managed(nm_manager_get_config(priv->manager),
self,
old,
by_mac,
NULL);
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
_("failed to persist 'managed=%d' on disk, other configurations may be "
"overriding it"),
managed);
return FALSE;
}
}
if (flags & NM_DEVICE_MANAGED_FLAGS_RUNTIME) {
if (managed == NM_DEVICE_MANAGED_RESET) {
nm_device_set_unmanaged_by_flags(self,
NM_UNMANAGED_USER_EXPLICIT,
NM_UNMAN_FLAG_OP_FORGET,
NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT);
} else {
g_object_set(self, NM_DEVICE_MANAGED, !!managed, NULL);
/* If requested, set the administrative state of the device to UP if the
* new managed state is YES, and to DOWN if it's NO. */
if (flags & NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE) {
if (nm_device_get_ifindex(self))
nm_platform_link_change_flags(nm_device_get_platform(self),
nm_device_get_ifindex(self),
IFF_UP,
!!managed);
}
}
}
return TRUE;
}
static void
set_managed_cb(NMDevice *self,
GDBusMethodInvocation *context,
NMAuthSubject *subject,
GError *error,
gpointer user_data)
{
SetManagedData *set_managed_data = user_data;
NMDeviceManaged managed;
NMDeviceManagedFlags flags;
GError *local = NULL;
managed = set_managed_data->managed_state;
flags = set_managed_data->managed_flags;
nm_g_slice_free(set_managed_data);
if (!error) {
if (!NM_IN_SET(managed,
NM_DEVICE_MANAGED_NO,
NM_DEVICE_MANAGED_YES,
NM_DEVICE_MANAGED_RESET))
g_set_error_literal(&error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"Invalid managed value");
else if ((flags & ~NM_DEVICE_MANAGED_FLAGS_ALL) != 0)
g_set_error_literal(&error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"Invalid flags");
}
if (error) {
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_MANAGED,
self,
FALSE,
NULL,
subject,
error->message);
g_dbus_method_invocation_return_gerror(context, error);
return;
}
if (!set_managed(self, managed, flags, &local)) {
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_MANAGED,
self,
FALSE,
NULL,
subject,
local->message);
g_dbus_method_invocation_take_error(context, g_steal_pointer(&local));
return;
}
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_MANAGED, self, TRUE, NULL, subject, NULL);
g_dbus_method_invocation_return_value(context, NULL);
}
static void
impl_device_set_managed(NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMDevice *self = NM_DEVICE(obj);
gs_free_error GError *error = NULL;
guint32 managed_u;
NMDeviceManaged managed;
guint32 flags_u;
NMDeviceManagedFlags flags;
SetManagedData *set_managed_data;
g_variant_get(parameters, "(uu)", &managed_u, &flags_u);
managed = managed_u;
flags = flags_u;
nm_assert(managed == managed_u && flags == flags_u);
set_managed_data = g_slice_new(SetManagedData);
*set_managed_data = (SetManagedData) {
.managed_state = managed,
.managed_flags = flags,
};
nm_device_auth_request(self,
invocation,
nm_device_get_applied_connection(self),
NM_AUTH_PERMISSION_NETWORK_CONTROL,
TRUE,
NULL,
set_managed_cb,
set_managed_data);
}
/*****************************************************************************/
static void
disconnect_cb(NMDevice *self,
GDBusMethodInvocation *context,
@ -19901,6 +20136,12 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"),
NM_DEFINE_GDBUS_ARG_INFO("version_id", "t"), ), ),
.handle = impl_device_get_applied_connection, ),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
NM_DEFINE_GDBUS_METHOD_INFO_INIT("SetManaged",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS(
NM_DEFINE_GDBUS_ARG_INFO("managed", "u"),
NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), ),
.handle = impl_device_set_managed, ),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Disconnect", ),
.handle = impl_device_disconnect, ),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Delete", ),

View file

@ -39,7 +39,9 @@ typedef struct {
bool activation_lifetime_bound_to_profile_visibility : 1;
bool settings_connection_is_unsaved : 1;
bool settings_connection_is_shadowed_owned : 1;
bool permanent_managed_by_mac : 1;
NMUnmanFlagOp unmanaged_explicit;
NMTernary permanent_managed;
NMActivationReason activation_reason;
gulong dev_exported_change_id;
} DeviceCheckpoint;
@ -496,14 +498,19 @@ nm_checkpoint_rollback(NMCheckpoint *self)
/* Start rolling-back each device */
g_hash_table_iter_init(&iter, priv->devices);
while (g_hash_table_iter_next(&iter, (gpointer *) &device, (gpointer *) &dev_checkpoint)) {
guint32 result = NM_ROLLBACK_RESULT_OK;
guint32 result = NM_ROLLBACK_RESULT_OK;
NMTernary perm_managed = NM_TERNARY_DEFAULT;
gboolean perm_managed_by_mac = FALSE;
gboolean force_perm_managed;
_LOGD("rollback: restoring device %s (state %d, realized %d, explicitly unmanaged %d, "
"connection-unsaved %d, connection-shadowed %d, connection-shadowed-owned %d)",
"permanently managed %d, connection-unsaved %d, connection-shadowed %d, "
"connection-shadowed-owned %d)",
dev_checkpoint->original_dev_name,
(int) dev_checkpoint->state,
dev_checkpoint->realized,
dev_checkpoint->unmanaged_explicit,
dev_checkpoint->permanent_managed,
dev_checkpoint->settings_connection_is_unsaved,
!!dev_checkpoint->settings_connection_shadowed,
dev_checkpoint->settings_connection_is_shadowed_owned);
@ -541,6 +548,43 @@ nm_checkpoint_rollback(NMCheckpoint *self)
NM_DEVICE_STATE_REASON_NOW_MANAGED);
}
force_perm_managed = !nm_config_get_device_managed(nm_config_get(),
device,
&perm_managed,
&perm_managed_by_mac,
NULL);
if (force_perm_managed || (perm_managed != dev_checkpoint->permanent_managed)
|| (dev_checkpoint->permanent_managed != NM_TERNARY_DEFAULT
&& perm_managed_by_mac != dev_checkpoint->permanent_managed_by_mac)) {
gs_free_error GError *error = NULL;
NMUnmanFlagOp set_op;
_LOGD("rollback: restore permanent managed state");
if (!nm_config_set_device_managed(nm_config_get(),
device,
dev_checkpoint->permanent_managed,
dev_checkpoint->permanent_managed_by_mac,
&error)) {
_LOGE("rollback: failed to restore permanent managed state: %s", error->message);
result = NM_ROLLBACK_RESULT_ERR_FAILED;
/* even if this failed, we try to continue the rollback */
}
if (dev_checkpoint->permanent_managed == NM_TERNARY_TRUE)
set_op = NM_UNMAN_FLAG_OP_SET_MANAGED;
else if (dev_checkpoint->permanent_managed == NM_TERNARY_FALSE)
set_op = NM_UNMAN_FLAG_OP_SET_UNMANAGED;
else
set_op = NM_UNMAN_FLAG_OP_FORGET;
nm_device_set_unmanaged_by_flags_queue(device,
NM_UNMANAGED_USER_CONF,
set_op,
NM_DEVICE_STATE_REASON_NOW_MANAGED);
}
if (dev_checkpoint->state == NM_DEVICE_STATE_UNMANAGED) {
if (nm_device_get_state(device) != NM_DEVICE_STATE_UNMANAGED
|| dev_checkpoint->unmanaged_explicit == NM_UNMAN_FLAG_OP_SET_UNMANAGED) {
@ -703,6 +747,8 @@ device_checkpoint_create(NMCheckpoint *self, NMDevice *device)
NMSettingsConnection *settings_connection;
const char *path;
NMActRequest *act_request;
gboolean perm_managed_by_mac;
gs_free_error GError *error = NULL;
nm_assert(NM_IS_DEVICE(device));
nm_assert(nm_device_is_real(device));
@ -728,12 +774,26 @@ device_checkpoint_create(NMCheckpoint *self, NMDevice *device)
} else
dev_checkpoint->unmanaged_explicit = NM_UNMAN_FLAG_OP_FORGET;
if (nm_config_get_device_managed(nm_config_get(),
device,
&dev_checkpoint->permanent_managed,
&perm_managed_by_mac,
NULL)) {
dev_checkpoint->permanent_managed_by_mac = perm_managed_by_mac;
} else {
dev_checkpoint->permanent_managed = NM_TERNARY_DEFAULT;
dev_checkpoint->permanent_managed_by_mac = FALSE;
_LOGW("error getting permanent managed state for %s: %s",
nm_device_get_iface(device),
error->message);
g_clear_error(&error);
}
act_request = nm_device_get_act_request(device);
if (act_request) {
NMSettingsStorage *storage;
gboolean shadowed_owned = FALSE;
const char *shadowed_file;
gs_free_error GError *error = NULL;
NMSettingsStorage *storage;
gboolean shadowed_owned = FALSE;
const char *shadowed_file;
settings_connection = nm_act_request_get_settings_connection(act_request);
applied_connection = nm_act_request_get_applied_connection(act_request);
@ -764,6 +824,7 @@ device_checkpoint_create(NMCheckpoint *self, NMDevice *device)
_LOGW("error reading shadowed connection file for %s: %s",
nm_device_get_iface(device),
error->message);
g_clear_error(&error);
}
}
}

View file

@ -2058,12 +2058,15 @@ _match_section_infos_construct(GKeyFile *keyfile, gboolean is_device)
{
char **groups;
gsize i, j, ngroups;
char *connection_tag = NULL;
char *main_group = NULL;
MatchSectionInfo *match_section_infos = NULL;
const char *prefix;
const char *prefix, *prefix_intern;
prefix =
is_device ? NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE : NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION;
prefix_intern =
is_device ? NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE
: NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION;
/* get the list of existing [connection.\+]/[device.\+] sections.
*
@ -2074,27 +2077,36 @@ _match_section_infos_construct(GKeyFile *keyfile, gboolean is_device)
if (!groups)
return NULL;
if (ngroups > 0) {
gsize l = strlen(prefix);
for (i = 0, j = 0; i < ngroups; i++) {
if (g_str_has_prefix(groups[i], prefix)) {
if (groups[i][l] == '\0')
connection_tag = groups[i];
else
groups[j++] = groups[i];
} else
g_free(groups[i]);
for (i = 0, j = 0; i < ngroups; i++) {
if (nm_streq0(groups[i], prefix)) {
main_group = groups[i];
} else if (nm_streq0(groups[i], prefix_intern)) {
/* [.intern.connection] and [.intern.device] should not exist */
_nm_log(LOGL_WARN,
LOGD_CORE,
0,
NULL,
NULL,
"Invalid [.intern.*] section 'connection' or 'device' found");
g_free(groups[i]);
continue;
} else if (g_str_has_prefix(groups[i], prefix)) {
groups[j++] = groups[i];
} else if (g_str_has_prefix(groups[i], prefix_intern)) {
/* [.intern.connection-whatever] and [.intern.device-whatever] can exist */
groups[j++] = groups[i];
} else {
g_free(groups[i]);
}
ngroups = j;
}
ngroups = j;
if (ngroups == 0 && !connection_tag) {
if (ngroups == 0 && !main_group) {
g_free(groups);
return NULL;
}
match_section_infos = g_new0(MatchSectionInfo, ngroups + 1 + (connection_tag ? 1 : 0));
match_section_infos = g_new0(MatchSectionInfo, ngroups + 1 + (main_group ? 1 : 0));
match_section_infos->is_device = is_device;
for (i = 0; i < ngroups; i++) {
/* pass ownership of @group on... */
@ -2103,9 +2115,9 @@ _match_section_infos_construct(GKeyFile *keyfile, gboolean is_device)
groups[ngroups - i - 1],
is_device);
}
if (connection_tag) {
/* pass ownership of @connection_tag on... */
_match_section_info_init(&match_section_infos[i], keyfile, connection_tag, is_device);
if (main_group) {
/* pass ownership of @main_group on... */
_match_section_info_init(&match_section_infos[i], keyfile, main_group, is_device);
}
g_free(groups);

View file

@ -2076,6 +2076,210 @@ nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled)
g_key_file_unref(keyfile);
}
/*****************************************************************************/
static gboolean
normalize_hwaddr_for_group_name(const char *hwaddr, char *out, GError **error)
{
guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX];
gsize hwaddr_bin_len;
if (!_nm_utils_hwaddr_aton(hwaddr, hwaddr_bin, sizeof(hwaddr_bin), &hwaddr_bin_len)) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"Invalid MAC address: %s",
hwaddr);
return FALSE;
}
nm_utils_bin2hexstr_full(hwaddr_bin, hwaddr_bin_len, '-', TRUE, out);
return TRUE;
}
/**
* nm_config_get_device_managed:
* @self: the NMConfig instance
* @device: the interface
* @out: (out): the managed state of the device
* @error: return location for a #GError, or %NULL
*
* Returns: TRUE if there were no errors, FALSE otherwise.
*/
gboolean
nm_config_get_device_managed(NMConfig *self,
NMDevice *device,
NMTernary *out_managed,
gboolean *out_by_mac,
GError **error)
{
NMConfigPrivate *priv;
const GKeyFile *keyfile = NULL;
gs_free char *group_by_name = NULL;
gs_free char *group_by_mac = NULL;
const char *ifname = nm_device_get_iface(device);
const char *hwaddr = nm_device_get_permanent_hw_address(device);
char mac_group_name[NM_UTILS_HWADDR_LEN_MAX * 3 + 1];
NMTernary val_by_name, val_by_mac = NM_TERNARY_DEFAULT;
g_return_val_if_fail(NM_IS_CONFIG(self), FALSE);
g_return_val_if_fail(NM_CONFIG_GET_PRIVATE(self)->config_data, FALSE);
g_return_val_if_fail(out_managed, FALSE);
g_return_val_if_fail(ifname, FALSE);
priv = NM_CONFIG_GET_PRIVATE(self);
keyfile = _nm_config_data_get_keyfile_intern(priv->config_data);
if (!keyfile) {
NM_SET_OUT(out_managed, NM_TERNARY_DEFAULT);
NM_SET_OUT(out_by_mac, FALSE);
return TRUE;
}
group_by_name =
g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s", ifname);
val_by_name = (NMTernary) nm_config_keyfile_get_boolean(keyfile,
group_by_name,
NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
NM_TERNARY_DEFAULT);
/* Devices without a kernel link (i.e. OVS ports) don't have a MAC address */
if (hwaddr) {
if (!normalize_hwaddr_for_group_name(hwaddr, mac_group_name, error))
return FALSE;
group_by_mac = g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s",
mac_group_name);
val_by_mac = (NMTernary) nm_config_keyfile_get_boolean(keyfile,
group_by_mac,
NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
NM_TERNARY_DEFAULT);
}
if (val_by_name != NM_TERNARY_DEFAULT && val_by_mac == NM_TERNARY_DEFAULT) {
NM_SET_OUT(out_managed, val_by_name);
NM_SET_OUT(out_by_mac, FALSE);
return TRUE;
} else if (val_by_mac != NM_TERNARY_DEFAULT && val_by_name == NM_TERNARY_DEFAULT) {
NM_SET_OUT(out_managed, val_by_mac);
NM_SET_OUT(out_by_mac, TRUE);
return TRUE;
} else if (val_by_name == NM_TERNARY_DEFAULT && val_by_mac == NM_TERNARY_DEFAULT) {
NM_SET_OUT(out_managed, NM_TERNARY_DEFAULT);
NM_SET_OUT(out_by_mac, FALSE);
return TRUE;
} else if (val_by_name == val_by_mac) {
NM_SET_OUT(out_managed, val_by_name);
NM_SET_OUT(out_by_mac, FALSE);
return TRUE;
}
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"Multiple managed states found for device: %s",
nm_device_get_iface(device));
return FALSE;
}
/**
* nm_config_set_device_managed:
* @self: the NMConfig instance
* @device: the NMDevice instance associated with this config change
* @managed: the managed state to set
* @by_mac: if %TRUE, match by MAC address, otherwise by interface name. This is
* only used when @managed = TRUE.
* @error: return location for a #GError, or %NULL
*
* Sets the managed state of the device to the intern keyfile. Here we store the
* configuration received via the D-Bus API. Configurations from other config
* files are still in place and may have higher precedence.
*
* Prior to setting the new state, the existing configuration is removed. If
* @managed is set to %NM_TERNARY_DEFAULT, we only do the removal of the previous
* configuration.
*/
gboolean
nm_config_set_device_managed(NMConfig *self,
NMDevice *device,
NMTernary managed,
gboolean by_mac,
GError **error)
{
NMConfigPrivate *priv;
g_autoptr(GKeyFile) keyfile = NULL;
char *group;
gs_free char *group_by_name = NULL;
gs_free char *group_by_mac = NULL;
gs_free char *match_value = NULL;
gboolean changed = FALSE;
const char *ifname = nm_device_get_iface(device);
const char *hwaddr = nm_device_get_permanent_hw_address(device);
char mac_group_name[NM_UTILS_HWADDR_LEN_MAX * 3 + 1];
g_return_val_if_fail(NM_IS_CONFIG(self), FALSE);
g_return_val_if_fail(NM_CONFIG_GET_PRIVATE(self)->config_data, FALSE);
g_return_val_if_fail(ifname, FALSE);
if (by_mac && !hwaddr) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"the device has no MAC address, but match by MAC was requested");
return FALSE;
}
if (hwaddr && !normalize_hwaddr_for_group_name(hwaddr, mac_group_name, error))
return FALSE;
priv = NM_CONFIG_GET_PRIVATE(self);
keyfile = nm_config_data_clone_keyfile_intern(priv->config_data);
/* Remove existing configs. Search them by group name [.intern.device-manage-*]. In
* the intern file, 'device-manage' sections are only used for this purpose, so we
* won't remove any other device's config. */
group_by_name =
g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s", ifname);
if (g_key_file_remove_group(keyfile, group_by_name, NULL))
changed = TRUE;
/* Devices without a kernel link (i.e. OVS ports) don't have a MAC address */
if (hwaddr) {
group_by_mac = g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s",
mac_group_name);
if (g_key_file_remove_group(keyfile, group_by_mac, NULL))
changed = TRUE;
}
/* If the new state is not explicitly TRUE of FALSE, we only remove the configs */
if (managed == NM_TERNARY_DEFAULT)
goto done;
/* Set new values */
if (by_mac) {
group = group_by_mac;
match_value = g_strdup_printf("mac:%s", hwaddr);
} else {
group = group_by_name;
match_value = g_strdup_printf("interface-name:=%s", ifname);
}
g_key_file_set_value(keyfile, group, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE, match_value);
g_key_file_set_value(keyfile, group, NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, managed ? "1" : "0");
changed = TRUE;
done:
if (changed)
nm_config_set_values(self, keyfile, TRUE, FALSE);
return TRUE;
}
/*****************************************************************************/
/**
* nm_config_set_values:
* @self: the NMConfig instance

View file

@ -142,6 +142,17 @@ gboolean nm_config_set_global_dns(NMConfig *self, NMGlobalDnsConfig *global_dns,
void nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled);
gboolean nm_config_get_device_managed(NMConfig *self,
NMDevice *device,
NMTernary *out_managed,
gboolean *out_by_mac,
GError **error);
gboolean nm_config_set_device_managed(NMConfig *self,
NMDevice *device,
NMTernary managed,
gboolean by_mac,
GError **error);
/* internal defines ... */
extern guint _nm_config_match_nm_version;
extern char *_nm_config_match_env;

View file

@ -311,6 +311,108 @@ test_config_override(void)
g_assert_cmpstr(plugins[3], ==, "delta");
}
static void
test_config_managed(void)
{
NMConfig *config;
const char *CONFIG_USER = BUILD_DIR "/test-config-managed.conf";
const char *CONFIG_INTERN = BUILD_DIR "/test-config-managed-intern.conf";
NMDevice *dev;
gs_free char *group_by_name = NULL;
const char *ifname, *group_by_mac;
NMTernary managed;
gboolean by_mac;
GKeyFile *kf = nm_config_create_keyfile();
dev = nm_test_device_new("11:11:11:11:11:11");
ifname = nm_device_get_iface(dev);
group_by_name =
g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-%s", ifname);
group_by_mac = NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-manage-11-11-11-11-11-11";
g_assert(g_file_set_contents(CONFIG_USER, "", 0, NULL));
g_assert(g_file_set_contents(CONFIG_INTERN, "", 0, NULL));
config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, NULL, "/no/such/dir", "", NULL);
g_assert(nm_config_get_device_managed(config, dev, &managed, NULL, NULL));
g_assert_cmpint(managed, ==, NM_TERNARY_DEFAULT);
/* Matching by name */
NMTST_EXPECT_NM_INFO("config: signal: *");
g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_TRUE, FALSE, NULL));
g_assert(nm_config_get_device_managed(config, dev, &managed, &by_mac, NULL));
g_assert_cmpint(managed, ==, NM_TERNARY_TRUE);
g_assert_false(by_mac);
g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL);
g_assert_false(g_key_file_has_key(kf, group_by_mac, "managed", NULL));
g_assert_true(g_key_file_has_key(kf, group_by_name, "managed", NULL));
g_assert_cmpint(g_key_file_get_integer(kf, group_by_name, "managed", NULL), ==, 1);
NMTST_EXPECT_NM_INFO("config: signal: *");
g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_FALSE, FALSE, NULL));
g_assert(nm_config_get_device_managed(config, dev, &managed, &by_mac, NULL));
g_assert_cmpint(managed, ==, NM_TERNARY_FALSE);
g_assert_false(by_mac);
g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL);
g_assert_false(g_key_file_has_key(kf, group_by_mac, "managed", NULL));
g_assert_true(g_key_file_has_key(kf, group_by_name, "managed", NULL));
g_assert_cmpint(g_key_file_get_integer(kf, group_by_name, "managed", NULL), ==, 0);
/* Matching by MAC address */
NMTST_EXPECT_NM_INFO("config: signal: *");
g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_TRUE, TRUE, NULL));
g_assert(nm_config_get_device_managed(config, dev, &managed, &by_mac, NULL));
g_assert_cmpint(managed, ==, NM_TERNARY_TRUE);
g_assert_true(by_mac);
g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL);
g_assert_false(g_key_file_has_key(kf, group_by_name, "managed", NULL));
g_assert_true(g_key_file_has_key(kf, group_by_mac, "managed", NULL));
g_assert_cmpint(g_key_file_get_integer(kf, group_by_mac, "managed", NULL), ==, 1);
NMTST_EXPECT_NM_INFO("config: signal: *");
g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_FALSE, TRUE, NULL));
g_assert(nm_config_get_device_managed(config, dev, &managed, &by_mac, NULL));
g_assert_cmpint(managed, ==, NM_TERNARY_FALSE);
g_assert_true(by_mac);
g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL);
g_assert_false(g_key_file_has_key(kf, group_by_name, "managed", NULL));
g_assert_true(g_key_file_has_key(kf, group_by_mac, "managed", NULL));
g_assert_cmpint(g_key_file_get_integer(kf, group_by_mac, "managed", NULL), ==, 0);
/* Resetting the managed state */
NMTST_EXPECT_NM_INFO("config: signal: *");
g_assert(nm_config_set_device_managed(config, dev, NM_TERNARY_DEFAULT, FALSE, NULL));
g_assert(nm_config_get_device_managed(config, dev, &managed, NULL, NULL));
g_assert_cmpint(managed, ==, NM_TERNARY_DEFAULT);
g_key_file_load_from_file(kf, CONFIG_INTERN, G_KEY_FILE_NONE, NULL);
g_assert_false(g_key_file_has_key(kf, group_by_name, "managed", NULL));
g_assert_false(g_key_file_has_key(kf, group_by_mac, "managed", NULL));
g_object_unref(config);
/* Both values set in the intern config file, different values */
g_key_file_set_string(kf, group_by_name, "managed", "1");
g_key_file_set_string(kf, group_by_mac, "managed", "0");
g_assert(g_key_file_save_to_file(kf, CONFIG_INTERN, NULL));
config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, NULL, "/no/such/dir", "", NULL);
g_assert(!nm_config_get_device_managed(config, dev, &managed, NULL, NULL));
g_object_unref(config);
/* Both values set in the intern config file, same values */
g_key_file_set_string(kf, group_by_name, "managed", "1");
g_key_file_set_string(kf, group_by_mac, "managed", "1");
g_assert(g_key_file_save_to_file(kf, CONFIG_INTERN, NULL));
config = setup_config(NULL, CONFIG_USER, CONFIG_INTERN, NULL, "/no/such/dir", "", NULL);
g_assert(nm_config_get_device_managed(config, dev, &managed, NULL, NULL));
g_assert_cmpint(managed, ==, NM_TERNARY_TRUE);
g_key_file_unref(kf);
g_object_unref(dev);
g_object_unref(config);
}
static void
test_config_global_dns(void)
{
@ -1412,6 +1514,7 @@ main(int argc, char **argv)
g_test_add_func("/config/set-values", test_config_set_values);
g_test_add_func("/config/global-dns", test_config_global_dns);
g_test_add_func("/config/managed", test_config_managed);
g_test_add_func("/config/connectivity-check", test_config_connectivity_check);
g_test_add_func("/config/signal", test_config_signal);

View file

@ -90,4 +90,7 @@
#define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN \
NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN
#define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE \
NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE
#endif /* __NM_CONFIG_BASE_H__ */

View file

@ -2114,6 +2114,10 @@ global:
nm_device_geneve_get_tos;
nm_device_geneve_get_ttl;
nm_device_geneve_get_type;
nm_device_managed_flags_get_type;
nm_device_managed_get_type;
nm_device_set_managed_async;
nm_device_set_managed_finish;
nm_ip_config_get_clat_address;
nm_ip_config_get_clat_pref64;
nm_setting_geneve_df_get_type;

View file

@ -1446,9 +1446,7 @@ nm_device_get_managed(NMDevice *device)
*
* Since: 1.2
*
* Deprecated: 1.22: Use the async command nm_client_dbus_set_property() on
* nm_object_get_path(), interface %NM_DBUS_INTERFACE_DEVICE to set the
* "Managed" property to a "(b)" boolean value.
* Deprecated: 1.22: Use nm_device_set_managed_async() instead.
* This function is deprecated because it calls a synchronous D-Bus method
* and modifies the content of the NMClient cache client side. Also, it does
* not emit a property changed signal.
@ -1470,6 +1468,72 @@ nm_device_set_managed(NMDevice *device, gboolean managed)
managed);
}
/**
* nm_device_set_managed_async:
* @device: a #NMDevice
* @managed: the managed state
* @flags: the flags argument. See #NMDeviceManagedFlags.
* @cancellable: a #GCancellable, or %NULL
* @callback: callback to be called when the set_managed operation completes
* @user_data: caller-specific data passed to @callback
*
* Asynchronously begins setting the managed state of the device. With the flags
* argument different behaviors can be achieved, such as persisting the state to
* disk or matching the device by MAC address.
*
* Since: 1.58
**/
void
nm_device_set_managed_async(NMDevice *device,
NMDeviceManaged managed,
NMDeviceManagedFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail(NM_IS_DEVICE(device));
g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable));
_nm_client_dbus_call(_nm_object_get_client(device),
device,
nm_device_set_managed_async,
cancellable,
callback,
user_data,
_nm_object_get_path(device),
NM_DBUS_INTERFACE_DEVICE,
"SetManaged",
g_variant_new("(uu)", managed, flags),
G_VARIANT_TYPE("()"),
G_DBUS_CALL_FLAGS_NONE,
NM_DBUS_DEFAULT_TIMEOUT_MSEC,
nm_dbus_connection_call_finish_void_strip_dbus_error_cb);
}
/**
* nm_device_set_managed_finish:
* @device: a #NMDevice
* @result: the result passed to the #GAsyncReadyCallback
* @error: location for a #GError, or %NULL
*
* Gets the result of a call to nm_device_set_managed_async().
*
* Returns: %TRUE on success, %FALSE on error, in which case @error
* will be set.
*
* Since: 1.58
**/
gboolean
nm_device_set_managed_finish(NMDevice *device, GAsyncResult *result, GError **error)
{
g_return_val_if_fail(NM_IS_DEVICE(device), FALSE);
g_return_val_if_fail(nm_g_task_is_valid(result, device, nm_device_set_managed_async), FALSE);
return g_task_propagate_boolean(G_TASK(result), error);
}
/*****************************************************************************/
/**
* nm_device_get_autoconnect:
* @device: a #NMDevice

View file

@ -150,6 +150,15 @@ NM_AVAILABLE_IN_1_2
NM_DEPRECATED_IN_1_22
_NM_DEPRECATED_SYNC_METHOD
void nm_device_set_managed(NMDevice *device, gboolean managed);
NM_AVAILABLE_IN_1_58
void nm_device_set_managed_async(NMDevice *device,
NMDeviceManaged managed,
NMDeviceManagedFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
NM_AVAILABLE_IN_1_58
gboolean nm_device_set_managed_finish(NMDevice *device, GAsyncResult *result, GError **error);
gboolean nm_device_get_autoconnect(NMDevice *device);

View file

@ -1248,6 +1248,54 @@ typedef enum /*< flags >*/ {
NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP = 0x1,
} NMDeviceReapplyFlags;
/**
* NMDeviceManaged:
* @NM_DEVICE_MANAGED_NO: the device is not managed.
* @NM_DEVICE_MANAGED_YES: the device is managed.
* @NM_DEVICE_MANAGED_RESET: reset the device managed state to the default value.
*
* Values for the SetManaged() D-Bus call of a device and nm_device_set_managed_async().
*
* Since: 1.58
*/
typedef enum {
NM_DEVICE_MANAGED_NO = 0,
NM_DEVICE_MANAGED_YES = 1,
NM_DEVICE_MANAGED_RESET = 2,
} NMDeviceManaged;
/**
* NMDeviceManagedFlags:
* @NM_DEVICE_MANAGED_FLAGS_NONE: no flag set.
* @NM_DEVICE_MANAGED_FLAGS_RUNTIME: to set the device managed state to the runtime value.
* @NM_DEVICE_MANAGED_FLAGS_PERMANENT: to set the device managed state to the permanent (on disk) value.
* @NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME: to match the device by name, not by MAC address.
* @NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC: to match the device by MAC address, not by name.
* @NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE: to set the administrative state of the
* device to up if the managed state is %NM_DEVICE_MANAGED_YES, and down if the managed state
* is %NM_DEVICE_MANAGED_NO. If the flag is not set, the administrative state is not changed.
* The flag is ignored for %NM_DEVICE_MANAGED_RESET.
* @NM_DEVICE_MANAGED_FLAGS_ALL: all flags.
*
* Flags for the SetManaged() D-Bus call of a device and nm_device_set_managed_async().
*
* %NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME and %NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC
* are mutually exclusive, and they only make sense together with %NM_DEVICE_MANAGED_FLAGS_PERMANENT.
* If none is set, the matching criteria is selected automatically.
*
* Since: 1.58
*/
typedef enum /*< flags >*/ {
NM_DEVICE_MANAGED_FLAGS_NONE = 0,
NM_DEVICE_MANAGED_FLAGS_RUNTIME = 0x1,
NM_DEVICE_MANAGED_FLAGS_PERMANENT = 0x2,
NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_NAME = 0x4,
NM_DEVICE_MANAGED_FLAGS_PERMANENT_BY_MAC = 0x8,
NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE = 0x10,
NM_DEVICE_MANAGED_FLAGS_ALL = 0x1F, /* <skip> */
} NMDeviceManagedFlags;
/**
* NMTernary:
* @NM_TERNARY_DEFAULT: use the globally-configured default value.

View file

@ -857,7 +857,8 @@ usage(void)
"delete | monitor | wifi | lldp }\n\n"
" status\n\n"
" show [<ifname>]\n\n"
" set [ifname] <ifname> [autoconnect yes|no] [managed yes|no]\n\n"
" set [ifname] <ifname> [autoconnect yes|no] [managed [--permanent|--permanent-only] "
"yes|no|up|down|reset]\n\n"
" connect <ifname>\n\n"
" reapply <ifname>\n\n"
" modify <ifname> ([+|-]<setting>.<property> <value>)+\n\n"
@ -975,14 +976,15 @@ usage_device_delete(void)
static void
usage_device_set(void)
{
nmc_printerr(_("Usage: nmcli device set { ARGUMENTS | help }\n"
"\n"
"ARGUMENTS := DEVICE { PROPERTY [ PROPERTY ... ] }\n"
"DEVICE := [ifname] <ifname> \n"
"PROPERTY := { autoconnect { yes | no } |\n"
" { managed { yes | no }\n"
"\n"
"Modify device properties.\n\n"));
nmc_printerr(_(
"Usage: nmcli device set { ARGUMENTS | help }\n"
"\n"
"ARGUMENTS := DEVICE { PROPERTY [ PROPERTY ... ] }\n"
"DEVICE := [ifname] <ifname> \n"
"PROPERTY := { autoconnect { yes | no } |\n"
" { managed [--permanent | --permanent-only] { yes | no | up | down | reset }\n"
"\n"
"Modify device properties.\n\n"));
}
static void
@ -2816,20 +2818,62 @@ do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const
}
}
typedef struct {
NmCli *nmc;
GSource *timeout_source;
} DeviceSetCbInfo;
static void
device_set_cb_info_finish(DeviceSetCbInfo *info)
{
nm_clear_g_source_inst(&info->timeout_source);
g_slice_free(DeviceSetCbInfo, info);
quit();
}
static gboolean
device_set_timeout_cb(gpointer user_data)
{
DeviceSetCbInfo *cb_info = user_data;
timeout_cb(cb_info->nmc);
device_set_cb_info_finish(cb_info);
return G_SOURCE_REMOVE;
}
static void
device_set_cb(GObject *object, GAsyncResult *result, gpointer user_data)
{
NMDevice *device = NM_DEVICE(object);
DeviceSetCbInfo *info = (DeviceSetCbInfo *) user_data;
NmCli *nmc = info->nmc;
gs_free_error GError *error = NULL;
/* Only 'managed' is treated asynchronously, 'autoconnect' is treated synchronously */
if (!nm_device_set_managed_finish(device, result, &error)) {
g_string_printf(nmc->return_text, _("Error: set managed failed: %s."), error->message);
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
}
device_set_cb_info_finish(info);
}
static void
do_device_set(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv)
{
#define DEV_SET_AUTOCONNECT 0
#define DEV_SET_MANAGED 1
NMDevice *device = NULL;
int i;
NMDevice *device = NULL;
DeviceSetCbInfo *cb_info = NULL;
int i;
struct {
int idx;
gboolean value;
} values[2] = {
[DEV_SET_AUTOCONNECT] = {-1},
[DEV_SET_MANAGED] = {-1},
};
} autoconnect_data = {-1};
struct {
int idx;
NMDeviceManaged value;
guint32 flags;
} managed_data = {-1};
gs_free_error GError *error = NULL;
next_arg(nmc, &argc, &argv, NULL);
@ -2851,49 +2895,120 @@ do_device_set(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *ar
i = 0;
do {
gboolean flag;
if (argc == 1 && nmc->complete)
nmc_complete_strings(*argv, "managed", "autoconnect");
if (matches(*argv, "managed")) {
NMDeviceManaged val;
guint32 flags = 0;
gboolean val_bool;
gboolean perm = FALSE, perm_only = FALSE;
argc--;
argv++;
if (argc == 1 && nmc->complete) {
nmc_complete_strings(*argv,
"true",
"yes",
"on",
"false",
"no",
"off",
"up",
"down",
"reset",
"--permanent",
"--permanent-only");
}
if (managed_data.idx != -1) {
g_string_printf(nmc->return_text, _("Error: 'managed' can only be set once."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
return;
}
while (argc > 0) {
/* --perm matches with --permanent, the most common,
* but --permanent-only requires a exact match */
if (nm_streq0(*argv, "--permanent-only"))
perm_only = TRUE;
else if (matches(*argv, "--permanent"))
perm = TRUE;
else
break;
argc--;
argv++;
}
if (perm_only && perm) {
g_string_printf(
nmc->return_text,
_("Error: '--permanent-only' and '--permanent' cannot be used together."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
return;
} else if (perm) {
flags |= NM_DEVICE_MANAGED_FLAGS_RUNTIME | NM_DEVICE_MANAGED_FLAGS_PERMANENT;
} else if (perm_only) {
flags |= NM_DEVICE_MANAGED_FLAGS_PERMANENT;
} else {
/* If --permanent/--permanent-only are missing, set only the runtime flag, as this
* is how it used to work when these options didn't exist. */
flags |= NM_DEVICE_MANAGED_FLAGS_RUNTIME;
}
if (!argc) {
g_string_printf(nmc->return_text,
_("Error: '%s' argument is missing."),
*(argv - 1));
g_string_printf(nmc->return_text, _("Error: 'managed' argument is missing."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
return;
}
if (argc == 1 && nmc->complete)
nmc_complete_bool(*argv);
if (!nmc_string_to_bool(*argv, &flag, &error)) {
g_string_printf(nmc->return_text, _("Error: 'managed': %s."), error->message);
if (matches(*argv, "up") || matches(*argv, "down")) {
flags |= NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE;
val = matches(*argv, "up") ? NM_DEVICE_MANAGED_YES : NM_DEVICE_MANAGED_NO;
} else if (matches(*argv, "reset")) {
val = NM_DEVICE_MANAGED_RESET;
} else if (nmc_string_to_bool(*argv, &val_bool, NULL)) {
val = val_bool ? NM_DEVICE_MANAGED_YES : NM_DEVICE_MANAGED_NO;
} else {
g_string_printf(
nmc->return_text,
_("Error: 'managed': '%s' is not valid, use 'yes/no/up/down/reset'."),
*argv);
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
return;
}
values[DEV_SET_MANAGED].idx = ++i;
values[DEV_SET_MANAGED].value = flag;
managed_data.idx = i++;
managed_data.value = val;
managed_data.flags = flags;
} else if (matches(*argv, "autoconnect")) {
gboolean val;
argc--;
argv++;
if (!argc) {
g_string_printf(nmc->return_text,
_("Error: '%s' argument is missing."),
*(argv - 1));
g_string_printf(nmc->return_text, _("Error: 'autoconnect' argument is missing."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
return;
}
if (autoconnect_data.idx != -1) {
g_string_printf(nmc->return_text, _("Error: 'autoconnect' can only be set once."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
return;
}
if (argc == 1 && nmc->complete)
nmc_complete_bool(*argv);
if (!nmc_string_to_bool(*argv, &flag, &error)) {
if (!nmc_string_to_bool(*argv, &val, &error)) {
g_string_printf(nmc->return_text, _("Error: 'autoconnect': %s."), error->message);
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
return;
}
values[DEV_SET_AUTOCONNECT].idx = ++i;
values[DEV_SET_AUTOCONNECT].value = flag;
autoconnect_data.idx = i++;
autoconnect_data.value = val;
} else {
g_string_printf(nmc->return_text, _("Error: property '%s' is not known."), *argv);
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
@ -2906,15 +3021,28 @@ do_device_set(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *ar
/* when multiple properties are specified, set them in the order as they
* are specified on the command line. */
if (values[DEV_SET_AUTOCONNECT].idx >= 0 && values[DEV_SET_MANAGED].idx >= 0
&& values[DEV_SET_MANAGED].idx < values[DEV_SET_AUTOCONNECT].idx) {
nm_device_set_managed(device, values[DEV_SET_MANAGED].value);
values[DEV_SET_MANAGED].idx = -1;
for (i = 0; i < 2; i++) {
if (autoconnect_data.idx == i) {
nm_device_set_autoconnect(device, autoconnect_data.value);
}
if (managed_data.idx == i) {
cb_info = g_slice_new0(DeviceSetCbInfo);
cb_info->nmc = nmc;
if (nmc->timeout > 0)
cb_info->timeout_source =
nm_g_timeout_add_seconds_source(nmc->timeout, device_set_timeout_cb, cb_info);
nmc->nowait_flag = (nmc->timeout == 0);
nmc->should_wait++;
nm_device_set_managed_async(device,
managed_data.value,
managed_data.flags,
NULL,
device_set_cb,
cb_info);
}
}
if (values[DEV_SET_AUTOCONNECT].idx >= 0)
nm_device_set_autoconnect(device, values[DEV_SET_AUTOCONNECT].value);
if (values[DEV_SET_MANAGED].idx >= 0)
nm_device_set_managed(device, values[DEV_SET_MANAGED].value);
}
static void