]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
udev/net: introduce [Link] Property=, ImportProperty=, and UnsetProperty= settings
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 5 Jan 2024 11:08:26 +0000 (20:08 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 9 Jan 2024 19:33:51 +0000 (04:33 +0900)
The applied order is equivalent to Environment=, PassEnvironment=, and
UnsetEnvironment= for [Service] or so.

man/systemd.link.xml
src/udev/net/link-config-gperf.gperf
src/udev/net/link-config.c
src/udev/net/link-config.h
src/udev/udev-builtin-net_setup_link.c
src/udev/udevadm-test-builtin.c

index 1f50506bfbc1d82e2f58477fbe5ed7bc954393af..8869d9589f0fbec6663df7dd0ad176384b0bccaf 100644 (file)
           <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>
 
index 240f16e251130ceeb0daafb1def7d44ef5401ee1..42d7cc7ee21a721b1439e00d8c158300cb811b44 100644 (file)
@@ -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)
index c9c4e9c9270feea9dfe016058fbac60b11f057f7..a8b2cc23a2c57747030de4f73c63b636c59f14c8 100644 (file)
@@ -15,6 +15,7 @@
 #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"
@@ -31,6 +32,7 @@
 #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;
@@ -55,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);
@@ -365,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);
@@ -385,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);
@@ -932,6 +946,31 @@ static int link_apply_udev_properties(Link *link, bool test) {
         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;
@@ -988,6 +1027,142 @@ int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link, boo
         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,
index 06666a23ba1535e0964e578fa7a969869f0d6c3e..98cadc212e1fe9fe8380843eda3ba5ccc595a386 100644 (file)
@@ -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,7 +99,7 @@ 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);
 
@@ -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);
index 59a0043422be25cc0d7b08409d6b57044b2a4c2b..fc614443efadcd69730ee03bdefe3374f76f6076 100644 (file)
@@ -41,7 +41,7 @@ static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool
                 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;
index ed71eaa3fab679b14acce69f75d522a8a73e7e40..fdbdb6f59a7060af99fd7da73f331c3c5ca6340b 100644 (file)
@@ -6,6 +6,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#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);