]> git.ipfire.org Git - people/ms/systemd.git/blob - device.c
monitor udev for device changes
[people/ms/systemd.git] / device.c
1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
2
3 #include <errno.h>
4 #include <sys/epoll.h>
5 #include <libudev.h>
6
7 #include "unit.h"
8 #include "device.h"
9 #include "strv.h"
10 #include "log.h"
11
12 static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = {
13 [DEVICE_DEAD] = UNIT_INACTIVE,
14 [DEVICE_AVAILABLE] = UNIT_ACTIVE
15 };
16
17 static const char* const state_string_table[_DEVICE_STATE_MAX] = {
18 [DEVICE_DEAD] = "dead",
19 [DEVICE_AVAILABLE] = "available"
20 };
21
22 static void device_done(Unit *u) {
23 Device *d = DEVICE(u);
24
25 assert(d);
26 free(d->sysfs);
27 }
28
29 static void device_set_state(Device *d, DeviceState state) {
30 DeviceState old_state;
31 assert(d);
32
33 old_state = d->state;
34 d->state = state;
35
36 log_debug("%s changed %s → %s", unit_id(UNIT(d)), state_string_table[old_state], state_string_table[state]);
37
38 unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state]);
39 }
40
41 static int device_coldplug(Unit *u) {
42 Device *d = DEVICE(u);
43
44 assert(d);
45 assert(d->state == DEVICE_DEAD);
46
47 if (d->sysfs)
48 device_set_state(d, DEVICE_AVAILABLE);
49
50 return 0;
51 }
52
53 static void device_dump(Unit *u, FILE *f, const char *prefix) {
54 Device *d = DEVICE(u);
55
56 assert(d);
57
58 fprintf(f,
59 "%sDevice State: %s\n"
60 "%sSysfs Path: %s\n",
61 prefix, state_string_table[d->state],
62 prefix, strna(d->sysfs));
63 }
64
65 static UnitActiveState device_active_state(Unit *u) {
66 assert(u);
67
68 return state_translation_table[DEVICE(u)->state];
69 }
70
71 static int device_add_escaped_name(Unit *u, const char *prefix, const char *dn, bool make_id) {
72 char *e;
73 int r;
74
75 assert(u);
76 assert(dn);
77 assert(dn[0] == '/');
78
79 if (!(e = unit_name_escape_path(prefix, dn+1, ".device")))
80 return -ENOMEM;
81
82 r = unit_add_name(u, e);
83
84 if (r >= 0 && make_id)
85 unit_choose_id(u, e);
86
87 free(e);
88
89 if (r < 0 && r != -EEXIST)
90 return r;
91
92 return 0;
93 }
94
95 static int device_process_new_device(Manager *m, struct udev_device *dev, bool update_state) {
96 const char *dn, *names, *wants, *sysfs;
97 Unit *u = NULL;
98 int r;
99 char *e, *w, *state;
100 size_t l;
101 bool delete;
102 struct udev_list_entry *item = NULL, *first = NULL;
103
104 assert(m);
105
106 /* Check whether this entry is even relevant for us. */
107 dn = udev_device_get_devnode(dev);
108 names = udev_device_get_property_value(dev, "SYSTEMD_NAMES");
109 wants = udev_device_get_property_value(dev, "SYSTEMD_WANTS");
110
111 if (!dn && !names && !wants)
112 return 0;
113
114 /* Ok, seems kinda interesting. Now, let's see if this one
115 * already exists. */
116
117 if (!(sysfs = udev_device_get_syspath(dev)))
118 return -ENOMEM;
119
120 assert(sysfs[0] == '/');
121 if (!(e = unit_name_escape_path("sysfs-", sysfs+1, ".device")))
122 return -ENOMEM;
123
124 if (!(u = manager_get_unit(m, e))) {
125 const char *model;
126
127 delete = true;
128
129 if (!(u = unit_new(m))) {
130 free(e);
131 return -ENOMEM;
132 }
133
134 r = unit_add_name(u, e);
135 free(e);
136
137 if (r < 0)
138 goto fail;
139
140 if (!(DEVICE(u)->sysfs = strdup(sysfs))) {
141 r = -ENOMEM;
142 goto fail;
143 }
144
145 if ((model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE")) ||
146 (model = udev_device_get_property_value(dev, "ID_MODEL")))
147 if ((r = unit_set_description(u, model)) < 0)
148 goto fail;
149
150 unit_add_to_load_queue(u);
151 } else {
152 delete = false;
153 free(e);
154 }
155
156 if (dn)
157 if ((r = device_add_escaped_name(u, "node-", dn, true)) < 0)
158 goto fail;
159
160 first = udev_device_get_devlinks_list_entry(dev);
161 udev_list_entry_foreach(item, first)
162 if ((r = device_add_escaped_name(u, "node-", udev_list_entry_get_name(item), false)) < 0)
163 goto fail;
164
165 if (names) {
166 FOREACH_WORD(w, l, names, state) {
167 if (!(e = strndup(w, l)))
168 goto fail;
169
170 r = unit_add_name(u, e);
171 free(e);
172
173 if (r < 0 && r != -EEXIST)
174 goto fail;
175 }
176 }
177
178 if (wants) {
179 FOREACH_WORD(w, l, wants, state) {
180 if (!(e = strndup(w, l)))
181 goto fail;
182
183 r = unit_add_dependency_by_name(u, UNIT_WANTS, e);
184 free(e);
185
186 if (r < 0)
187 goto fail;
188 }
189 }
190
191 if (update_state) {
192 manager_dispatch_load_queue(u->meta.manager);
193 device_set_state(DEVICE(u), DEVICE_AVAILABLE);
194 }
195
196 return 0;
197
198 fail:
199 if (delete && u)
200 unit_free(u);
201 return r;
202 }
203
204 static int device_process_path(Manager *m, const char *path, bool update_state) {
205 int r;
206 struct udev_device *dev;
207
208 assert(m);
209 assert(path);
210
211 if (!(dev = udev_device_new_from_syspath(m->udev, path))) {
212 log_warning("Failed to get udev device object from udev for path %s.", path);
213 return -ENOMEM;
214 }
215
216 r = device_process_new_device(m, dev, update_state);
217 udev_device_unref(dev);
218 return r;
219 }
220
221 static int device_process_removed_device(Manager *m, struct udev_device *dev) {
222 const char *sysfs;
223 char *e;
224 Unit *u;
225 Device *d;
226
227 assert(m);
228 assert(dev);
229
230 if (!(sysfs = udev_device_get_syspath(dev)))
231 return -ENOMEM;
232
233 assert(sysfs[0] == '/');
234 if (!(e = unit_name_escape_path("sysfs-", sysfs+1, ".device")))
235 return -ENOMEM;
236
237 u = manager_get_unit(m, e);
238 free(e);
239
240 if (!u)
241 return 0;
242
243 d = DEVICE(u);
244 free(d->sysfs);
245 d->sysfs = NULL;
246
247 device_set_state(d, DEVICE_DEAD);
248 return 0;
249 }
250
251 static void device_shutdown(Manager *m) {
252 assert(m);
253
254 if (m->udev_monitor)
255 udev_monitor_unref(m->udev_monitor);
256
257 if (m->udev)
258 udev_unref(m->udev);
259 }
260
261 static int device_enumerate(Manager *m) {
262 struct epoll_event ev;
263 int r;
264 struct udev_enumerate *e = NULL;
265 struct udev_list_entry *item = NULL, *first = NULL;
266
267 assert(m);
268
269 if (!(m->udev = udev_new()))
270 return -ENOMEM;
271
272 if (!(m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"))) {
273 r = -ENOMEM;
274 goto fail;
275 }
276
277 if (udev_monitor_enable_receiving(m->udev_monitor) < 0) {
278 r = -EIO;
279 goto fail;
280 }
281
282 m->udev_watch.type = WATCH_UDEV;
283 m->udev_watch.fd = udev_monitor_get_fd(m->udev_monitor);
284
285 zero(ev);
286 ev.events = EPOLLIN;
287 ev.data.ptr = &m->udev_watch;
288
289 if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_watch.fd, &ev) < 0)
290 return -errno;
291
292 if (!(e = udev_enumerate_new(m->udev))) {
293 r = -ENOMEM;
294 goto fail;
295 }
296
297 if (udev_enumerate_scan_devices(e) < 0) {
298 r = -EIO;
299 goto fail;
300 }
301
302 first = udev_enumerate_get_list_entry(e);
303 udev_list_entry_foreach(item, first)
304 device_process_path(m, udev_list_entry_get_name(item), false);
305
306 udev_enumerate_unref(e);
307 return 0;
308
309 fail:
310 if (e)
311 udev_enumerate_unref(e);
312
313 device_shutdown(m);
314 return r;
315 }
316
317 void device_fd_event(Manager *m, int events) {
318 struct udev_device *dev;
319 int r;
320 const char *action;
321
322 assert(m);
323 assert(events == EPOLLIN);
324
325 log_debug("got udev event");
326
327 if (!(dev = udev_monitor_receive_device(m->udev_monitor))) {
328 log_error("Failed to receive device.");
329 return;
330 }
331
332 if (!(action = udev_device_get_action(dev))) {
333 log_error("Failed to get udev action string.");
334 goto fail;
335 }
336
337 if (streq(action, "remove")) {
338 if ((r = device_process_removed_device(m, dev)) < 0) {
339 log_error("Failed to process udev device event: %s", strerror(-r));
340 goto fail;
341 }
342 } else {
343 if ((r = device_process_new_device(m, dev, true)) < 0) {
344 log_error("Failed to process udev device event: %s", strerror(-r));
345 goto fail;
346 }
347 }
348
349 fail:
350 udev_device_unref(dev);
351 }
352
353 const UnitVTable device_vtable = {
354 .suffix = ".device",
355
356 .init = unit_load_fragment_and_dropin,
357 .done = device_done,
358 .coldplug = device_coldplug,
359
360 .dump = device_dump,
361
362 .active_state = device_active_state,
363
364 .enumerate = device_enumerate,
365 .shutdown = device_shutdown
366 };