core: config: implement storing to disk for Device.SetManaged()

If NM_DEVICE_MANAGED_FLAGS_TO_DISK is passed, add an
[.intern.device-ifname] section to NetworkManager-intern.conf so it is
persisted across reboots.

Allow also to clear previously stored configurations by passing
NM_DEVICE_MANAGED_FLAGS_CLEAR_DISK.
This commit is contained in:
Íñigo Huguet 2026-02-12 12:45:09 +01:00 committed by Rahul Rajesh
parent 9f256c3e86
commit 528356a4b2
7 changed files with 202 additions and 7 deletions

View file

@ -397,10 +397,12 @@
<!--
SetManaged:
@managed: Whether the device is managed.
@flags: Flags which would modify the behavior of the SetManaged call. Invalid flags are rejected.
@flags: (<link linkend="NMDeviceManagedFlags">NMDeviceManagedFlags</link>) flags.
@since: 1.58
Set the managed state of the device.
Set the managed state of the device. With the flags argument different
behaviors can be achieved, like storing the new managed state to disk or
clearing previous managed state from disk.
-->
<method name="SetManaged">
<arg name="managed" type="b" direction="in"/>

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

@ -14943,15 +14943,71 @@ typedef struct {
NMDeviceManagedFlags managed_flags;
} SetManagedData;
/**
* 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. If the %NM_DEVICE_MANAGED_FLAGS_TO_DISK
* flag is set, the new state will be persisted to disk so it will be restored
* after a reboot. If the %NM_DEVICE_MANAGED_FLAGS_CLEAR_DISK flag is set, the
* managed flag will be cleared from disk.
*
* Returns: %TRUE if the managed state was set successfully, %FALSE otherwise.
*/
static gboolean
set_managed(NMDevice *self, gboolean managed, NMDeviceManagedFlags flags, GError **error)
{
gboolean old;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean managed_old;
NMTernary managed_to_disk, managed_to_disk_old;
g_object_get(self, NM_DEVICE_MANAGED, &old, NULL);
g_object_set(self, NM_DEVICE_MANAGED, managed, NULL);
if (flags & NM_DEVICE_MANAGED_FLAGS_CLEAR_DISK)
flags |= NM_DEVICE_MANAGED_FLAGS_TO_DISK;
if (flags & NM_DEVICE_MANAGED_FLAGS_TO_DISK) {
managed_to_disk = flags & NM_DEVICE_MANAGED_FLAGS_CLEAR_DISK ? NM_TERNARY_DEFAULT : managed;
managed_to_disk_old = nm_config_get_device_managed(nm_manager_get_config(priv->manager),
nm_device_get_iface(self));
if (!nm_config_set_device_managed(nm_manager_get_config(priv->manager),
self,
managed_to_disk,
flags & NM_DEVICE_MANAGED_FLAGS_BY_MAC,
error))
return FALSE;
/* Update the unmanaged flags after the change on disk */
nm_device_set_unmanaged_by_user_conf(self);
if (!(flags & NM_DEVICE_MANAGED_FLAGS_CLEAR_DISK)
&& !!nm_device_get_unmanaged_flags(self, NM_UNMANAGED_USER_CONF) != !managed) {
/* 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,
managed_to_disk_old,
flags & NM_DEVICE_MANAGED_FLAGS_BY_MAC,
error);
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;
}
}
g_object_get(self, NM_DEVICE_MANAGED, &managed_old, NULL);
g_object_set(self, NM_DEVICE_MANAGED, !!managed, NULL);
g_object_get(self, NM_DEVICE_MANAGED, &managed, NULL);
if (old != managed)
if (managed_old != managed)
_notify(self, PROP_MANAGED);
return TRUE;

View file

@ -2076,6 +2076,121 @@ nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled)
g_key_file_unref(keyfile);
}
/*****************************************************************************/
/**
* nm_config_get_device_managed:
* @self: the NMConfig instance
* @ifname: the interface name
*
* Returns: the current managed state of the device in the intern keyfile. If it
* is not set or it's invalid, returns %NM_TERNARY_DEFAULT.
*/
NMTernary
nm_config_get_device_managed(NMConfig *self, const char *ifname)
{
NMConfigPrivate *priv;
const GKeyFile *keyfile = NULL;
gs_free char *group = NULL;
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);
priv = NM_CONFIG_GET_PRIVATE(self);
keyfile = _nm_config_data_get_keyfile_intern(priv->config_data);
group = g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-%s", ifname);
if (!keyfile)
return NM_TERNARY_DEFAULT;
return (NMTernary) nm_config_keyfile_get_boolean(keyfile,
group,
NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
NM_TERNARY_DEFAULT);
}
/**
* nm_config_set_device_managed:
* @self: the NMConfig instance
* @device: the NMDevice instance associated with this config change
* @ifname: the interface name
* @hwaddr: the hardware address
* @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;
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);
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 && hwaddr, FALSE);
priv = NM_CONFIG_GET_PRIVATE(self);
keyfile = nm_config_data_clone_keyfile_intern(priv->config_data);
group_by_name = g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE "-%s", ifname);
group_by_mac =
g_strdup_printf(NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_DEVICE ".by-mac.%s", hwaddr);
/* Remove existing configs. Search them by group name [.intern.device-*] or [.intern.device.by-mac.*]. In
* the intern file, 'device' sections are only used for this purpose, so we won't remove
* any other device's config. */
if (g_key_file_remove_group(keyfile, group_by_name, NULL)
|| 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)
match_value = g_strdup_printf("mac:%s", hwaddr);
else
match_value = g_strdup_printf("interface-name:=%s", ifname);
g_key_file_set_value(keyfile,
by_mac ? group_by_mac : group_by_name,
NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE,
match_value);
g_key_file_set_value(keyfile,
by_mac ? group_by_mac : group_by_name,
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,13 @@ gboolean nm_config_set_global_dns(NMConfig *self, NMGlobalDnsConfig *global_dns,
void nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled);
NMTernary nm_config_get_device_managed(NMConfig *self, const char *ifname);
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

@ -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

@ -1251,13 +1251,24 @@ typedef enum /*< flags >*/ {
/**
* NMDeviceManagedFlags:
* @NM_DEVICE_MANAGED_FLAGS_NONE: no flag set.
* @NM_DEVICE_MANAGED_FLAGS_TO_DISK: to also persist the device managed state to disk.
* @NM_DEVICE_MANAGED_FLAGS_BY_MAC: to match the device by MAC address, not by name.
* This option only makes sense together with %NM_DEVICE_MANAGED_FLAGS_TO_DISK.
* @NM_DEVICE_MANAGED_FLAGS_CLEAR_DISK: to clear the managed flag from disk.
* If set, the other flags and the 'managed' argument are ignored.
* @NM_DEVICE_MANAGED_FLAGS_SET_ADMIN_STATE: to set the administrative state of the
* device to up if the managed state is TRUE, and down if the managed state is FALSE.
* If the flag is not set, the administrative state is not changed.
*
* Flags for the SetManaged() D-Bus call of a device and nm_device_set_managed_async().
*
* Since: 1.58
*/
typedef enum /*< flags >*/ {
NM_DEVICE_MANAGED_FLAGS_NONE = 0,
NM_DEVICE_MANAGED_FLAGS_NONE = 0,
NM_DEVICE_MANAGED_FLAGS_TO_DISK = 0x1,
NM_DEVICE_MANAGED_FLAGS_BY_MAC = 0x2,
NM_DEVICE_MANAGED_FLAGS_CLEAR_DISK = 0x4,
} NMDeviceManagedFlags;
/**