diff --git a/TODO b/TODO
index 130da3e8d06..9b6a52f5cfb 100644
--- a/TODO
+++ b/TODO
@@ -137,9 +137,6 @@ Features:
to read them from. This way the data doesn't remain in the SMBIOS blob during
runtime, but only in the credentials fs.
-* In .link files add support for setting ID_NET_MANAGED_BY= udev field via some
- high-level setting. Possibly also add setting to add arbitrary udev fields.
-
* add a new ExecStart= flag that inserts the configured user's shell as first
word in the command line. (maybe use character '.'). Usecase: tool such as
uid0 can use that to spawn the target user's default shell.
diff --git a/man/systemd.link.xml b/man/systemd.link.xml
index 4d99cd88f6b..8869d9589f0 100644
--- a/man/systemd.link.xml
+++ b/man/systemd.link.xml
@@ -361,7 +361,60 @@
A description of the device.
-
+
+
+
+
+ Property=
+
+ Set specified udev properties. This takes space separated list of key-value pairs
+ concatenated with equal sign (=). Example:
+ Property=HOGE=foo BAR=baz
+ This option supports simple specifier expansion, see the Specifiers section below.
+ This option can be specified multiple times. If an empty string is assigned, then the all previous
+ assignments are cleared.
+
+ This setting is useful to configure the ID_NET_MANAGED_BY= property which
+ declares which network management service shall manage the interface, which is respected by
+ systemd-networkd and others. Use
+ Property=ID_NET_MANAGED_BY=io.systemd.Network
+ to declare explicitly that systemd-networkd shall manage the interface, or set
+ the property to something else to declare explicitly it shall not do so. See
+ systemd.network5
+ for details how this property is used to match interface names.
+
+
+
+
+
+ ImportProperty=
+
+ Import specified udev properties from the saved database. This takes space separated list of
+ property names. Example: ImportProperty=HOGE BAR
+ This option supports simple specifier expansion, see the Specifiers section below.
+ This option can be specified multiple times. If an empty string is assigned, then the all previous
+ assignments are cleared.
+ If the same property is also set in Property= in the above, then the
+ imported property value will be overridden by the value specified in Property=.
+
+
+
+
+
+
+ UnsetProperty=
+
+ Unset specified udev properties. This takes space separated list of
+ property names. Example: ImportProperty=HOGE BAR
+ This option supports simple specifier expansion, see the Specifiers section below.
+ This option can be specified multiple times. If an empty string is assigned, then the all previous
+ assignments are cleared.
+ This setting is applied after ImportProperty= and
+ Property= are applied. Hence, if the same property is specified in
+ ImportProperty= or Property=, then the imported or specified
+ property value will be ignored, and the property will be unset.
+
+
@@ -369,7 +422,7 @@
The ifalias interface property is set to this value.
-
+
@@ -1260,6 +1313,47 @@
+
+ Specifiers
+
+ Some settings resolve specifiers which may be used to write generic unit files referring to runtime
+ or unit parameters that are replaced when the unit files are loaded. Specifiers must be known and
+ resolvable for the setting to be valid. The following specifiers are understood:
+
+
+ Specifiers available in unit files
+
+
+
+
+
+
+ Specifier
+ Meaning
+ Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Examples
diff --git a/src/libsystemd/sd-device/device-util.h b/src/libsystemd/sd-device/device-util.h
index a9a9b7ad988..534a296715b 100644
--- a/src/libsystemd/sd-device/device-util.h
+++ b/src/libsystemd/sd-device/device-util.h
@@ -10,6 +10,7 @@
#include "alloc-util.h"
#include "log.h"
#include "macro.h"
+#include "strv.h"
#define device_unref_and_replace(a, b) \
unref_and_replace_full(a, b, sd_device_ref, sd_device_unref)
@@ -105,3 +106,10 @@ char** device_make_log_fields(sd_device *device);
bool device_in_subsystem(sd_device *device, const char *subsystem);
bool device_is_devtype(sd_device *device, const char *devtype);
+
+static inline bool device_property_can_set(const char *property) {
+ return property &&
+ !STR_IN_SET(property,
+ "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER",
+ "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS");
+}
diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf
index 240f16e2511..42d7cc7ee21 100644
--- a/src/udev/net/link-config-gperf.gperf
+++ b/src/udev/net/link-config-gperf.gperf
@@ -38,6 +38,9 @@ Match.Credential, config_parse_net_condition,
Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(LinkConfig, conditions)
Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(LinkConfig, conditions)
Link.Description, config_parse_string, 0, offsetof(LinkConfig, description)
+Link.Property, config_parse_udev_property, 0, offsetof(LinkConfig, properties)
+Link.ImportProperty, config_parse_udev_property_name, 0, offsetof(LinkConfig, import_properties)
+Link.UnsetProperty, config_parse_udev_property_name, 0, offsetof(LinkConfig, unset_properties)
Link.MACAddressPolicy, config_parse_mac_address_policy, 0, offsetof(LinkConfig, mac_address_policy)
Link.MACAddress, config_parse_hw_addr, 0, offsetof(LinkConfig, hw_addr)
Link.NamePolicy, config_parse_name_policy, 0, offsetof(LinkConfig, name_policy)
diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c
index 910ec2709e5..a8b2cc23a2c 100644
--- a/src/udev/net/link-config.c
+++ b/src/udev/net/link-config.c
@@ -15,6 +15,8 @@
#include "creds-util.h"
#include "device-private.h"
#include "device-util.h"
+#include "env-util.h"
+#include "escape.h"
#include "ethtool-util.h"
#include "fd-util.h"
#include "fileio.h"
@@ -30,12 +32,20 @@
#include "path-util.h"
#include "proc-cmdline.h"
#include "random-util.h"
+#include "specifier.h"
#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
+#include "udev-builtin.h"
#include "utf8.h"
+static const Specifier link_specifier_table[] = {
+ COMMON_SYSTEM_SPECIFIERS,
+ COMMON_TMP_SPECIFIERS,
+ {}
+};
+
struct LinkConfigContext {
LIST_HEAD(LinkConfig, configs);
int ethtool_fd;
@@ -53,6 +63,9 @@ static LinkConfig* link_config_free(LinkConfig *config) {
condition_free_list(config->conditions);
free(config->description);
+ strv_free(config->properties);
+ strv_free(config->import_properties);
+ strv_free(config->unset_properties);
free(config->name_policy);
free(config->name);
strv_free(config->alternative_names);
@@ -363,18 +376,20 @@ Link *link_free(Link *link) {
return NULL;
sd_device_unref(link->device);
+ sd_device_unref(link->device_db_clone);
free(link->kind);
strv_free(link->altnames);
return mfree(link);
}
-int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link **ret) {
+int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, sd_device *device_db_clone, Link **ret) {
_cleanup_(link_freep) Link *link = NULL;
int r;
assert(ctx);
assert(rtnl);
assert(device);
+ assert(device_db_clone);
assert(ret);
link = new(Link, 1);
@@ -383,6 +398,7 @@ int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link
*link = (Link) {
.device = sd_device_ref(device),
+ .device_db_clone = sd_device_ref(device_db_clone),
};
r = sd_device_get_sysname(device, &link->ifname);
@@ -921,21 +937,69 @@ static int link_apply_sr_iov_config(Link *link, sd_netlink **rtnl) {
return 0;
}
-int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link) {
+static int link_apply_udev_properties(Link *link, bool test) {
+ LinkConfig *config;
+ sd_device *device;
+
+ assert(link);
+
+ config = ASSERT_PTR(link->config);
+ device = ASSERT_PTR(link->device);
+
+ /* 1. apply ImportProperty=. */
+ STRV_FOREACH(p, config->import_properties)
+ (void) udev_builtin_import_property(device, link->device_db_clone, test, *p);
+
+ /* 2. apply Property=. */
+ STRV_FOREACH(p, config->properties) {
+ _cleanup_free_ char *key = NULL;
+ const char *eq;
+
+ eq = strchr(*p, '=');
+ if (!eq)
+ continue;
+
+ key = strndup(*p, eq - *p);
+ if (!key)
+ return log_oom();
+
+ (void) udev_builtin_add_property(device, test, key, eq + 1);
+ }
+
+ /* 3. apply UnsetProperty=. */
+ STRV_FOREACH(p, config->unset_properties)
+ (void) udev_builtin_add_property(device, test, *p, NULL);
+
+ /* 4. set the default properties. */
+ (void) udev_builtin_add_property(device, test, "ID_NET_LINK_FILE", config->filename);
+
+ _cleanup_free_ char *joined = NULL;
+ STRV_FOREACH(d, config->dropins) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = xescape(*d, ":");
+ if (!escaped)
+ return log_oom();
+
+ if (!strextend_with_separator(&joined, ":", escaped))
+ return log_oom();
+ }
+
+ (void) udev_builtin_add_property(device, test, "ID_NET_LINK_FILE_DROPINS", joined);
+
+ if (link->new_name)
+ (void) udev_builtin_add_property(device, test, "ID_NET_NAME", link->new_name);
+
+ return 0;
+}
+
+int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link, bool test) {
int r;
assert(ctx);
assert(rtnl);
assert(link);
- if (!IN_SET(link->action, SD_DEVICE_ADD, SD_DEVICE_BIND, SD_DEVICE_MOVE)) {
- log_link_debug(link, "Skipping to apply .link settings on '%s' uevent.",
- device_action_to_string(link->action));
-
- link->new_name = link->ifname;
- return 0;
- }
-
r = link_apply_ethtool_settings(link, &ctx->ethtool_fd);
if (r < 0)
return r;
@@ -956,9 +1020,149 @@ int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link) {
if (r < 0)
return r;
+ r = link_apply_udev_properties(link, test);
+ if (r < 0)
+ return r;
+
return 0;
}
+int config_parse_udev_property(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char ***properties = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ *properties = strv_free(*properties);
+ return 0;
+ }
+
+ for (const char *p = rvalue;; ) {
+ _cleanup_free_ char *word = NULL, *resolved = NULL, *key = NULL;
+ const char *eq;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid syntax, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ r = specifier_printf(word, SIZE_MAX, link_specifier_table, NULL, NULL, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to resolve specifiers in %s, ignoring assignment: %m", word);
+ continue;
+ }
+
+ /* The restriction for udev property is not clear. Let's apply the one for environment variable here. */
+ if (!env_assignment_is_valid(resolved)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property, ignoring assignment: %s", word);
+ continue;
+ }
+
+ assert_se(eq = strchr(resolved, '='));
+ key = strndup(resolved, eq - resolved);
+ if (!key)
+ return log_oom();
+
+ if (!device_property_can_set(key)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property name '%s', ignoring assignment: %s", key, resolved);
+ continue;
+ }
+
+ r = strv_env_replace_consume(properties, TAKE_PTR(resolved));
+ if (r < 0)
+ return log_error_errno(r, "Failed to update properties: %m");
+ }
+}
+
+int config_parse_udev_property_name(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char ***properties = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ *properties = strv_free(*properties);
+ return 0;
+ }
+
+ for (const char *p = rvalue;; ) {
+ _cleanup_free_ char *word = NULL, *resolved = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid syntax, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ r = specifier_printf(word, SIZE_MAX, link_specifier_table, NULL, NULL, &resolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to resolve specifiers in %s, ignoring assignment: %m", word);
+ continue;
+ }
+
+ /* The restriction for udev property is not clear. Let's apply the one for environment variable here. */
+ if (!env_name_is_valid(resolved)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property name, ignoring assignment: %s", resolved);
+ continue;
+ }
+
+ if (!device_property_can_set(resolved)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid udev property name, ignoring assignment: %s", resolved);
+ continue;
+ }
+
+ r = strv_consume(properties, TAKE_PTR(resolved));
+ if (r < 0)
+ return log_error_errno(r, "Failed to update properties: %m");
+ }
+}
+
int config_parse_ifalias(
const char *unit,
const char *filename,
diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h
index bab9d12970a..98cadc212e1 100644
--- a/src/udev/net/link-config.h
+++ b/src/udev/net/link-config.h
@@ -31,6 +31,7 @@ typedef struct Link {
LinkConfig *config;
sd_device *device;
+ sd_device *device_db_clone;
sd_device_action_t action;
char *kind;
@@ -51,6 +52,9 @@ struct LinkConfig {
LIST_HEAD(Condition, conditions);
char *description;
+ char **properties;
+ char **import_properties;
+ char **unset_properties;
struct hw_addr_data hw_addr;
MACAddressPolicy mac_address_policy;
NamePolicy *name_policy;
@@ -95,12 +99,12 @@ int link_load_one(LinkConfigContext *ctx, const char *filename);
int link_config_load(LinkConfigContext *ctx);
bool link_config_should_reload(LinkConfigContext *ctx);
-int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link **ret);
+int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, sd_device *device_db_clone, Link **ret);
Link *link_free(Link *link);
DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free);
int link_get_config(LinkConfigContext *ctx, Link *link);
-int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link);
+int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link, bool test);
const char *mac_address_policy_to_string(MACAddressPolicy p) _const_;
MACAddressPolicy mac_address_policy_from_string(const char *p) _pure_;
@@ -108,6 +112,8 @@ MACAddressPolicy mac_address_policy_from_string(const char *p) _pure_;
/* gperf lookup function */
const struct ConfigPerfItem* link_config_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+CONFIG_PARSER_PROTOTYPE(config_parse_udev_property);
+CONFIG_PARSER_PROTOTYPE(config_parse_udev_property_name);
CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
CONFIG_PARSER_PROTOTYPE(config_parse_rx_tx_queues);
CONFIG_PARSER_PROTOTYPE(config_parse_txqueuelen);
diff --git a/src/udev/udev-builtin-net_setup_link.c b/src/udev/udev-builtin-net_setup_link.c
index a308a211fb0..fc614443efa 100644
--- a/src/udev/udev-builtin-net_setup_link.c
+++ b/src/udev/udev-builtin-net_setup_link.c
@@ -1,8 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
+#include "device-private.h"
#include "device-util.h"
-#include "escape.h"
#include "errno-util.h"
#include "link-config.h"
#include "log.h"
@@ -15,13 +15,33 @@ static LinkConfigContext *ctx = NULL;
static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool test) {
sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
_cleanup_(link_freep) Link *link = NULL;
- _cleanup_free_ char *joined = NULL;
int r;
if (argc > 1)
return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
- r = link_new(ctx, &event->rtnl, dev, &link);
+ sd_device_action_t action;
+ r = sd_device_get_action(dev, &action);
+ if (r < 0)
+ return log_device_error_errno(dev, r, "Failed to get action: %m");
+
+ if (!IN_SET(action, SD_DEVICE_ADD, SD_DEVICE_BIND, SD_DEVICE_MOVE)) {
+ log_device_debug(dev, "Skipping to apply .link settings on '%s' uevent.",
+ device_action_to_string(action));
+
+ /* Import previously assigned .link file name. */
+ (void) udev_builtin_import_property(dev, event->dev_db_clone, test, "ID_NET_LINK_FILE");
+ (void) udev_builtin_import_property(dev, event->dev_db_clone, test, "ID_NET_LINK_FILE_DROPINS");
+
+ /* Set ID_NET_NAME= with the current interface name. */
+ const char *value;
+ if (sd_device_get_sysname(dev, &value) >= 0)
+ (void) udev_builtin_add_property(dev, test, "ID_NET_NAME", value);
+
+ return 0;
+ }
+
+ r = link_new(ctx, &event->rtnl, dev, event->dev_db_clone, &link);
if (r == -ENODEV) {
log_device_debug_errno(dev, r, "Link vanished while getting information, ignoring.");
return 0;
@@ -39,31 +59,14 @@ static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool
return log_device_error_errno(dev, r, "Failed to get link config: %m");
}
- r = link_apply_config(ctx, &event->rtnl, link);
+ r = link_apply_config(ctx, &event->rtnl, link, test);
if (r == -ENODEV)
log_device_debug_errno(dev, r, "Link vanished while applying configuration, ignoring.");
else if (r < 0)
log_device_warning_errno(dev, r, "Could not apply link configuration, ignoring: %m");
- udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE", link->config->filename);
- if (link->new_name)
- udev_builtin_add_property(dev, test, "ID_NET_NAME", link->new_name);
-
event->altnames = TAKE_PTR(link->altnames);
- STRV_FOREACH(d, link->config->dropins) {
- _cleanup_free_ char *escaped = NULL;
-
- escaped = xescape(*d, ":");
- if (!escaped)
- return log_oom();
-
- if (!strextend_with_separator(&joined, ":", escaped))
- return log_oom();
- }
-
- udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE_DROPINS", joined);
-
return 0;
}
diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c
index ebeadc3f3d1..c4057d63c35 100644
--- a/src/udev/udev-builtin-path_id.c
+++ b/src/udev/udev-builtin-path_id.c
@@ -644,7 +644,6 @@ static int find_real_nvme_parent(sd_device *dev, sd_device **ret) {
static void add_id_with_usb_revision(sd_device *dev, bool test, char *path) {
char *p;
- int r;
assert(dev);
assert(path);
@@ -660,9 +659,7 @@ static void add_id_with_usb_revision(sd_device *dev, bool test, char *path) {
if (p[1] != '-')
return;
- r = udev_builtin_add_property(dev, test, "ID_PATH_WITH_USB_REVISION", path);
- if (r < 0)
- log_device_debug_errno(dev, r, "Failed to add ID_PATH_WITH_USB_REVISION property, ignoring: %m");
+ (void) udev_builtin_add_property(dev, test, "ID_PATH_WITH_USB_REVISION", path);
/* Drop the USB revision specifier for backward compatibility. */
memmove(p - 1, p + 1, strlen(p + 1) + 1);
@@ -671,7 +668,6 @@ static void add_id_with_usb_revision(sd_device *dev, bool test, char *path) {
static void add_id_tag(sd_device *dev, bool test, const char *path) {
char tag[UDEV_NAME_SIZE];
size_t i = 0;
- int r;
/* compose valid udev tag name */
for (const char *p = path; *p; p++) {
@@ -697,9 +693,7 @@ static void add_id_tag(sd_device *dev, bool test, const char *path) {
i--;
tag[i] = '\0';
- r = udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
- if (r < 0)
- log_device_debug_errno(dev, r, "Failed to add ID_PATH_TAG property, ignoring: %m");
+ (void) udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag);
}
static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) {
@@ -859,9 +853,7 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test)
add_id_with_usb_revision(dev, test, path);
- r = udev_builtin_add_property(dev, test, "ID_PATH", path);
- if (r < 0)
- log_device_debug_errno(dev, r, "Failed to add ID_PATH property, ignoring: %m");
+ (void) udev_builtin_add_property(dev, test, "ID_PATH", path);
add_id_tag(dev, test, path);
@@ -871,7 +863,7 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test)
* ID_PATH_ATA_COMPAT
*/
if (compat_path)
- udev_builtin_add_property(dev, test, "ID_PATH_ATA_COMPAT", compat_path);
+ (void) udev_builtin_add_property(dev, test, "ID_PATH_ATA_COMPAT", compat_path);
return 0;
}
diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c
index bcc2018c6fb..6caea8eccee 100644
--- a/src/udev/udev-builtin.c
+++ b/src/udev/udev-builtin.c
@@ -154,3 +154,26 @@ int udev_builtin_add_propertyf(sd_device *dev, bool test, const char *key, const
return udev_builtin_add_property(dev, test, key, val);
}
+
+int udev_builtin_import_property(sd_device *dev, sd_device *src, bool test, const char *key) {
+ const char *val;
+ int r;
+
+ assert(dev);
+ assert(key);
+
+ if (!src)
+ return 0;
+
+ r = sd_device_get_property_value(src, key, &val);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_device_debug_errno(src, r, "Failed to get property \"%s\", ignoring: %m", key);
+
+ r = udev_builtin_add_property(dev, test, key, val);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h
index fcd41d615de..c7a48b0201c 100644
--- a/src/udev/udev-builtin.h
+++ b/src/udev/udev-builtin.h
@@ -84,5 +84,6 @@ void udev_builtin_list(void);
bool udev_builtin_should_reload(void);
int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val);
int udev_builtin_add_propertyf(sd_device *dev, bool test, const char *key, const char *valf, ...) _printf_(4, 5);
+int udev_builtin_import_property(sd_device *dev, sd_device *src, bool test, const char *key);
int udev_builtin_hwdb_lookup(sd_device *dev, const char *prefix, const char *modalias,
const char *filter, bool test);
diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c
index 9d01e5866c4..3ec675746bc 100644
--- a/src/udev/udev-rules.c
+++ b/src/udev/udev-rules.c
@@ -691,9 +691,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
}
if (!is_match) {
- if (STR_IN_SET(attr,
- "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER",
- "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS"))
+ if (!device_property_can_set(attr))
return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
"Invalid ENV attribute. '%s' cannot be set.", attr);
diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c
index ed71eaa3fab..fdbdb6f59a7 100644
--- a/src/udev/udevadm-test-builtin.c
+++ b/src/udev/udevadm-test-builtin.c
@@ -6,6 +6,8 @@
#include
#include
+#include "device-private.h"
+#include "device-util.h"
#include "log.h"
#include "udev-builtin.h"
#include "udevadm.h"
@@ -104,6 +106,15 @@ int builtin_main(int argc, char *argv[], void *userdata) {
goto finish;
}
+ if (arg_action != SD_DEVICE_REMOVE) {
+ /* For net_setup_link */
+ r = device_clone_with_db(dev, &event->dev_db_clone);
+ if (r < 0) {
+ log_device_error_errno(dev, r, "Failed to clone device: %m");
+ goto finish;
+ }
+ }
+
r = udev_builtin_run(event, cmd, arg_command, true);
if (r < 0) {
log_debug_errno(r, "Builtin command '%s' fails: %m", arg_command);
diff --git a/test/units/testsuite-17.link-property.sh b/test/units/testsuite-17.link-property.sh
new file mode 100755
index 00000000000..a43ad22b434
--- /dev/null
+++ b/test/units/testsuite-17.link-property.sh
@@ -0,0 +1,201 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# shellcheck source=test/units/util.sh
+. "$(dirname "$0")"/util.sh
+
+udevadm control --log-level=debug
+
+mkdir -p /run/systemd/network/
+cat >/run/systemd/network/10-test.link </run/systemd/network/10-test.link.d/10-override.conf </run/systemd/network/10-test.link.d/11-override.conf </run/systemd/network/10-test.link.d/12-override.conf </run/systemd/network/10-test.link.d/13-override.conf <