1
0
mirror of https://github.com/systemd/systemd synced 2024-07-09 04:26:06 +00:00

udev/net: introduce [Link] Property=, ImportProperty=, and UnsetProperty= settings

The applied order is equivalent to Environment=, PassEnvironment=, and
UnsetEnvironment= for [Service] or so.
This commit is contained in:
Yu Watanabe 2024-01-05 20:08:26 +09:00
parent 513ca8b6f0
commit 046286e863
6 changed files with 292 additions and 3 deletions

View File

@ -364,6 +364,59 @@
<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>
@ -1260,6 +1313,47 @@
</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>

View 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)

View 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"
@ -38,6 +40,12 @@
#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,

View 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);

View 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;

View 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);