]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
57fa1d09 TG |
2 | |
3 | #include <ctype.h> | |
57fa1d09 | 4 | #include <net/if.h> |
07630cea | 5 | #include <sys/types.h> |
57fa1d09 TG |
6 | |
7 | #include "sd-device.h" | |
8 | ||
b5efdb8a | 9 | #include "alloc-util.h" |
57fa1d09 TG |
10 | #include "device-internal.h" |
11 | #include "device-private.h" | |
07630cea | 12 | #include "device-util.h" |
3ffd4af2 | 13 | #include "fd-util.h" |
07630cea | 14 | #include "fileio.h" |
f4f15635 | 15 | #include "fs-util.h" |
07630cea LP |
16 | #include "hashmap.h" |
17 | #include "macro.h" | |
18 | #include "mkdir.h" | |
d8b4d14d | 19 | #include "nulstr-util.h" |
6bedfcbb | 20 | #include "parse-util.h" |
07630cea | 21 | #include "path-util.h" |
07630cea | 22 | #include "set.h" |
e7f781e4 | 23 | #include "stdio-util.h" |
8b43440b | 24 | #include "string-table.h" |
07630cea LP |
25 | #include "string-util.h" |
26 | #include "strv.h" | |
27 | #include "strxcpyx.h" | |
e4de7287 | 28 | #include "tmpfile-util.h" |
ee104e11 | 29 | #include "user-util.h" |
57fa1d09 TG |
30 | |
31 | int device_add_property(sd_device *device, const char *key, const char *value) { | |
32 | int r; | |
33 | ||
34 | assert(device); | |
35 | assert(key); | |
36 | ||
37 | r = device_add_property_aux(device, key, value, false); | |
38 | if (r < 0) | |
39 | return r; | |
40 | ||
41 | if (key[0] != '.') { | |
42 | r = device_add_property_aux(device, key, value, true); | |
43 | if (r < 0) | |
44 | return r; | |
45 | } | |
46 | ||
47 | return 0; | |
48 | } | |
49 | ||
57fa1d09 TG |
50 | void device_set_devlink_priority(sd_device *device, int priority) { |
51 | assert(device); | |
52 | ||
53 | device->devlink_priority = priority; | |
54 | } | |
55 | ||
56 | void device_set_is_initialized(sd_device *device) { | |
57 | assert(device); | |
58 | ||
59 | device->is_initialized = true; | |
60 | } | |
61 | ||
62 | int device_ensure_usec_initialized(sd_device *device, sd_device *device_old) { | |
dc5042c0 | 63 | usec_t when; |
57fa1d09 TG |
64 | |
65 | assert(device); | |
66 | ||
67 | if (device_old && device_old->usec_initialized > 0) | |
dc5042c0 | 68 | when = device_old->usec_initialized; |
57fa1d09 | 69 | else |
dc5042c0 | 70 | when = now(CLOCK_MONOTONIC); |
57fa1d09 | 71 | |
dc5042c0 | 72 | return device_set_usec_initialized(device, when); |
57fa1d09 TG |
73 | } |
74 | ||
57fa1d09 TG |
75 | uint64_t device_get_properties_generation(sd_device *device) { |
76 | assert(device); | |
77 | ||
78 | return device->properties_generation; | |
79 | } | |
80 | ||
81 | uint64_t device_get_tags_generation(sd_device *device) { | |
82 | assert(device); | |
83 | ||
84 | return device->tags_generation; | |
85 | } | |
86 | ||
87 | uint64_t device_get_devlinks_generation(sd_device *device) { | |
88 | assert(device); | |
89 | ||
90 | return device->devlinks_generation; | |
91 | } | |
92 | ||
93 | int device_get_devnode_mode(sd_device *device, mode_t *mode) { | |
94 | int r; | |
95 | ||
96 | assert(device); | |
57fa1d09 TG |
97 | |
98 | r = device_read_db(device); | |
99 | if (r < 0) | |
100 | return r; | |
101 | ||
f5fbe71d | 102 | if (device->devmode == MODE_INVALID) |
dcfbde3a YW |
103 | return -ENOENT; |
104 | ||
78ffb476 YW |
105 | if (mode) |
106 | *mode = device->devmode; | |
57fa1d09 TG |
107 | |
108 | return 0; | |
109 | } | |
110 | ||
111 | int device_get_devnode_uid(sd_device *device, uid_t *uid) { | |
112 | int r; | |
113 | ||
114 | assert(device); | |
57fa1d09 TG |
115 | |
116 | r = device_read_db(device); | |
117 | if (r < 0) | |
118 | return r; | |
119 | ||
f5fbe71d | 120 | if (device->devuid == UID_INVALID) |
dcfbde3a YW |
121 | return -ENOENT; |
122 | ||
78ffb476 YW |
123 | if (uid) |
124 | *uid = device->devuid; | |
57fa1d09 TG |
125 | |
126 | return 0; | |
127 | } | |
128 | ||
129 | static int device_set_devuid(sd_device *device, const char *uid) { | |
130 | unsigned u; | |
131 | int r; | |
132 | ||
133 | assert(device); | |
134 | assert(uid); | |
135 | ||
136 | r = safe_atou(uid, &u); | |
137 | if (r < 0) | |
138 | return r; | |
139 | ||
140 | r = device_add_property_internal(device, "DEVUID", uid); | |
141 | if (r < 0) | |
142 | return r; | |
143 | ||
144 | device->devuid = u; | |
145 | ||
146 | return 0; | |
147 | } | |
148 | ||
149 | int device_get_devnode_gid(sd_device *device, gid_t *gid) { | |
150 | int r; | |
151 | ||
152 | assert(device); | |
57fa1d09 TG |
153 | |
154 | r = device_read_db(device); | |
155 | if (r < 0) | |
156 | return r; | |
157 | ||
f5fbe71d | 158 | if (device->devgid == GID_INVALID) |
dcfbde3a YW |
159 | return -ENOENT; |
160 | ||
78ffb476 YW |
161 | if (gid) |
162 | *gid = device->devgid; | |
57fa1d09 TG |
163 | |
164 | return 0; | |
165 | } | |
166 | ||
167 | static int device_set_devgid(sd_device *device, const char *gid) { | |
168 | unsigned g; | |
169 | int r; | |
170 | ||
171 | assert(device); | |
172 | assert(gid); | |
173 | ||
174 | r = safe_atou(gid, &g); | |
175 | if (r < 0) | |
176 | return r; | |
177 | ||
178 | r = device_add_property_internal(device, "DEVGID", gid); | |
179 | if (r < 0) | |
180 | return r; | |
181 | ||
182 | device->devgid = g; | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
20798980 | 187 | int device_set_action(sd_device *device, sd_device_action_t a) { |
5ebd3fc3 YW |
188 | int r; |
189 | ||
190 | assert(device); | |
20798980 | 191 | assert(a >= 0 && a < _SD_DEVICE_ACTION_MAX); |
5ebd3fc3 | 192 | |
a1130022 | 193 | r = device_add_property_internal(device, "ACTION", device_action_to_string(a)); |
5ebd3fc3 YW |
194 | if (r < 0) |
195 | return r; | |
196 | ||
197 | device->action = a; | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
20798980 YW |
202 | static int device_set_action_from_string(sd_device *device, const char *action) { |
203 | sd_device_action_t a; | |
204 | ||
205 | assert(device); | |
206 | assert(action); | |
207 | ||
208 | a = device_action_from_string(action); | |
209 | if (a < 0) | |
210 | return a; | |
211 | ||
212 | return device_set_action(device, a); | |
213 | } | |
214 | ||
5ebd3fc3 YW |
215 | static int device_set_seqnum(sd_device *device, const char *str) { |
216 | uint64_t seqnum; | |
217 | int r; | |
218 | ||
219 | assert(device); | |
220 | assert(str); | |
221 | ||
222 | r = safe_atou64(str, &seqnum); | |
223 | if (r < 0) | |
224 | return r; | |
225 | if (seqnum == 0) | |
226 | return -EINVAL; | |
227 | ||
228 | r = device_add_property_internal(device, "SEQNUM", str); | |
229 | if (r < 0) | |
230 | return r; | |
231 | ||
232 | device->seqnum = seqnum; | |
233 | ||
234 | return 0; | |
235 | } | |
236 | ||
122adcb2 LB |
237 | static int device_set_diskseq(sd_device *device, const char *str) { |
238 | uint64_t diskseq; | |
239 | int r; | |
240 | ||
241 | assert(device); | |
242 | assert(str); | |
243 | ||
244 | r = safe_atou64(str, &diskseq); | |
245 | if (r < 0) | |
246 | return r; | |
247 | if (diskseq == 0) | |
248 | return -EINVAL; | |
249 | ||
250 | r = device_add_property_internal(device, "DISKSEQ", str); | |
251 | if (r < 0) | |
252 | return r; | |
253 | ||
254 | device->diskseq = diskseq; | |
255 | ||
256 | return 0; | |
257 | } | |
258 | ||
401cb614 | 259 | static int device_amend(sd_device *device, const char *key, const char *value) { |
57fa1d09 TG |
260 | int r; |
261 | ||
262 | assert(device); | |
263 | assert(key); | |
264 | assert(value); | |
265 | ||
266 | if (streq(key, "DEVPATH")) { | |
267 | char *path; | |
268 | ||
269 | path = strjoina("/sys", value); | |
270 | ||
271 | /* the caller must verify or trust this data (e.g., if it comes from the kernel) */ | |
272 | r = device_set_syspath(device, path, false); | |
273 | if (r < 0) | |
c7d54dae | 274 | return log_device_debug_errno(device, r, "sd-device: Failed to set syspath to '%s': %m", path); |
57fa1d09 TG |
275 | } else if (streq(key, "SUBSYSTEM")) { |
276 | r = device_set_subsystem(device, value); | |
277 | if (r < 0) | |
c7d54dae | 278 | return log_device_debug_errno(device, r, "sd-device: Failed to set subsystem to '%s': %m", value); |
57fa1d09 TG |
279 | } else if (streq(key, "DEVTYPE")) { |
280 | r = device_set_devtype(device, value); | |
281 | if (r < 0) | |
c7d54dae | 282 | return log_device_debug_errno(device, r, "sd-device: Failed to set devtype to '%s': %m", value); |
57fa1d09 TG |
283 | } else if (streq(key, "DEVNAME")) { |
284 | r = device_set_devname(device, value); | |
285 | if (r < 0) | |
c7d54dae | 286 | return log_device_debug_errno(device, r, "sd-device: Failed to set devname to '%s': %m", value); |
57fa1d09 | 287 | } else if (streq(key, "USEC_INITIALIZED")) { |
dc5042c0 ZJS |
288 | usec_t t; |
289 | ||
290 | r = safe_atou64(value, &t); | |
291 | if (r < 0) | |
292 | return log_device_debug_errno(device, r, "sd-device: Failed to parse timestamp '%s': %m", value); | |
293 | ||
294 | r = device_set_usec_initialized(device, t); | |
57fa1d09 | 295 | if (r < 0) |
c7d54dae | 296 | return log_device_debug_errno(device, r, "sd-device: Failed to set usec-initialized to '%s': %m", value); |
57fa1d09 TG |
297 | } else if (streq(key, "DRIVER")) { |
298 | r = device_set_driver(device, value); | |
299 | if (r < 0) | |
c7d54dae | 300 | return log_device_debug_errno(device, r, "sd-device: Failed to set driver to '%s': %m", value); |
57fa1d09 TG |
301 | } else if (streq(key, "IFINDEX")) { |
302 | r = device_set_ifindex(device, value); | |
303 | if (r < 0) | |
c7d54dae | 304 | return log_device_debug_errno(device, r, "sd-device: Failed to set ifindex to '%s': %m", value); |
57fa1d09 TG |
305 | } else if (streq(key, "DEVMODE")) { |
306 | r = device_set_devmode(device, value); | |
307 | if (r < 0) | |
c7d54dae | 308 | return log_device_debug_errno(device, r, "sd-device: Failed to set devmode to '%s': %m", value); |
57fa1d09 TG |
309 | } else if (streq(key, "DEVUID")) { |
310 | r = device_set_devuid(device, value); | |
311 | if (r < 0) | |
c7d54dae | 312 | return log_device_debug_errno(device, r, "sd-device: Failed to set devuid to '%s': %m", value); |
57fa1d09 TG |
313 | } else if (streq(key, "DEVGID")) { |
314 | r = device_set_devgid(device, value); | |
315 | if (r < 0) | |
c7d54dae | 316 | return log_device_debug_errno(device, r, "sd-device: Failed to set devgid to '%s': %m", value); |
5ebd3fc3 | 317 | } else if (streq(key, "ACTION")) { |
20798980 | 318 | r = device_set_action_from_string(device, value); |
5ebd3fc3 YW |
319 | if (r < 0) |
320 | return log_device_debug_errno(device, r, "sd-device: Failed to set action to '%s': %m", value); | |
321 | } else if (streq(key, "SEQNUM")) { | |
322 | r = device_set_seqnum(device, value); | |
323 | if (r < 0) | |
324 | return log_device_debug_errno(device, r, "sd-device: Failed to set SEQNUM to '%s': %m", value); | |
122adcb2 LB |
325 | } else if (streq(key, "DISKSEQ")) { |
326 | r = device_set_diskseq(device, value); | |
327 | if (r < 0) | |
328 | return log_device_debug_errno(device, r, "sd-device: Failed to set DISKSEQ to '%s': %m", value); | |
57fa1d09 | 329 | } else if (streq(key, "DEVLINKS")) { |
87a4d416 ZJS |
330 | for (const char *p = value;;) { |
331 | _cleanup_free_ char *word = NULL; | |
57fa1d09 | 332 | |
31063db0 YW |
333 | /* udev rules may set escaped strings, and sd-device does not modify the input |
334 | * strings. So, it is also necessary to keep the strings received through | |
335 | * sd-device-monitor. */ | |
336 | r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE); | |
87a4d416 ZJS |
337 | if (r < 0) |
338 | return r; | |
339 | if (r == 0) | |
340 | break; | |
57fa1d09 | 341 | |
87a4d416 | 342 | r = device_add_devlink(device, word); |
57fa1d09 | 343 | if (r < 0) |
87a4d416 | 344 | return log_device_debug_errno(device, r, "sd-device: Failed to add devlink '%s': %m", word); |
57fa1d09 | 345 | } |
e77b146f | 346 | } else if (STR_IN_SET(key, "TAGS", "CURRENT_TAGS")) { |
87a4d416 ZJS |
347 | for (const char *p = value;;) { |
348 | _cleanup_free_ char *word = NULL; | |
57fa1d09 | 349 | |
87a4d416 ZJS |
350 | r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); |
351 | if (r < 0) | |
352 | return r; | |
353 | if (r == 0) | |
354 | break; | |
57fa1d09 | 355 | |
87a4d416 | 356 | r = device_add_tag(device, word, streq(key, "CURRENT_TAGS")); |
57fa1d09 | 357 | if (r < 0) |
87a4d416 | 358 | return log_device_debug_errno(device, r, "sd-device: Failed to add tag '%s': %m", word); |
57fa1d09 TG |
359 | } |
360 | } else { | |
361 | r = device_add_property_internal(device, key, value); | |
362 | if (r < 0) | |
c7d54dae | 363 | return log_device_debug_errno(device, r, "sd-device: Failed to add property '%s=%s': %m", key, value); |
57fa1d09 TG |
364 | } |
365 | ||
366 | return 0; | |
367 | } | |
368 | ||
96fb82aa LP |
369 | static int device_append( |
370 | sd_device *device, | |
371 | char *key, | |
372 | const char **_major, | |
373 | const char **_minor) { | |
374 | ||
57fa1d09 TG |
375 | const char *major = NULL, *minor = NULL; |
376 | char *value; | |
377 | int r; | |
378 | ||
379 | assert(device); | |
380 | assert(key); | |
381 | assert(_major); | |
382 | assert(_minor); | |
57fa1d09 TG |
383 | |
384 | value = strchr(key, '='); | |
9e791238 ZJS |
385 | if (!value) |
386 | return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), | |
387 | "sd-device: Not a key-value pair: '%s'", key); | |
57fa1d09 TG |
388 | |
389 | *value = '\0'; | |
390 | ||
391 | value++; | |
392 | ||
393 | if (streq(key, "MAJOR")) | |
394 | major = value; | |
395 | else if (streq(key, "MINOR")) | |
396 | minor = value; | |
397 | else { | |
401cb614 | 398 | r = device_amend(device, key, value); |
57fa1d09 TG |
399 | if (r < 0) |
400 | return r; | |
401 | } | |
402 | ||
96fb82aa | 403 | if (major) |
57fa1d09 TG |
404 | *_major = major; |
405 | ||
96fb82aa | 406 | if (minor) |
57fa1d09 TG |
407 | *_minor = minor; |
408 | ||
57fa1d09 TG |
409 | return 0; |
410 | } | |
411 | ||
412 | void device_seal(sd_device *device) { | |
413 | assert(device); | |
414 | ||
415 | device->sealed = true; | |
416 | } | |
417 | ||
5ebd3fc3 | 418 | static int device_verify(sd_device *device) { |
e5ca293f YW |
419 | int r; |
420 | ||
57fa1d09 TG |
421 | assert(device); |
422 | ||
9e791238 ZJS |
423 | if (!device->devpath || !device->subsystem || device->action < 0 || device->seqnum == 0) |
424 | return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), | |
425 | "sd-device: Device created from strv or nulstr lacks devpath, subsystem, action or seqnum."); | |
57fa1d09 | 426 | |
e5ca293f YW |
427 | if (streq(device->subsystem, "drivers")) { |
428 | r = device_set_drivers_subsystem(device); | |
429 | if (r < 0) | |
430 | return r; | |
431 | } | |
432 | ||
57fa1d09 TG |
433 | device->sealed = true; |
434 | ||
435 | return 0; | |
436 | } | |
437 | ||
438 | int device_new_from_strv(sd_device **ret, char **strv) { | |
4afd3348 | 439 | _cleanup_(sd_device_unrefp) sd_device *device = NULL; |
57fa1d09 | 440 | const char *major = NULL, *minor = NULL; |
57fa1d09 TG |
441 | int r; |
442 | ||
443 | assert(ret); | |
444 | assert(strv); | |
445 | ||
446 | r = device_new_aux(&device); | |
447 | if (r < 0) | |
448 | return r; | |
449 | ||
450 | STRV_FOREACH(key, strv) { | |
5ebd3fc3 | 451 | r = device_append(device, *key, &major, &minor); |
57fa1d09 TG |
452 | if (r < 0) |
453 | return r; | |
454 | } | |
455 | ||
456 | if (major) { | |
457 | r = device_set_devnum(device, major, minor); | |
458 | if (r < 0) | |
c7d54dae | 459 | return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor); |
57fa1d09 TG |
460 | } |
461 | ||
5ebd3fc3 | 462 | r = device_verify(device); |
57fa1d09 TG |
463 | if (r < 0) |
464 | return r; | |
465 | ||
1cc6c93a | 466 | *ret = TAKE_PTR(device); |
57fa1d09 TG |
467 | |
468 | return 0; | |
469 | } | |
470 | ||
471 | int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len) { | |
4afd3348 | 472 | _cleanup_(sd_device_unrefp) sd_device *device = NULL; |
57fa1d09 | 473 | const char *major = NULL, *minor = NULL; |
57fa1d09 TG |
474 | int r; |
475 | ||
476 | assert(ret); | |
477 | assert(nulstr); | |
478 | assert(len); | |
479 | ||
480 | r = device_new_aux(&device); | |
481 | if (r < 0) | |
482 | return r; | |
483 | ||
ce634c4a | 484 | for (size_t i = 0; i < len; ) { |
57fa1d09 TG |
485 | char *key; |
486 | const char *end; | |
487 | ||
ce634c4a | 488 | key = (char*) &nulstr[i]; |
57fa1d09 | 489 | end = memchr(key, '\0', len - i); |
9e791238 ZJS |
490 | if (!end) |
491 | return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), | |
492 | "sd-device: Failed to parse nulstr"); | |
493 | ||
57fa1d09 TG |
494 | i += end - key + 1; |
495 | ||
b9cbb08e YW |
496 | /* netlink messages for some devices contain an unwanted newline at the end of value. |
497 | * Let's drop the newline and remaining characters after the newline. */ | |
498 | truncate_nl(key); | |
499 | ||
5ebd3fc3 | 500 | r = device_append(device, key, &major, &minor); |
57fa1d09 TG |
501 | if (r < 0) |
502 | return r; | |
503 | } | |
504 | ||
505 | if (major) { | |
506 | r = device_set_devnum(device, major, minor); | |
507 | if (r < 0) | |
c7d54dae | 508 | return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor); |
57fa1d09 TG |
509 | } |
510 | ||
5ebd3fc3 | 511 | r = device_verify(device); |
57fa1d09 TG |
512 | if (r < 0) |
513 | return r; | |
514 | ||
1cc6c93a | 515 | *ret = TAKE_PTR(device); |
57fa1d09 TG |
516 | |
517 | return 0; | |
518 | } | |
519 | ||
520 | static int device_update_properties_bufs(sd_device *device) { | |
521 | const char *val, *prop; | |
ccc1002a TG |
522 | _cleanup_free_ char **buf_strv = NULL; |
523 | _cleanup_free_ uint8_t *buf_nulstr = NULL; | |
ccc1002a | 524 | size_t nulstr_len = 0, num = 0, i = 0; |
57fa1d09 TG |
525 | |
526 | assert(device); | |
527 | ||
aa20f49a TG |
528 | if (!device->properties_buf_outdated) |
529 | return 0; | |
530 | ||
57fa1d09 TG |
531 | FOREACH_DEVICE_PROPERTY(device, prop, val) { |
532 | size_t len = 0; | |
533 | ||
534 | len = strlen(prop) + 1 + strlen(val); | |
535 | ||
319a4f4b | 536 | buf_nulstr = GREEDY_REALLOC0(buf_nulstr, nulstr_len + len + 2); |
57fa1d09 TG |
537 | if (!buf_nulstr) |
538 | return -ENOMEM; | |
539 | ||
57fa1d09 TG |
540 | strscpyl((char *)buf_nulstr + nulstr_len, len + 1, prop, "=", val, NULL); |
541 | nulstr_len += len + 1; | |
d854ba50 | 542 | ++num; |
57fa1d09 TG |
543 | } |
544 | ||
ccc1002a TG |
545 | /* build buf_strv from buf_nulstr */ |
546 | buf_strv = new0(char *, num + 1); | |
547 | if (!buf_strv) | |
548 | return -ENOMEM; | |
d854ba50 | 549 | |
d854ba50 | 550 | NULSTR_FOREACH(val, (char*) buf_nulstr) { |
ccc1002a | 551 | buf_strv[i] = (char *) val; |
d854ba50 MP |
552 | assert(i < num); |
553 | i++; | |
554 | } | |
57fa1d09 | 555 | |
f9ecfd3b | 556 | free_and_replace(device->properties_nulstr, buf_nulstr); |
ccc1002a | 557 | device->properties_nulstr_len = nulstr_len; |
f9ecfd3b | 558 | free_and_replace(device->properties_strv, buf_strv); |
ccc1002a | 559 | |
57fa1d09 TG |
560 | device->properties_buf_outdated = false; |
561 | ||
562 | return 0; | |
563 | } | |
564 | ||
565 | int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len) { | |
566 | int r; | |
567 | ||
568 | assert(device); | |
569 | assert(nulstr); | |
570 | assert(len); | |
571 | ||
aa20f49a TG |
572 | r = device_update_properties_bufs(device); |
573 | if (r < 0) | |
574 | return r; | |
57fa1d09 TG |
575 | |
576 | *nulstr = device->properties_nulstr; | |
577 | *len = device->properties_nulstr_len; | |
578 | ||
579 | return 0; | |
580 | } | |
581 | ||
582 | int device_get_properties_strv(sd_device *device, char ***strv) { | |
583 | int r; | |
584 | ||
585 | assert(device); | |
586 | assert(strv); | |
587 | ||
588 | r = device_update_properties_bufs(device); | |
589 | if (r < 0) | |
590 | return r; | |
591 | ||
592 | *strv = device->properties_strv; | |
593 | ||
594 | return 0; | |
595 | } | |
596 | ||
597 | int device_get_devlink_priority(sd_device *device, int *priority) { | |
598 | int r; | |
599 | ||
600 | assert(device); | |
601 | assert(priority); | |
602 | ||
603 | r = device_read_db(device); | |
604 | if (r < 0) | |
605 | return r; | |
606 | ||
607 | *priority = device->devlink_priority; | |
608 | ||
609 | return 0; | |
610 | } | |
611 | ||
e7f781e4 YW |
612 | int device_get_watch_handle(sd_device *device) { |
613 | char path_wd[STRLEN("/run/udev/watch/") + DECIMAL_STR_MAX(int)]; | |
614 | _cleanup_free_ char *buf = NULL; | |
615 | const char *id, *path_id; | |
616 | int wd, r; | |
617 | ||
618 | assert(device); | |
619 | ||
620 | if (device->watch_handle >= 0) | |
621 | return device->watch_handle; | |
622 | ||
623 | r = device_get_device_id(device, &id); | |
624 | if (r < 0) | |
625 | return r; | |
626 | ||
627 | path_id = strjoina("/run/udev/watch/", id); | |
628 | r = readlink_malloc(path_id, &buf); | |
629 | if (r < 0) | |
630 | return r; | |
631 | ||
632 | r = safe_atoi(buf, &wd); | |
633 | if (r < 0) | |
634 | return r; | |
635 | ||
636 | if (wd < 0) | |
637 | return -EBADF; | |
638 | ||
639 | buf = mfree(buf); | |
640 | xsprintf(path_wd, "/run/udev/watch/%d", wd); | |
641 | r = readlink_malloc(path_wd, &buf); | |
642 | if (r < 0) | |
643 | return r; | |
644 | ||
645 | if (!streq(buf, id)) | |
646 | return -EBADF; | |
647 | ||
648 | return device->watch_handle = wd; | |
649 | } | |
650 | ||
651 | static void device_remove_watch_handle(sd_device *device) { | |
652 | const char *id; | |
653 | int wd; | |
654 | ||
655 | assert(device); | |
656 | ||
657 | /* First, remove the symlink from handle to device id. */ | |
658 | wd = device_get_watch_handle(device); | |
659 | if (wd >= 0) { | |
660 | char path_wd[STRLEN("/run/udev/watch/") + DECIMAL_STR_MAX(int)]; | |
661 | ||
662 | xsprintf(path_wd, "/run/udev/watch/%d", wd); | |
663 | if (unlink(path_wd) < 0 && errno != ENOENT) | |
664 | log_device_debug_errno(device, errno, | |
665 | "sd-device: failed to remove %s, ignoring: %m", | |
666 | path_wd); | |
667 | } | |
668 | ||
669 | /* Next, remove the symlink from device id to handle. */ | |
670 | if (device_get_device_id(device, &id) >= 0) { | |
671 | const char *path_id; | |
672 | ||
673 | path_id = strjoina("/run/udev/watch/", id); | |
674 | if (unlink(path_id) < 0 && errno != ENOENT) | |
675 | log_device_debug_errno(device, errno, | |
676 | "sd-device: failed to remove %s, ignoring: %m", | |
677 | path_id); | |
678 | } | |
679 | ||
680 | device->watch_handle = -1; | |
681 | } | |
682 | ||
683 | int device_set_watch_handle(sd_device *device, int wd) { | |
684 | char path_wd[STRLEN("/run/udev/watch/") + DECIMAL_STR_MAX(int)]; | |
685 | const char *id, *path_id; | |
57fa1d09 TG |
686 | int r; |
687 | ||
688 | assert(device); | |
57fa1d09 | 689 | |
20ec7d9e | 690 | if (wd >= 0 && wd == device_get_watch_handle(device)) |
e7f781e4 YW |
691 | return 0; |
692 | ||
693 | device_remove_watch_handle(device); | |
694 | ||
695 | if (wd < 0) | |
696 | /* negative wd means that the caller requests to clear saved watch handle. */ | |
697 | return 0; | |
698 | ||
699 | r = device_get_device_id(device, &id); | |
57fa1d09 TG |
700 | if (r < 0) |
701 | return r; | |
702 | ||
e7f781e4 YW |
703 | path_id = strjoina("/run/udev/watch/", id); |
704 | xsprintf(path_wd, "/run/udev/watch/%d", wd); | |
705 | ||
706 | r = mkdir_parents(path_wd, 0755); | |
707 | if (r < 0) | |
708 | return r; | |
709 | ||
710 | if (symlink(id, path_wd) < 0) | |
711 | return -errno; | |
dcfbde3a | 712 | |
e7f781e4 YW |
713 | if (symlink(path_wd + STRLEN("/run/udev/watch/"), path_id) < 0) { |
714 | r = -errno; | |
715 | if (unlink(path_wd) < 0 && errno != ENOENT) | |
716 | log_device_debug_errno(device, errno, | |
717 | "sd-device: failed to remove %s, ignoring: %m", | |
718 | path_wd); | |
719 | return r; | |
720 | } | |
721 | ||
722 | device->watch_handle = wd; | |
57fa1d09 TG |
723 | |
724 | return 0; | |
725 | } | |
726 | ||
e7f781e4 YW |
727 | int device_new_from_watch_handle_at(sd_device **ret, int dirfd, int wd) { |
728 | char path_wd[STRLEN("/run/udev/watch/") + DECIMAL_STR_MAX(int)]; | |
729 | _cleanup_free_ char *id = NULL; | |
730 | int r; | |
731 | ||
732 | assert(ret); | |
733 | ||
734 | if (wd < 0) | |
735 | return -EBADF; | |
57fa1d09 | 736 | |
e7f781e4 YW |
737 | if (dirfd >= 0) { |
738 | xsprintf(path_wd, "%d", wd); | |
739 | r = readlinkat_malloc(dirfd, path_wd, &id); | |
740 | } else { | |
741 | xsprintf(path_wd, "/run/udev/watch/%d", wd); | |
742 | r = readlink_malloc(path_wd, &id); | |
743 | } | |
744 | if (r < 0) | |
745 | return r; | |
746 | ||
747 | return sd_device_new_from_device_id(ret, id); | |
57fa1d09 TG |
748 | } |
749 | ||
750 | int device_rename(sd_device *device, const char *name) { | |
751 | _cleanup_free_ char *dirname = NULL; | |
270384b2 | 752 | const char *new_syspath, *interface; |
57fa1d09 TG |
753 | int r; |
754 | ||
755 | assert(device); | |
756 | assert(name); | |
757 | ||
758 | dirname = dirname_malloc(device->syspath); | |
759 | if (!dirname) | |
760 | return -ENOMEM; | |
761 | ||
270384b2 | 762 | new_syspath = prefix_roota(dirname, name); |
57fa1d09 TG |
763 | |
764 | /* the user must trust that the new name is correct */ | |
765 | r = device_set_syspath(device, new_syspath, false); | |
766 | if (r < 0) | |
767 | return r; | |
768 | ||
769 | r = sd_device_get_property_value(device, "INTERFACE", &interface); | |
770 | if (r >= 0) { | |
f7b1c8d1 EV |
771 | /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ |
772 | r = device_add_property_internal(device, "INTERFACE_OLD", interface); | |
57fa1d09 TG |
773 | if (r < 0) |
774 | return r; | |
775 | ||
f7b1c8d1 | 776 | r = device_add_property_internal(device, "INTERFACE", name); |
57fa1d09 TG |
777 | if (r < 0) |
778 | return r; | |
779 | } else if (r != -ENOENT) | |
780 | return r; | |
781 | ||
782 | return 0; | |
783 | } | |
784 | ||
785 | int device_shallow_clone(sd_device *old_device, sd_device **new_device) { | |
4afd3348 | 786 | _cleanup_(sd_device_unrefp) sd_device *ret = NULL; |
381f6d4b | 787 | const char *val; |
57fa1d09 TG |
788 | int r; |
789 | ||
790 | assert(old_device); | |
791 | assert(new_device); | |
792 | ||
793 | r = device_new_aux(&ret); | |
794 | if (r < 0) | |
795 | return r; | |
796 | ||
797 | r = device_set_syspath(ret, old_device->syspath, false); | |
798 | if (r < 0) | |
799 | return r; | |
800 | ||
df49a732 ZJS |
801 | (void) sd_device_get_subsystem(old_device, &val); |
802 | r = device_set_subsystem(ret, val); | |
803 | if (r < 0) | |
804 | return r; | |
805 | if (streq_ptr(val, "drivers")) { | |
806 | r = free_and_strdup(&ret->driver_subsystem, old_device->driver_subsystem); | |
2255e8ad YW |
807 | if (r < 0) |
808 | return r; | |
df49a732 | 809 | } |
57fa1d09 | 810 | |
381f6d4b YW |
811 | /* The device may be already removed. Let's copy minimal set of information to make |
812 | * device_get_device_id() work without uevent file. */ | |
57fa1d09 | 813 | |
381f6d4b YW |
814 | if (sd_device_get_property_value(old_device, "IFINDEX", &val) >= 0) { |
815 | r = device_set_ifindex(ret, val); | |
816 | if (r < 0) | |
817 | return r; | |
818 | } | |
819 | ||
820 | if (sd_device_get_property_value(old_device, "MAJOR", &val) >= 0) { | |
821 | const char *minor = NULL; | |
57fa1d09 | 822 | |
381f6d4b YW |
823 | (void) sd_device_get_property_value(old_device, "MINOR", &minor); |
824 | r = device_set_devnum(ret, val, minor); | |
825 | if (r < 0) | |
826 | return r; | |
827 | } | |
828 | ||
829 | /* And then read uevent file, but ignore errors, as some devices seem to return a spurious | |
830 | * error on read, e.g. -ENODEV, and even if ifindex or devnum is set in the above, | |
831 | * sd_device_get_ifindex() or sd_device_get_devnum() fails. See. #19788. */ | |
832 | (void) device_read_uevent_file(ret); | |
833 | ||
834 | *new_device = TAKE_PTR(ret); | |
57fa1d09 TG |
835 | return 0; |
836 | } | |
837 | ||
838 | int device_clone_with_db(sd_device *old_device, sd_device **new_device) { | |
4afd3348 | 839 | _cleanup_(sd_device_unrefp) sd_device *ret = NULL; |
57fa1d09 TG |
840 | int r; |
841 | ||
842 | assert(old_device); | |
843 | assert(new_device); | |
844 | ||
845 | r = device_shallow_clone(old_device, &ret); | |
846 | if (r < 0) | |
847 | return r; | |
848 | ||
849 | r = device_read_db(ret); | |
850 | if (r < 0) | |
851 | return r; | |
852 | ||
853 | ret->sealed = true; | |
854 | ||
1cc6c93a | 855 | *new_device = TAKE_PTR(ret); |
57fa1d09 TG |
856 | |
857 | return 0; | |
858 | } | |
859 | ||
57fa1d09 TG |
860 | int device_copy_properties(sd_device *device_dst, sd_device *device_src) { |
861 | const char *property, *value; | |
862 | int r; | |
863 | ||
864 | assert(device_dst); | |
865 | assert(device_src); | |
866 | ||
a3ce8136 YW |
867 | r = device_properties_prepare(device_src); |
868 | if (r < 0) | |
869 | return r; | |
870 | ||
90e74a66 | 871 | ORDERED_HASHMAP_FOREACH_KEY(value, property, device_src->properties_db) { |
a3ce8136 YW |
872 | r = device_add_property_aux(device_dst, property, value, true); |
873 | if (r < 0) | |
874 | return r; | |
875 | } | |
876 | ||
90e74a66 | 877 | ORDERED_HASHMAP_FOREACH_KEY(value, property, device_src->properties) { |
a3ce8136 | 878 | r = device_add_property_aux(device_dst, property, value, false); |
57fa1d09 TG |
879 | if (r < 0) |
880 | return r; | |
881 | } | |
882 | ||
883 | return 0; | |
884 | } | |
885 | ||
886 | void device_cleanup_tags(sd_device *device) { | |
887 | assert(device); | |
888 | ||
e77b146f LP |
889 | device->all_tags = set_free_free(device->all_tags); |
890 | device->current_tags = set_free_free(device->current_tags); | |
57fa1d09 | 891 | device->property_tags_outdated = true; |
313cefa1 | 892 | device->tags_generation++; |
57fa1d09 TG |
893 | } |
894 | ||
895 | void device_cleanup_devlinks(sd_device *device) { | |
896 | assert(device); | |
897 | ||
898 | set_free_free(device->devlinks); | |
899 | device->devlinks = NULL; | |
900 | device->property_devlinks_outdated = true; | |
313cefa1 | 901 | device->devlinks_generation++; |
57fa1d09 TG |
902 | } |
903 | ||
904 | void device_remove_tag(sd_device *device, const char *tag) { | |
905 | assert(device); | |
906 | assert(tag); | |
907 | ||
e77b146f | 908 | free(set_remove(device->current_tags, tag)); |
57fa1d09 | 909 | device->property_tags_outdated = true; |
313cefa1 | 910 | device->tags_generation++; |
57fa1d09 TG |
911 | } |
912 | ||
913 | static int device_tag(sd_device *device, const char *tag, bool add) { | |
914 | const char *id; | |
915 | char *path; | |
916 | int r; | |
917 | ||
918 | assert(device); | |
919 | assert(tag); | |
920 | ||
fe732381 | 921 | r = device_get_device_id(device, &id); |
57fa1d09 TG |
922 | if (r < 0) |
923 | return r; | |
924 | ||
925 | path = strjoina("/run/udev/tags/", tag, "/", id); | |
926 | ||
927 | if (add) { | |
928 | r = touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444); | |
929 | if (r < 0) | |
930 | return r; | |
931 | } else { | |
932 | r = unlink(path); | |
933 | if (r < 0 && errno != ENOENT) | |
934 | return -errno; | |
935 | } | |
936 | ||
937 | return 0; | |
938 | } | |
939 | ||
940 | int device_tag_index(sd_device *device, sd_device *device_old, bool add) { | |
941 | const char *tag; | |
942 | int r = 0, k; | |
943 | ||
944 | if (add && device_old) { | |
945 | /* delete possible left-over tags */ | |
946 | FOREACH_DEVICE_TAG(device_old, tag) { | |
947 | if (!sd_device_has_tag(device, tag)) { | |
948 | k = device_tag(device_old, tag, false); | |
949 | if (r >= 0 && k < 0) | |
950 | r = k; | |
951 | } | |
952 | } | |
953 | } | |
954 | ||
955 | FOREACH_DEVICE_TAG(device, tag) { | |
956 | k = device_tag(device, tag, add); | |
957 | if (r >= 0 && k < 0) | |
958 | r = k; | |
959 | } | |
960 | ||
961 | return r; | |
962 | } | |
963 | ||
964 | static bool device_has_info(sd_device *device) { | |
965 | assert(device); | |
966 | ||
967 | if (!set_isempty(device->devlinks)) | |
968 | return true; | |
969 | ||
970 | if (device->devlink_priority != 0) | |
971 | return true; | |
972 | ||
973 | if (!ordered_hashmap_isempty(device->properties_db)) | |
974 | return true; | |
975 | ||
e77b146f LP |
976 | if (!set_isempty(device->all_tags)) |
977 | return true; | |
978 | ||
979 | if (!set_isempty(device->current_tags)) | |
57fa1d09 TG |
980 | return true; |
981 | ||
57fa1d09 TG |
982 | return false; |
983 | } | |
984 | ||
985 | void device_set_db_persist(sd_device *device) { | |
986 | assert(device); | |
987 | ||
988 | device->db_persist = true; | |
989 | } | |
990 | ||
991 | int device_update_db(sd_device *device) { | |
992 | const char *id; | |
993 | char *path; | |
994 | _cleanup_fclose_ FILE *f = NULL; | |
995 | _cleanup_free_ char *path_tmp = NULL; | |
996 | bool has_info; | |
997 | int r; | |
998 | ||
999 | assert(device); | |
1000 | ||
1001 | has_info = device_has_info(device); | |
1002 | ||
fe732381 | 1003 | r = device_get_device_id(device, &id); |
57fa1d09 TG |
1004 | if (r < 0) |
1005 | return r; | |
1006 | ||
1007 | path = strjoina("/run/udev/data/", id); | |
1008 | ||
1009 | /* do not store anything for otherwise empty devices */ | |
1010 | if (!has_info && major(device->devnum) == 0 && device->ifindex == 0) { | |
1011 | r = unlink(path); | |
1012 | if (r < 0 && errno != ENOENT) | |
1013 | return -errno; | |
1014 | ||
1015 | return 0; | |
1016 | } | |
1017 | ||
1018 | /* write a database file */ | |
1019 | r = mkdir_parents(path, 0755); | |
1020 | if (r < 0) | |
1021 | return r; | |
1022 | ||
1023 | r = fopen_temporary(path, &f, &path_tmp); | |
1024 | if (r < 0) | |
1025 | return r; | |
1026 | ||
1027 | /* | |
1028 | * set 'sticky' bit to indicate that we should not clean the | |
1029 | * database when we transition from initramfs to the real root | |
1030 | */ | |
827f8650 YW |
1031 | if (fchmod(fileno(f), device->db_persist ? 01644 : 0644) < 0) { |
1032 | r = -errno; | |
1033 | goto fail; | |
57fa1d09 TG |
1034 | } |
1035 | ||
1036 | if (has_info) { | |
1037 | const char *property, *value, *tag; | |
57fa1d09 TG |
1038 | |
1039 | if (major(device->devnum) > 0) { | |
1040 | const char *devlink; | |
1041 | ||
1042 | FOREACH_DEVICE_DEVLINK(device, devlink) | |
fbd0b64f | 1043 | fprintf(f, "S:%s\n", devlink + STRLEN("/dev/")); |
57fa1d09 TG |
1044 | |
1045 | if (device->devlink_priority != 0) | |
1046 | fprintf(f, "L:%i\n", device->devlink_priority); | |
57fa1d09 TG |
1047 | } |
1048 | ||
1049 | if (device->usec_initialized > 0) | |
1050 | fprintf(f, "I:"USEC_FMT"\n", device->usec_initialized); | |
1051 | ||
90e74a66 | 1052 | ORDERED_HASHMAP_FOREACH_KEY(value, property, device->properties_db) |
57fa1d09 TG |
1053 | fprintf(f, "E:%s=%s\n", property, value); |
1054 | ||
1055 | FOREACH_DEVICE_TAG(device, tag) | |
e77b146f LP |
1056 | fprintf(f, "G:%s\n", tag); /* Any tag */ |
1057 | ||
90e74a66 | 1058 | SET_FOREACH(tag, device->current_tags) |
e77b146f | 1059 | fprintf(f, "Q:%s\n", tag); /* Current tag */ |
58b30ada YW |
1060 | |
1061 | /* Always write the latest database version here, instead of the value stored in | |
1062 | * device->database_version, as which may be 0. */ | |
1063 | fputs("V:" STRINGIFY(LATEST_UDEV_DATABASE_VERSION) "\n", f); | |
57fa1d09 TG |
1064 | } |
1065 | ||
1066 | r = fflush_and_check(f); | |
1067 | if (r < 0) | |
1068 | goto fail; | |
1069 | ||
827f8650 | 1070 | if (rename(path_tmp, path) < 0) { |
57fa1d09 TG |
1071 | r = -errno; |
1072 | goto fail; | |
1073 | } | |
1074 | ||
c7d54dae YW |
1075 | log_device_debug(device, "sd-device: Created %s file '%s' for '%s'", has_info ? "db" : "empty", |
1076 | path, device->devpath); | |
57fa1d09 TG |
1077 | |
1078 | return 0; | |
1079 | ||
1080 | fail: | |
dacd6cee LP |
1081 | (void) unlink(path); |
1082 | (void) unlink(path_tmp); | |
57fa1d09 | 1083 | |
c7d54dae | 1084 | return log_device_debug_errno(device, r, "sd-device: Failed to create %s file '%s' for '%s'", has_info ? "db" : "empty", path, device->devpath); |
57fa1d09 TG |
1085 | } |
1086 | ||
1087 | int device_delete_db(sd_device *device) { | |
1088 | const char *id; | |
1089 | char *path; | |
1090 | int r; | |
1091 | ||
1092 | assert(device); | |
1093 | ||
fe732381 | 1094 | r = device_get_device_id(device, &id); |
57fa1d09 TG |
1095 | if (r < 0) |
1096 | return r; | |
1097 | ||
1098 | path = strjoina("/run/udev/data/", id); | |
1099 | ||
1100 | r = unlink(path); | |
1101 | if (r < 0 && errno != ENOENT) | |
1102 | return -errno; | |
1103 | ||
1104 | return 0; | |
1105 | } | |
5ebd3fc3 | 1106 | |
a1130022 LP |
1107 | static const char* const device_action_table[_SD_DEVICE_ACTION_MAX] = { |
1108 | [SD_DEVICE_ADD] = "add", | |
1109 | [SD_DEVICE_REMOVE] = "remove", | |
1110 | [SD_DEVICE_CHANGE] = "change", | |
1111 | [SD_DEVICE_MOVE] = "move", | |
1112 | [SD_DEVICE_ONLINE] = "online", | |
1113 | [SD_DEVICE_OFFLINE] = "offline", | |
1114 | [SD_DEVICE_BIND] = "bind", | |
1115 | [SD_DEVICE_UNBIND] = "unbind", | |
5ebd3fc3 YW |
1116 | }; |
1117 | ||
a1130022 | 1118 | DEFINE_STRING_TABLE_LOOKUP(device_action, sd_device_action_t); |
9e0196b1 YW |
1119 | |
1120 | void dump_device_action_table(void) { | |
a1130022 | 1121 | DUMP_STRING_TABLE(device_action, sd_device_action_t, _SD_DEVICE_ACTION_MAX); |
9e0196b1 | 1122 | } |