]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
b237a168 | 2 | |
5953d8b9 | 3 | #include <ctype.h> |
152d0efa | 4 | #include <errno.h> |
bee33d05 | 5 | #include <sys/inotify.h> |
030a0d79 | 6 | #include <unistd.h> |
b237a168 | 7 | |
152d0efa | 8 | #include "alloc-util.h" |
393fcaf7 | 9 | #include "device-nodes.h" |
a1130022 | 10 | #include "device-private.h" |
f822c5d5 | 11 | #include "device-util.h" |
686d13b9 | 12 | #include "env-file.h" |
acfc2a1d | 13 | #include "errno-util.h" |
aea3253e | 14 | #include "escape.h" |
bee33d05 | 15 | #include "fd-util.h" |
b237a168 | 16 | #include "log.h" |
aea3253e | 17 | #include "macro.h" |
4b3ca79e | 18 | #include "parse-util.h" |
030a0d79 | 19 | #include "path-util.h" |
e2099267 | 20 | #include "signal-util.h" |
bc768f04 | 21 | #include "string-table.h" |
b237a168 | 22 | #include "string-util.h" |
1223227f | 23 | #include "strxcpyx.h" |
b237a168 | 24 | #include "udev-util.h" |
aea3253e | 25 | #include "utf8.h" |
b237a168 | 26 | |
bc768f04 ZJS |
27 | static const char* const resolve_name_timing_table[_RESOLVE_NAME_TIMING_MAX] = { |
28 | [RESOLVE_NAME_NEVER] = "never", | |
29 | [RESOLVE_NAME_LATE] = "late", | |
30 | [RESOLVE_NAME_EARLY] = "early", | |
31 | }; | |
32 | ||
33 | DEFINE_STRING_TABLE_LOOKUP(resolve_name_timing, ResolveNameTiming); | |
34 | ||
4b3ca79e ZJS |
35 | int udev_parse_config_full( |
36 | unsigned *ret_children_max, | |
37 | usec_t *ret_exec_delay_usec, | |
a14e7af1 | 38 | usec_t *ret_event_timeout_usec, |
e2099267 MS |
39 | ResolveNameTiming *ret_resolve_name_timing, |
40 | int *ret_timeout_signal) { | |
4b3ca79e | 41 | |
e2099267 | 42 | _cleanup_free_ char *log_val = NULL, *children_max = NULL, *exec_delay = NULL, *event_timeout = NULL, *resolve_names = NULL, *timeout_signal = NULL; |
b237a168 ZJS |
43 | int r; |
44 | ||
aa8fbc74 | 45 | r = parse_env_file(NULL, "/etc/udev/udev.conf", |
4b3ca79e ZJS |
46 | "udev_log", &log_val, |
47 | "children_max", &children_max, | |
48 | "exec_delay", &exec_delay, | |
9b2934cb | 49 | "event_timeout", &event_timeout, |
e2099267 MS |
50 | "resolve_names", &resolve_names, |
51 | "timeout_signal", &timeout_signal); | |
4b3ca79e | 52 | if (r == -ENOENT) |
b237a168 ZJS |
53 | return 0; |
54 | if (r < 0) | |
55 | return r; | |
56 | ||
4b3ca79e ZJS |
57 | if (log_val) { |
58 | const char *log; | |
59 | size_t n; | |
60 | ||
61 | /* unquote */ | |
62 | n = strlen(log_val); | |
63 | if (n >= 2 && | |
64 | ((log_val[0] == '"' && log_val[n-1] == '"') || | |
65 | (log_val[0] == '\'' && log_val[n-1] == '\''))) { | |
66 | log_val[n - 1] = '\0'; | |
67 | log = log_val + 1; | |
68 | } else | |
69 | log = log_val; | |
70 | ||
71 | /* we set the udev log level here explicitly, this is supposed | |
72 | * to regulate the code in libudev/ and udev/. */ | |
3cc6b14a | 73 | r = log_set_max_level_from_string(log); |
4b3ca79e | 74 | if (r < 0) |
d7921114 ZJS |
75 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
76 | "failed to set udev log level '%s', ignoring: %m", log); | |
4b3ca79e ZJS |
77 | } |
78 | ||
79 | if (ret_children_max && children_max) { | |
80 | r = safe_atou(children_max, ret_children_max); | |
81 | if (r < 0) | |
d7921114 | 82 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
e2099267 | 83 | "failed to parse children_max=%s, ignoring: %m", children_max); |
4b3ca79e ZJS |
84 | } |
85 | ||
86 | if (ret_exec_delay_usec && exec_delay) { | |
87 | r = parse_sec(exec_delay, ret_exec_delay_usec); | |
88 | if (r < 0) | |
d7921114 | 89 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
e2099267 | 90 | "failed to parse exec_delay=%s, ignoring: %m", exec_delay); |
4b3ca79e ZJS |
91 | } |
92 | ||
93 | if (ret_event_timeout_usec && event_timeout) { | |
94 | r = parse_sec(event_timeout, ret_event_timeout_usec); | |
95 | if (r < 0) | |
d7921114 | 96 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
e2099267 | 97 | "failed to parse event_timeout=%s, ignoring: %m", event_timeout); |
4b3ca79e | 98 | } |
b237a168 | 99 | |
a14e7af1 ZJS |
100 | if (ret_resolve_name_timing && resolve_names) { |
101 | ResolveNameTiming t; | |
102 | ||
103 | t = resolve_name_timing_from_string(resolve_names); | |
104 | if (t < 0) | |
d7921114 | 105 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
e2099267 | 106 | "failed to parse resolve_names=%s, ignoring.", resolve_names); |
a14e7af1 ZJS |
107 | else |
108 | *ret_resolve_name_timing = t; | |
109 | } | |
e2099267 MS |
110 | |
111 | if (ret_timeout_signal && timeout_signal) { | |
112 | r = signal_from_string(timeout_signal); | |
113 | if (r < 0) | |
114 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, | |
115 | "failed to parse timeout_signal=%s, ignoring: %m", timeout_signal); | |
116 | else | |
117 | *ret_timeout_signal = r; | |
118 | } | |
a14e7af1 | 119 | |
b237a168 ZJS |
120 | return 0; |
121 | } | |
ed435031 | 122 | |
030a0d79 LB |
123 | /* Note that if -ENOENT is returned, it will be logged at debug level rather than error, |
124 | * because it's an expected, common occurrence that the caller will handle with a fallback */ | |
125 | static int device_new_from_dev_path(const char *devlink, sd_device **ret_device) { | |
126 | struct stat st; | |
127 | int r; | |
128 | ||
129 | assert(devlink); | |
130 | ||
fe79f107 ZJS |
131 | if (stat(devlink, &st) < 0) |
132 | return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, | |
133 | "Failed to stat() %s: %m", devlink); | |
030a0d79 LB |
134 | |
135 | if (!S_ISBLK(st.st_mode)) | |
fe79f107 ZJS |
136 | return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), |
137 | "%s does not point to a block device: %m", devlink); | |
030a0d79 | 138 | |
930aa88f | 139 | r = sd_device_new_from_stat_rdev(ret_device, &st); |
030a0d79 LB |
140 | if (r < 0) |
141 | return log_error_errno(r, "Failed to initialize device from %s: %m", devlink); | |
142 | ||
143 | return 0; | |
144 | } | |
145 | ||
ed435031 ZJS |
146 | struct DeviceMonitorData { |
147 | const char *sysname; | |
030a0d79 | 148 | const char *devlink; |
ed435031 ZJS |
149 | sd_device *device; |
150 | }; | |
151 | ||
ce5eef65 LB |
152 | static void device_monitor_data_free(struct DeviceMonitorData *d) { |
153 | assert(d); | |
154 | ||
155 | sd_device_unref(d->device); | |
156 | } | |
157 | ||
ed435031 ZJS |
158 | static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) { |
159 | struct DeviceMonitorData *data = userdata; | |
160 | const char *sysname; | |
161 | ||
162 | assert(device); | |
163 | assert(data); | |
030a0d79 | 164 | assert(data->sysname || data->devlink); |
ed435031 ZJS |
165 | assert(!data->device); |
166 | ||
e13d96ca LP |
167 | /* Ignore REMOVE events here. We are waiting for initialization after all, not de-initialization. We |
168 | * might see a REMOVE event from an earlier use of the device (devices by the same name are recycled | |
169 | * by the kernel after all), which we should not get confused by. After all we cannot distinguish use | |
170 | * cycles of the devices, as the udev queue is entirely asynchronous. | |
171 | * | |
172 | * If we see a REMOVE event here for the use cycle we actually care about then we won't notice of | |
173 | * course, but that should be OK, given the timeout logic used on the wait loop: this will be noticed | |
174 | * by means of -ETIMEDOUT. Thus we won't notice immediately, but eventually, and that should be | |
175 | * sufficient for an error path that should regularly not happen. | |
176 | * | |
177 | * (And yes, we only need to special case REMOVE. It's the only "negative" event type, where a device | |
178 | * ceases to exist. All other event types are "positive": the device exists and is registered in the | |
179 | * udev database, thus whenever we see the event, we can consider it initialized.) */ | |
a1130022 | 180 | if (device_for_action(device, SD_DEVICE_REMOVE)) |
e13d96ca LP |
181 | return 0; |
182 | ||
030a0d79 LB |
183 | if (data->sysname && sd_device_get_sysname(device, &sysname) >= 0 && streq(sysname, data->sysname)) |
184 | goto found; | |
185 | ||
186 | if (data->devlink) { | |
187 | const char *devlink; | |
188 | ||
189 | FOREACH_DEVICE_DEVLINK(device, devlink) | |
190 | if (path_equal(devlink, data->devlink)) | |
191 | goto found; | |
192 | ||
193 | if (sd_device_get_devname(device, &devlink) >= 0 && path_equal(devlink, data->devlink)) | |
194 | goto found; | |
ed435031 ZJS |
195 | } |
196 | ||
197 | return 0; | |
030a0d79 LB |
198 | |
199 | found: | |
200 | data->device = sd_device_ref(device); | |
201 | return sd_event_exit(sd_device_monitor_get_event(monitor), 0); | |
ed435031 ZJS |
202 | } |
203 | ||
030a0d79 LB |
204 | static int device_wait_for_initialization_internal( |
205 | sd_device *_device, | |
206 | const char *devlink, | |
207 | const char *subsystem, | |
9e3d9067 | 208 | usec_t deadline, |
030a0d79 | 209 | sd_device **ret) { |
9e3d9067 | 210 | |
ed435031 | 211 | _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; |
1b47436e | 212 | _cleanup_(sd_event_source_unrefp) sd_event_source *timeout_source = NULL; |
ed435031 | 213 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; |
030a0d79 LB |
214 | /* Ensure that if !_device && devlink, device gets unrefd on errors since it will be new */ |
215 | _cleanup_(sd_device_unrefp) sd_device *device = sd_device_ref(_device); | |
ce5eef65 | 216 | _cleanup_(device_monitor_data_free) struct DeviceMonitorData data = { |
030a0d79 LB |
217 | .devlink = devlink, |
218 | }; | |
ed435031 ZJS |
219 | int r; |
220 | ||
030a0d79 | 221 | assert(device || (subsystem && devlink)); |
ed435031 | 222 | |
030a0d79 LB |
223 | /* Devlink might already exist, if it does get the device to use the sysname filtering */ |
224 | if (!device && devlink) { | |
225 | r = device_new_from_dev_path(devlink, &device); | |
226 | if (r < 0 && r != -ENOENT) | |
227 | return r; | |
ed435031 ZJS |
228 | } |
229 | ||
030a0d79 LB |
230 | if (device) { |
231 | if (sd_device_get_is_initialized(device) > 0) { | |
232 | if (ret) | |
233 | *ret = sd_device_ref(device); | |
234 | return 0; | |
235 | } | |
236 | /* We need either the sysname or the devlink for filtering */ | |
237 | assert_se(sd_device_get_sysname(device, &data.sysname) >= 0 || devlink); | |
238 | } | |
ed435031 ZJS |
239 | |
240 | /* Wait until the device is initialized, so that we can get access to the ID_PATH property */ | |
241 | ||
fc40bfa7 | 242 | r = sd_event_new(&event); |
ed435031 ZJS |
243 | if (r < 0) |
244 | return log_error_errno(r, "Failed to get default event: %m"); | |
245 | ||
246 | r = sd_device_monitor_new(&monitor); | |
247 | if (r < 0) | |
248 | return log_error_errno(r, "Failed to acquire monitor: %m"); | |
249 | ||
030a0d79 | 250 | if (device && !subsystem) { |
f822c5d5 YW |
251 | r = sd_device_get_subsystem(device, &subsystem); |
252 | if (r < 0 && r != -ENOENT) | |
253 | return log_device_error_errno(device, r, "Failed to get subsystem: %m"); | |
254 | } | |
255 | ||
256 | if (subsystem) { | |
257 | r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, subsystem, NULL); | |
258 | if (r < 0) | |
259 | return log_error_errno(r, "Failed to add %s subsystem match to monitor: %m", subsystem); | |
260 | } | |
ed435031 ZJS |
261 | |
262 | r = sd_device_monitor_attach_event(monitor, event); | |
263 | if (r < 0) | |
264 | return log_error_errno(r, "Failed to attach event to device monitor: %m"); | |
265 | ||
266 | r = sd_device_monitor_start(monitor, device_monitor_handler, &data); | |
267 | if (r < 0) | |
268 | return log_error_errno(r, "Failed to start device monitor: %m"); | |
269 | ||
9e3d9067 LP |
270 | if (deadline != USEC_INFINITY) { |
271 | r = sd_event_add_time( | |
39cf0351 | 272 | event, &timeout_source, |
9e3d9067 | 273 | CLOCK_MONOTONIC, deadline, 0, |
bac0bfc1 | 274 | NULL, INT_TO_PTR(-ETIMEDOUT)); |
1b47436e YW |
275 | if (r < 0) |
276 | return log_error_errno(r, "Failed to add timeout event source: %m"); | |
277 | } | |
278 | ||
ed435031 ZJS |
279 | /* Check again, maybe things changed. Udev will re-read the db if the device wasn't initialized |
280 | * yet. */ | |
030a0d79 LB |
281 | if (!device && devlink) { |
282 | r = device_new_from_dev_path(devlink, &device); | |
283 | if (r < 0 && r != -ENOENT) | |
284 | return r; | |
285 | } | |
286 | if (device && sd_device_get_is_initialized(device) > 0) { | |
ed435031 ZJS |
287 | if (ret) |
288 | *ret = sd_device_ref(device); | |
289 | return 0; | |
290 | } | |
291 | ||
292 | r = sd_event_loop(event); | |
293 | if (r < 0) | |
1b47436e | 294 | return log_error_errno(r, "Failed to wait for device to be initialized: %m"); |
ed435031 ZJS |
295 | |
296 | if (ret) | |
297 | *ret = TAKE_PTR(data.device); | |
298 | return 0; | |
299 | } | |
90ba130f | 300 | |
9e3d9067 LP |
301 | int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t deadline, sd_device **ret) { |
302 | return device_wait_for_initialization_internal(device, NULL, subsystem, deadline, ret); | |
030a0d79 LB |
303 | } |
304 | ||
9e3d9067 LP |
305 | int device_wait_for_devlink(const char *devlink, const char *subsystem, usec_t deadline, sd_device **ret) { |
306 | return device_wait_for_initialization_internal(NULL, devlink, subsystem, deadline, ret); | |
030a0d79 LB |
307 | } |
308 | ||
90ba130f YW |
309 | int device_is_renaming(sd_device *dev) { |
310 | int r; | |
311 | ||
312 | assert(dev); | |
313 | ||
314 | r = sd_device_get_property_value(dev, "ID_RENAMING", NULL); | |
b9daaedb LP |
315 | if (r == -ENOENT) |
316 | return false; | |
317 | if (r < 0) | |
90ba130f YW |
318 | return r; |
319 | ||
b9daaedb | 320 | return true; |
90ba130f | 321 | } |
a707c65b | 322 | |
a1130022 LP |
323 | bool device_for_action(sd_device *dev, sd_device_action_t a) { |
324 | sd_device_action_t b; | |
a707c65b YW |
325 | |
326 | assert(dev); | |
327 | ||
a1130022 | 328 | if (a < 0) |
a707c65b YW |
329 | return false; |
330 | ||
a1130022 LP |
331 | if (sd_device_get_action(dev, &b) < 0) |
332 | return false; | |
333 | ||
334 | return a == b; | |
a707c65b | 335 | } |
aea3253e | 336 | |
b2d9e58f | 337 | void log_device_uevent(sd_device *device, const char *str) { |
a1130022 | 338 | sd_device_action_t action = _SD_DEVICE_ACTION_INVALID; |
b2d9e58f YW |
339 | uint64_t seqnum = 0; |
340 | ||
341 | if (!DEBUG_LOGGING) | |
342 | return; | |
343 | ||
a1130022 LP |
344 | (void) sd_device_get_seqnum(device, &seqnum); |
345 | (void) sd_device_get_action(device, &action); | |
b2d9e58f YW |
346 | log_device_debug(device, "%s%s(SEQNUM=%"PRIu64", ACTION=%s)", |
347 | strempty(str), isempty(str) ? "" : " ", | |
348 | seqnum, strna(device_action_to_string(action))); | |
349 | } | |
350 | ||
aea3253e YLY |
351 | int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos) { |
352 | char *i, *j; | |
353 | int r; | |
354 | bool is_escaped; | |
355 | ||
356 | /* value must be double quotated */ | |
357 | is_escaped = str[0] == 'e'; | |
358 | str += is_escaped; | |
359 | if (str[0] != '"') | |
360 | return -EINVAL; | |
361 | str++; | |
362 | ||
363 | if (!is_escaped) { | |
364 | /* unescape double quotation '\"'->'"' */ | |
365 | for (i = j = str; *i != '"'; i++, j++) { | |
366 | if (*i == '\0') | |
367 | return -EINVAL; | |
368 | if (i[0] == '\\' && i[1] == '"') | |
369 | i++; | |
370 | *j = *i; | |
371 | } | |
372 | j[0] = '\0'; | |
373 | } else { | |
374 | _cleanup_free_ char *unescaped = NULL; | |
375 | ||
376 | /* find the end position of value */ | |
377 | for (i = str; *i != '"'; i++) { | |
378 | if (i[0] == '\\') | |
379 | i++; | |
380 | if (*i == '\0') | |
381 | return -EINVAL; | |
382 | } | |
383 | i[0] = '\0'; | |
384 | ||
385 | r = cunescape_length(str, i - str, 0, &unescaped); | |
386 | if (r < 0) | |
387 | return r; | |
388 | assert(r <= i - str); | |
389 | memcpy(str, unescaped, r + 1); | |
390 | } | |
391 | ||
392 | *ret_value = str; | |
393 | *ret_endpos = i + 1; | |
394 | return 0; | |
395 | } | |
5953d8b9 YW |
396 | |
397 | size_t udev_replace_whitespace(const char *str, char *to, size_t len) { | |
398 | bool is_space = false; | |
399 | size_t i, j; | |
400 | ||
401 | assert(str); | |
402 | assert(to); | |
403 | ||
404 | /* Copy from 'str' to 'to', while removing all leading and trailing whitespace, and replacing | |
405 | * each run of consecutive whitespace with a single underscore. The chars from 'str' are copied | |
406 | * up to the \0 at the end of the string, or at most 'len' chars. This appends \0 to 'to', at | |
407 | * the end of the copied characters. | |
408 | * | |
409 | * If 'len' chars are copied into 'to', the final \0 is placed at len+1 (i.e. 'to[len] = \0'), | |
410 | * so the 'to' buffer must have at least len+1 chars available. | |
411 | * | |
412 | * Note this may be called with 'str' == 'to', i.e. to replace whitespace in-place in a buffer. | |
413 | * This function can handle that situation. | |
414 | * | |
415 | * Note that only 'len' characters are read from 'str'. */ | |
416 | ||
417 | i = strspn(str, WHITESPACE); | |
418 | ||
419 | for (j = 0; j < len && i < len && str[i] != '\0'; i++) { | |
420 | if (isspace(str[i])) { | |
421 | is_space = true; | |
422 | continue; | |
423 | } | |
424 | ||
425 | if (is_space) { | |
426 | if (j + 1 >= len) | |
427 | break; | |
428 | ||
429 | to[j++] = '_'; | |
430 | is_space = false; | |
431 | } | |
432 | to[j++] = str[i]; | |
433 | } | |
434 | ||
435 | to[j] = '\0'; | |
436 | return j; | |
437 | } | |
393fcaf7 YW |
438 | |
439 | size_t udev_replace_chars(char *str, const char *allow) { | |
440 | size_t i = 0, replaced = 0; | |
441 | ||
442 | assert(str); | |
443 | ||
444 | /* allow chars in allow list, plain ascii, hex-escaping and valid utf8. */ | |
445 | ||
446 | while (str[i] != '\0') { | |
447 | int len; | |
448 | ||
449 | if (allow_listed_char_for_devnode(str[i], allow)) { | |
450 | i++; | |
451 | continue; | |
452 | } | |
453 | ||
454 | /* accept hex encoding */ | |
455 | if (str[i] == '\\' && str[i+1] == 'x') { | |
456 | i += 2; | |
457 | continue; | |
458 | } | |
459 | ||
460 | /* accept valid utf8 */ | |
f5fbe71d | 461 | len = utf8_encoded_valid_unichar(str + i, SIZE_MAX); |
393fcaf7 YW |
462 | if (len > 1) { |
463 | i += len; | |
464 | continue; | |
465 | } | |
466 | ||
467 | /* if space is allowed, replace whitespace with ordinary space */ | |
468 | if (isspace(str[i]) && allow && strchr(allow, ' ')) { | |
469 | str[i] = ' '; | |
470 | i++; | |
471 | replaced++; | |
472 | continue; | |
473 | } | |
474 | ||
475 | /* everything else is replaced with '_' */ | |
476 | str[i] = '_'; | |
477 | i++; | |
478 | replaced++; | |
479 | } | |
480 | return replaced; | |
481 | } | |
1223227f YW |
482 | |
483 | int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value) { | |
484 | _cleanup_(sd_device_unrefp) sd_device *dev = NULL; | |
485 | _cleanup_free_ char *temp = NULL; | |
486 | char *subsys, *sysname, *attr; | |
487 | const char *val; | |
488 | int r; | |
489 | ||
490 | assert(string); | |
491 | assert(result); | |
492 | ||
493 | /* handle "[<SUBSYSTEM>/<KERNEL>]<attribute>" format */ | |
494 | ||
495 | if (string[0] != '[') | |
496 | return -EINVAL; | |
497 | ||
498 | temp = strdup(string); | |
499 | if (!temp) | |
500 | return -ENOMEM; | |
501 | ||
502 | subsys = &temp[1]; | |
503 | ||
504 | sysname = strchr(subsys, '/'); | |
505 | if (!sysname) | |
506 | return -EINVAL; | |
507 | sysname[0] = '\0'; | |
508 | sysname = &sysname[1]; | |
509 | ||
510 | attr = strchr(sysname, ']'); | |
511 | if (!attr) | |
512 | return -EINVAL; | |
513 | attr[0] = '\0'; | |
514 | attr = &attr[1]; | |
515 | if (attr[0] == '/') | |
516 | attr = &attr[1]; | |
517 | if (attr[0] == '\0') | |
518 | attr = NULL; | |
519 | ||
520 | if (read_value && !attr) | |
521 | return -EINVAL; | |
522 | ||
523 | r = sd_device_new_from_subsystem_sysname(&dev, subsys, sysname); | |
524 | if (r < 0) | |
525 | return r; | |
526 | ||
527 | if (read_value) { | |
528 | r = sd_device_get_sysattr_value(dev, attr, &val); | |
acfc2a1d | 529 | if (r < 0 && !ERRNO_IS_PRIVILEGE(r) && r != -ENOENT) |
1223227f | 530 | return r; |
acfc2a1d | 531 | if (r >= 0) |
1223227f | 532 | strscpy(result, maxsize, val); |
acfc2a1d YW |
533 | else |
534 | result[0] = '\0'; | |
1223227f YW |
535 | log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); |
536 | } else { | |
537 | r = sd_device_get_syspath(dev, &val); | |
538 | if (r < 0) | |
539 | return r; | |
540 | ||
541 | strscpyl(result, maxsize, val, attr ? "/" : NULL, attr ?: NULL, NULL); | |
542 | log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, strempty(attr), result); | |
543 | } | |
544 | return 0; | |
545 | } | |
bee33d05 YW |
546 | |
547 | int udev_queue_is_empty(void) { | |
548 | return access("/run/udev/queue", F_OK) < 0 ? | |
549 | (errno == ENOENT ? true : -errno) : false; | |
550 | } | |
551 | ||
552 | int udev_queue_init(void) { | |
553 | _cleanup_close_ int fd = -1; | |
554 | ||
555 | fd = inotify_init1(IN_CLOEXEC); | |
556 | if (fd < 0) | |
557 | return -errno; | |
558 | ||
559 | if (inotify_add_watch(fd, "/run/udev" , IN_DELETE) < 0) | |
560 | return -errno; | |
561 | ||
562 | return TAKE_FD(fd); | |
563 | } |