<xi:include href="version-info.xml" xpointer="v211"/>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>Property=</varname></term>
+ <listitem>
+ <para>Set specified udev properties. This takes space separated list of key-value pairs
+ concatenated with equal sign (<literal>=</literal>). Example:
+ <programlisting>Property=HOGE=foo BAR=baz</programlisting>
+ 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.</para>
+
+ <para>This setting is useful to configure the <literal>ID_NET_MANAGED_BY=</literal> property which
+ declares which network management service shall manage the interface, which is respected by
+ systemd-networkd and others. Use
+ <programlisting>Property=ID_NET_MANAGED_BY=io.systemd.Network</programlisting>
+ to declare explicitly that <command>systemd-networkd</command> shall manage the interface, or set
+ the property to something else to declare explicitly it shall not do so. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details how this property is used to match interface names.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>ImportProperty=</varname></term>
+ <listitem>
+ <para>Import specified udev properties from the saved database. This takes space separated list of
+ property names. Example: <programlisting>ImportProperty=HOGE BAR</programlisting>
+ 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.</para>
+ <para>If the same property is also set in <varname>Property=</varname> in the above, then the
+ imported property value will be overridden by the value specified in <varname>Property=</varname>.
+ </para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>UnsetProperty=</varname></term>
+ <listitem>
+ <para>Unset specified udev properties. This takes space separated list of
+ property names. Example: <programlisting>ImportProperty=HOGE BAR</programlisting>
+ 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.</para>
+ <para>This setting is applied after <varname>ImportProperty=</varname> and
+ <varname>Property=</varname> are applied. Hence, if the same property is specified in
+ <varname>ImportProperty=</varname> or <varname>Property=</varname>, then the imported or specified
+ property value will be ignored, and the property will be unset.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term><varname>Alias=</varname></term>
<listitem>
</variablelist>
</refsect1>
+ <refsect1>
+ <title>Specifiers</title>
+
+ <para>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:</para>
+
+ <table class='specifiers'>
+ <title>Specifiers available in unit files</title>
+ <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+ <colspec colname="spec" />
+ <colspec colname="mean" />
+ <colspec colname="detail" />
+ <thead>
+ <row>
+ <entry>Specifier</entry>
+ <entry>Meaning</entry>
+ <entry>Details</entry>
+ </row>
+ </thead>
+ <tbody>
+ <xi:include href="standard-specifiers.xml" xpointer="a"/>
+ <xi:include href="standard-specifiers.xml" xpointer="A"/>
+ <xi:include href="standard-specifiers.xml" xpointer="b"/>
+ <xi:include href="standard-specifiers.xml" xpointer="B"/>
+ <xi:include href="standard-specifiers.xml" xpointer="H"/>
+ <xi:include href="standard-specifiers.xml" xpointer="l"/>
+ <xi:include href="standard-specifiers.xml" xpointer="m"/>
+ <xi:include href="standard-specifiers.xml" xpointer="M"/>
+ <xi:include href="standard-specifiers.xml" xpointer="o"/>
+ <xi:include href="standard-specifiers.xml" xpointer="q"/>
+ <xi:include href="standard-specifiers.xml" xpointer="T"/>
+ <xi:include href="standard-specifiers.xml" xpointer="v"/>
+ <xi:include href="standard-specifiers.xml" xpointer="V"/>
+ <xi:include href="standard-specifiers.xml" xpointer="w"/>
+ <xi:include href="standard-specifiers.xml" xpointer="W"/>
+ </tbody>
+ </tgroup>
+ </table>
+ </refsect1>
+
<refsect1>
<title>Examples</title>
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)
#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 "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 "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;
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);
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);
*link = (Link) {
.device = sd_device_ref(device),
+ .device_db_clone = sd_device_ref(device_db_clone),
};
r = sd_device_get_sysname(device, &link->ifname);
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;
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,
LinkConfig *config;
sd_device *device;
+ sd_device *device_db_clone;
sd_device_action_t action;
char *kind;
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;
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);
/* 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);
return 0;
}
- r = link_new(ctx, &event->rtnl, dev, &link);
+ 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;
#include <stdio.h>
#include <stdlib.h>
+#include "device-private.h"
+#include "device-util.h"
#include "log.h"
#include "udev-builtin.h"
#include "udevadm.h"
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);