diff --git a/Makefile.am b/Makefile.am index c41fdd7fe3..22389a6b90 100644 --- a/Makefile.am +++ b/Makefile.am @@ -83,6 +83,7 @@ userunitdir=$(prefix)/lib/systemd/user userpresetdir=$(prefix)/lib/systemd/user-preset tmpfilesdir=$(prefix)/lib/tmpfiles.d sysctldir=$(prefix)/lib/sysctl.d +linkdir=$(prefix)/lib/net/links pkgincludedir=$(includedir)/systemd systemgeneratordir=$(rootlibexecdir)/system-generators usergeneratordir=$(prefix)/lib/systemd/user-generators @@ -179,6 +180,7 @@ AM_CPPFLAGS = \ -I $(top_srcdir)/src/core \ -I $(top_srcdir)/src/libudev \ -I $(top_srcdir)/src/udev \ + -I $(top_srcdir)/src/udev/net \ -I $(top_builddir)/src/udev \ -I $(top_srcdir)/src/libsystemd-bus \ $(OUR_CPPFLAGS) @@ -2208,6 +2210,7 @@ dist_udevrules_DATA += \ rules/75-tty-description.rules \ rules/78-sound-card.rules \ rules/80-net-name-slot.rules \ + rules/85-net-configure-link.rules \ rules/95-udev-late.rules dist_udevhwdb_DATA = \ @@ -2288,19 +2291,27 @@ libudev_core_la_SOURCES = \ src/udev/udev-builtin-input_id.c \ src/udev/udev-builtin-keyboard.c \ src/udev/udev-builtin-net_id.c \ + src/udev/udev-builtin-net_link.c \ src/udev/udev-builtin-path_id.c \ - src/udev/udev-builtin-usb_id.c + src/udev/udev-builtin-usb_id.c \ + src/udev/net/link-config.h \ + src/udev/net/link-config.c nodist_libudev_core_la_SOURCES = \ src/udev/keyboard-keys-from-name.h \ - src/udev/keyboard-keys-to-name.h + src/udev/keyboard-keys-to-name.h \ + src/udev/net/link-config-gperf.c BUILT_SOURCES += \ $(nodist_libudev_core_la_SOURCES) CLEANFILES += \ src/udev/keyboard-keys-from-name.gperf \ - src/udev/keyboard-keys.txt + src/udev/keyboard-keys.txt \ + src/udev/net/link-config-gperf.c + +EXTRA_DIST += \ + src/udev/net/link-config-gperf.gperf libudev_core_la_CFLAGS = \ $(AM_CFLAGS) \ @@ -4456,6 +4467,8 @@ endif INSTALL_DIRS += \ $(prefix)/lib/modules-load.d \ $(sysconfdir)/modules-load.d \ + $(prefix)/lib/net/links \ + $(sysconfdir)/net/links \ $(prefix)/lib/sysctl.d \ $(sysconfdir)/sysctl.d \ $(prefix)/lib/kernel/install.d \ diff --git a/TODO b/TODO index cbb2cbe9bf..a3ea2f3fe8 100644 --- a/TODO +++ b/TODO @@ -714,6 +714,14 @@ Features: * systemd-run is missing zsh completion scripts +* udev-link-config: + - Make sure ID_PATH is always exported and complete for + network devices where possible, so we can safely rely + on Path= matching + - NamePolicy= replace the current naming rules + - MACPolicy= support 'firmware', 'synthetic' and 'random' + - Check if Driver= is broken, or just my driver (bcma) + External: * dbus: diff --git a/rules/85-net-configure-link.rules b/rules/85-net-configure-link.rules new file mode 100644 index 0000000000..29d689325d --- /dev/null +++ b/rules/85-net-configure-link.rules @@ -0,0 +1,11 @@ +# do not edit this file, it will be overwritten on update + +SUBSYSTEM!="net", GOTO="net_link_end" + +IMPORT{builtin}="path_id" + +ACTION!="add", GOTO="net_link_end" + +RUN{builtin}="net_link" + +LABEL="net_link_end" diff --git a/src/udev/net/.gitignore b/src/udev/net/.gitignore new file mode 100644 index 0000000000..9ca85bacc9 --- /dev/null +++ b/src/udev/net/.gitignore @@ -0,0 +1 @@ +/link-config-gperf.c diff --git a/src/udev/net/Makefile b/src/udev/net/Makefile new file mode 120000 index 0000000000..94aaae2c4d --- /dev/null +++ b/src/udev/net/Makefile @@ -0,0 +1 @@ +../../Makefile \ No newline at end of file diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf new file mode 100644 index 0000000000..c567e6dfaa --- /dev/null +++ b/src/udev/net/link-config-gperf.gperf @@ -0,0 +1,21 @@ +%{ +#include +#include "conf-parser.h" +#include "link-config.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name link_config_gperf_hash +%define lookup-function-name link_config_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Match.MACAddress, config_parse_string, 0, offsetof(link_config, match_mac) +Match.Path, config_parse_string, 0, offsetof(link_config, match_path) +Match.Driver, config_parse_string, 0, offsetof(link_config, match_driver) +Match.Type, config_parse_string, 0, offsetof(link_config, match_type) +Link.Description, config_parse_string, 0, offsetof(link_config, description) diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c new file mode 100644 index 0000000000..7686d87f80 --- /dev/null +++ b/src/udev/net/link-config.c @@ -0,0 +1,244 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "link-config.h" + +#include "util.h" +#include "log.h" +#include "strv.h" +#include "path-util.h" +#include "conf-parser.h" +#include "conf-files.h" + +struct link_config_ctx { + LIST_HEAD(link_config, links); + + char **link_dirs; + usec_t *link_dirs_ts_usec; +}; + +int link_config_ctx_new(link_config_ctx **ret) { + link_config_ctx *ctx; + + if (!ret) + return -EINVAL; + + ctx = new0(link_config_ctx, 1); + if (!ctx) + return -ENOMEM; + + LIST_HEAD_INIT(ctx->links); + + ctx->link_dirs = strv_new("/etc/net/links", + "/run/net/links", + "/usr/lib/net/links", + NULL); + if (!ctx->link_dirs) { + log_error("failed to build link config directory array"); + link_config_ctx_free(ctx); + return -ENOMEM; + } + if (!path_strv_canonicalize_uniq(ctx->link_dirs)) { + log_error("failed to canonicalize link config directories\n"); + link_config_ctx_free(ctx); + return -ENOMEM; + } + + ctx->link_dirs_ts_usec = calloc(strv_length(ctx->link_dirs), sizeof(usec_t)); + if(!ctx->link_dirs_ts_usec) { + link_config_ctx_free(ctx); + return -ENOMEM; + } + + *ret = ctx; + return 0; +} + +static void link_configs_free(link_config_ctx *ctx) { + link_config *link, *link_next; + + if (!ctx) + return; + + LIST_FOREACH_SAFE(links, link, link_next, ctx->links) { + free(link->filename); + free(link->match_path); + free(link->match_driver); + free(link->match_type); + free(link->description); + + free(link); + } +} + +void link_config_ctx_free(link_config_ctx *ctx) { + if (!ctx) + return; + + strv_free(ctx->link_dirs); + free(ctx->link_dirs_ts_usec); + link_configs_free(ctx); + + free(ctx); + + return; +} + +static int load_link(link_config_ctx *ctx, const char *filename) { + link_config *link; + FILE *file; + int r; + + file = fopen(filename, "re"); + if (!file) { + if (errno == ENOENT) + return 0; + else + return errno; + } + + link = new0(link_config, 1); + if (!link) { + r = log_oom(); + goto failure; + } + + r = config_parse(NULL, filename, file, "Match\0Link\0Ethernet\0", config_item_perf_lookup, + (void*) link_config_gperf_lookup, false, false, link); + if (r < 0) { + log_warning("Colud not parse config file %s: %s", filename, strerror(-r)); + goto failure; + } else + log_info("Parsed configuration file %s", filename); + + link->filename = strdup(filename); + + LIST_PREPEND(links, ctx->links, link); + + return 0; + +failure: + free(link); + return r; +} + +int link_config_load(link_config_ctx *ctx) { + int r; + char **files, **f; + + link_configs_free(ctx); + + /* update timestamps */ + paths_check_timestamp(ctx->link_dirs, ctx->link_dirs_ts_usec, true); + + r = conf_files_list_strv(&files, ".link", NULL, (const char **)ctx->link_dirs); + if (r < 0) { + log_error("failed to enumerate link files: %s", strerror(-r)); + return r; + } + + STRV_FOREACH_BACKWARDS(f, files) { + r = load_link(ctx, *f); + if (r < 0) + return r; + } + + return 0; +} + +bool link_config_should_reload(link_config_ctx *ctx) { + return paths_check_timestamp(ctx->link_dirs, ctx->link_dirs_ts_usec, false); +} + +static bool match_config(link_config *match, struct udev_device *device) { + const char *property; + + if (match->match_mac) { + property = udev_device_get_sysattr_value(device, "address"); + if (!property || !streq(match->match_mac, property)) { + log_debug("Device MAC address (%s) did not match MACAddress=%s", property, match->match_mac); + return 0; + } + } + + if (match->match_path) { + property = udev_device_get_property_value(device, "ID_PATH"); + if (!property || !streq(match->match_path, property)) { + log_debug("Device's persistent path (%s) did not match Path=%s", property, match->match_path); + return 0; + } + } + + if (match->match_driver) { + property = udev_device_get_driver(device); + if (!property || !streq(match->match_driver, property)) { + log_debug("Device driver (%s) did not match Driver=%s", property, match->match_driver); + return 0; + } + } + + if (match->match_type) { + property = udev_device_get_devtype(device); + if (!property || !streq(match->match_type, property)) { + log_debug("Device type (%s) did not match Type=%s", property, match->match_type); + return 0; + } + } + + return 1; +} + +int link_config_get(link_config_ctx *ctx, struct udev_device *device, link_config **ret) { + link_config *link; + + LIST_FOREACH(links, link, ctx->links) { + if (!match_config(link, device)) { + log_info("Config file %s does not apply to device %s", link->filename, udev_device_get_sysname(device)); + } else { + log_info("Config file %s applies to device %s", link->filename, udev_device_get_sysname(device)); + *ret = link; + return 0; + } + } + + return -ENOENT; +} + +int link_config_apply(link_config_ctx *ctx, link_config *config, struct udev_device *device) { + const char *name; + int r; + + name = udev_device_get_sysname(device); + if (!name) + return -EINVAL; + + log_info("Configuring %s", name); + + if (config->description) { + r = udev_device_set_sysattr_value(device, "ifalias", config->description); + if (r < 0) + log_warning("Could not set description of %s to '%s': %s", name, config->description, strerror(-r)); + else + log_info("Set link description of %s to '%s'", name, config->description); + } + + return 0; +} diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h new file mode 100644 index 0000000000..da5608ce4c --- /dev/null +++ b/src/udev/net/link-config.h @@ -0,0 +1,55 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#pragma once + +#include "libudev.h" +#include "util.h" +#include "list.h" + + +typedef struct link_config_ctx link_config_ctx; +typedef struct link_config link_config; + +struct link_config { + char *filename; + + char *match_mac; + char *match_path; + char *match_driver; + char *match_type; + + char *description; + + LIST_FIELDS(link_config, links); +}; + +int link_config_ctx_new(link_config_ctx **ret); +void link_config_ctx_free(link_config_ctx *ctx); + +int link_config_load(link_config_ctx *ctx); +bool link_config_should_reload(link_config_ctx *ctx); + +int link_config_get(link_config_ctx *ctx, struct udev_device *device, struct link_config **ret); +int link_config_apply(link_config_ctx *ctx, struct link_config *config, struct udev_device *device); + +/* gperf lookup function */ +const struct ConfigPerfItem* link_config_gperf_lookup(const char *key, unsigned length); diff --git a/src/udev/udev-builtin-net_link.c b/src/udev/udev-builtin-net_link.c new file mode 100644 index 0000000000..d7cbe6a016 --- /dev/null +++ b/src/udev/udev-builtin-net_link.c @@ -0,0 +1,96 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "link-config.h" +#include "udev.h" +#include "log.h" + +link_config_ctx *ctx; + +static int builtin_net_link(struct udev_device *dev, int argc, char **argv, bool test) { + link_config *link; + int r; + + if (argc > 1) { + log_error("This program takes no arguments."); + return EXIT_FAILURE; + } + + r = link_config_get(ctx, dev, &link); + if (r < 0) { + if (r == -ENOENT) { + log_info("No matching link configuration found"); + return EXIT_SUCCESS; + } else { + log_error("Could not get link config"); + return EXIT_FAILURE; + } + } + + r = link_config_apply(ctx, link, dev); + if (r < 0) { + log_error("Could not apply link config"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +static int builtin_net_link_init(struct udev *udev) { + int r; + + if (ctx) + return 0; + + r = link_config_ctx_new(&ctx); + if (r < 0) + return r; + + r = link_config_load(ctx); + if (r < 0) + return r; + + log_debug("Created link configuration context"); + return 0; +} + +static void builtin_net_link_exit(struct udev *udev) { + link_config_ctx_free(ctx); + log_debug("Unloaded link configuration context"); +} + +static bool builtin_net_link_validate(struct udev *udev) { + log_debug("Check if link configuration needs reloading"); + if (!ctx) + return false; + + return link_config_should_reload(ctx); +} + +const struct udev_builtin udev_builtin_net_link = { + .name = "net_link", + .cmd = builtin_net_link, + .init = builtin_net_link_init, + .exit = builtin_net_link_exit, + .validate = builtin_net_link_validate, + .help = "configure network link", + .run_once = false, +}; diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index 6b3a518c2e..85901e8ee3 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -44,6 +44,7 @@ static const struct udev_builtin *builtins[] = { [UDEV_BUILTIN_KMOD] = &udev_builtin_kmod, #endif [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, + [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_link, [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, #ifdef HAVE_ACL diff --git a/src/udev/udev.h b/src/udev/udev.h index 29e96d637e..7cca8b8c83 100644 --- a/src/udev/udev.h +++ b/src/udev/udev.h @@ -153,6 +153,7 @@ enum udev_builtin_cmd { UDEV_BUILTIN_KMOD, #endif UDEV_BUILTIN_NET_ID, + UDEV_BUILTIN_NET_LINK, UDEV_BUILTIN_PATH_ID, UDEV_BUILTIN_USB_ID, #ifdef HAVE_ACL @@ -183,6 +184,7 @@ extern const struct udev_builtin udev_builtin_keyboard; extern const struct udev_builtin udev_builtin_kmod; #endif extern const struct udev_builtin udev_builtin_net_id; +extern const struct udev_builtin udev_builtin_net_link; extern const struct udev_builtin udev_builtin_path_id; extern const struct udev_builtin udev_builtin_usb_id; extern const struct udev_builtin udev_builtin_uaccess;