chiark / gitweb /
udev: link-config: add ethtool support
[elogind.git] / src / udev / net / link-config.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4  This file is part of systemd.
5
6  Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include "link-config.h"
23
24 #include "ethtool-util.h"
25
26 #include "util.h"
27 #include "log.h"
28 #include "strv.h"
29 #include "path-util.h"
30 #include "conf-parser.h"
31 #include "conf-files.h"
32
33 struct link_config_ctx {
34         LIST_HEAD(link_config, links);
35
36         int ethtool_fd;
37
38         char **link_dirs;
39         usec_t *link_dirs_ts_usec;
40 };
41
42 int link_config_ctx_new(link_config_ctx **ret) {
43         link_config_ctx *ctx;
44         int r;
45
46         if (!ret)
47                 return -EINVAL;
48
49         ctx = new0(link_config_ctx, 1);
50         if (!ctx)
51                 return -ENOMEM;
52
53         r = ethtool_connect(&ctx->ethtool_fd);
54         if (r < 0) {
55                 link_config_ctx_free(ctx);
56                 return r;
57         }
58
59         LIST_HEAD_INIT(ctx->links);
60
61         ctx->link_dirs = strv_new("/etc/net/links",
62                                   "/run/net/links",
63                                   "/usr/lib/net/links",
64                                   NULL);
65         if (!ctx->link_dirs) {
66                 log_error("failed to build link config directory array");
67                 link_config_ctx_free(ctx);
68                 return -ENOMEM;
69         }
70         if (!path_strv_canonicalize_uniq(ctx->link_dirs)) {
71                 log_error("failed to canonicalize link config directories\n");
72                 link_config_ctx_free(ctx);
73                 return -ENOMEM;
74         }
75
76         ctx->link_dirs_ts_usec = calloc(strv_length(ctx->link_dirs), sizeof(usec_t));
77         if(!ctx->link_dirs_ts_usec) {
78                 link_config_ctx_free(ctx);
79                 return -ENOMEM;
80         }
81
82         *ret = ctx;
83         return 0;
84 }
85
86 static void link_configs_free(link_config_ctx *ctx) {
87         link_config *link, *link_next;
88
89         if (!ctx)
90                 return;
91
92         LIST_FOREACH_SAFE(links, link, link_next, ctx->links) {
93                 free(link->filename);
94                 free(link->match_path);
95                 free(link->match_driver);
96                 free(link->match_type);
97                 free(link->description);
98
99                 free(link);
100         }
101 }
102
103 void link_config_ctx_free(link_config_ctx *ctx) {
104         if (!ctx)
105                 return;
106
107         close_nointr_nofail(ctx->ethtool_fd);
108         strv_free(ctx->link_dirs);
109         free(ctx->link_dirs_ts_usec);
110         link_configs_free(ctx);
111
112         free(ctx);
113
114         return;
115 }
116
117 static int load_link(link_config_ctx *ctx, const char *filename) {
118         link_config *link;
119         FILE *file;
120         int r;
121
122         file = fopen(filename, "re");
123         if (!file) {
124                 if (errno == ENOENT)
125                         return 0;
126                 else
127                         return errno;
128         }
129
130         link = new0(link_config, 1);
131         if (!link) {
132                 r = log_oom();
133                 goto failure;
134         }
135
136         r = config_parse(NULL, filename, file, "Match\0Link\0Ethernet\0", config_item_perf_lookup,
137                          (void*) link_config_gperf_lookup, false, false, link);
138         if (r < 0) {
139                 log_warning("Colud not parse config file %s: %s", filename, strerror(-r));
140                 goto failure;
141         } else
142                 log_info("Parsed configuration file %s", filename);
143
144         link->filename = strdup(filename);
145
146         LIST_PREPEND(links, ctx->links, link);
147
148         return 0;
149
150 failure:
151         free(link);
152         return r;
153 }
154
155 int link_config_load(link_config_ctx *ctx) {
156         int r;
157         char **files, **f;
158
159         link_configs_free(ctx);
160
161         /* update timestamps */
162         paths_check_timestamp(ctx->link_dirs, ctx->link_dirs_ts_usec, true);
163
164         r = conf_files_list_strv(&files, ".link", NULL, (const char **)ctx->link_dirs);
165         if (r < 0) {
166                 log_error("failed to enumerate link files: %s", strerror(-r));
167                 return r;
168         }
169
170         STRV_FOREACH_BACKWARDS(f, files) {
171                 r = load_link(ctx, *f);
172                 if (r < 0)
173                         return r;
174         }
175
176         return 0;
177 }
178
179 bool link_config_should_reload(link_config_ctx *ctx) {
180         return paths_check_timestamp(ctx->link_dirs, ctx->link_dirs_ts_usec, false);
181 }
182
183 static bool match_config(link_config *match, struct udev_device *device) {
184         const char *property;
185
186         if (match->match_mac) {
187                 property = udev_device_get_sysattr_value(device, "address");
188                 if (!property || !streq(match->match_mac, property)) {
189                         log_debug("Device MAC address (%s) did not match MACAddress=%s", property, match->match_mac);
190                         return 0;
191                 }
192         }
193
194         if (match->match_path) {
195                 property = udev_device_get_property_value(device, "ID_PATH");
196                 if (!property || !streq(match->match_path, property)) {
197                         log_debug("Device's persistent path (%s) did not match Path=%s", property, match->match_path);
198                         return 0;
199                 }
200         }
201
202         if (match->match_driver) {
203                 property = udev_device_get_driver(device);
204                 if (!property || !streq(match->match_driver, property)) {
205                         log_debug("Device driver (%s) did not match Driver=%s", property, match->match_driver);
206                         return 0;
207                 }
208         }
209
210         if (match->match_type) {
211                 property = udev_device_get_devtype(device);
212                 if (!property || !streq(match->match_type, property)) {
213                         log_debug("Device type (%s) did not match Type=%s", property, match->match_type);
214                         return 0;
215                 }
216         }
217
218         return 1;
219 }
220
221 int link_config_get(link_config_ctx *ctx, struct udev_device *device, link_config **ret) {
222         link_config *link;
223
224         LIST_FOREACH(links, link, ctx->links) {
225                 if (!match_config(link, device)) {
226                         log_info("Config file %s does not apply to device %s", link->filename, udev_device_get_sysname(device));
227                 } else {
228                         log_info("Config file %s applies to device %s", link->filename, udev_device_get_sysname(device));
229                         *ret = link;
230                         return 0;
231                 }
232         }
233
234         return -ENOENT;
235 }
236
237 int link_config_apply(link_config_ctx *ctx, link_config *config, struct udev_device *device) {
238         const char *name;
239         int r;
240
241         name = udev_device_get_sysname(device);
242         if (!name)
243                 return -EINVAL;
244
245         log_info("Configuring %s", name);
246
247         if (config->description) {
248                 r = udev_device_set_sysattr_value(device, "ifalias",
249                                                   config->description);
250                 if (r < 0)
251                         log_warning("Could not set description of %s to '%s': %s",
252                                     name, config->description, strerror(-r));
253                 else
254                         log_info("Set link description of %s to '%s'", name,
255                                  config->description);
256         }
257
258         if (config->speed || config->duplex) {
259                 r = ethtool_set_speed(ctx->ethtool_fd, name,
260                                       config->speed, config->duplex);
261                 if (r < 0)
262                         log_warning("Could not set speed or duplex of %s to %u Mbytes (%s): %s",
263                                     name, config->speed, config->duplex, strerror(-r));
264                 else
265                         log_info("Set speed or duplex of %s to %u Mbytes (%s)", name,
266                                  config->speed, config->duplex);
267         }
268
269         if (config->wol) {
270                 r = ethtool_set_wol(ctx->ethtool_fd, name, config->wol);
271                 if (r < 0)
272                         log_warning("Could not set WakeOnLan of %s to %s: %s",
273                                     name, config->wol, strerror(-r));
274                 else
275                         log_info("Set WakeOnLan of %s to %s", name, config->wol);
276         }
277
278         return 0;
279 }