]>
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" |
e9162760 | 16 | #include "id128-util.h" |
b237a168 | 17 | #include "log.h" |
aea3253e | 18 | #include "macro.h" |
4b3ca79e | 19 | #include "parse-util.h" |
030a0d79 | 20 | #include "path-util.h" |
e2099267 | 21 | #include "signal-util.h" |
e1ecfef1 | 22 | #include "socket-util.h" |
f92c5bb1 | 23 | #include "stat-util.h" |
bc768f04 | 24 | #include "string-table.h" |
b237a168 | 25 | #include "string-util.h" |
1223227f | 26 | #include "strxcpyx.h" |
b237a168 | 27 | #include "udev-util.h" |
aea3253e | 28 | #include "utf8.h" |
b237a168 | 29 | |
bc768f04 ZJS |
30 | static const char* const resolve_name_timing_table[_RESOLVE_NAME_TIMING_MAX] = { |
31 | [RESOLVE_NAME_NEVER] = "never", | |
1c6e17e5 | 32 | [RESOLVE_NAME_LATE] = "late", |
bc768f04 ZJS |
33 | [RESOLVE_NAME_EARLY] = "early", |
34 | }; | |
35 | ||
36 | DEFINE_STRING_TABLE_LOOKUP(resolve_name_timing, ResolveNameTiming); | |
37 | ||
4b3ca79e ZJS |
38 | int udev_parse_config_full( |
39 | unsigned *ret_children_max, | |
40 | usec_t *ret_exec_delay_usec, | |
a14e7af1 | 41 | usec_t *ret_event_timeout_usec, |
e2099267 MS |
42 | ResolveNameTiming *ret_resolve_name_timing, |
43 | int *ret_timeout_signal) { | |
4b3ca79e | 44 | |
e2099267 | 45 | _cleanup_free_ char *log_val = NULL, *children_max = NULL, *exec_delay = NULL, *event_timeout = NULL, *resolve_names = NULL, *timeout_signal = NULL; |
b237a168 ZJS |
46 | int r; |
47 | ||
aa8fbc74 | 48 | r = parse_env_file(NULL, "/etc/udev/udev.conf", |
4b3ca79e ZJS |
49 | "udev_log", &log_val, |
50 | "children_max", &children_max, | |
51 | "exec_delay", &exec_delay, | |
9b2934cb | 52 | "event_timeout", &event_timeout, |
e2099267 MS |
53 | "resolve_names", &resolve_names, |
54 | "timeout_signal", &timeout_signal); | |
4b3ca79e | 55 | if (r == -ENOENT) |
b237a168 ZJS |
56 | return 0; |
57 | if (r < 0) | |
58 | return r; | |
59 | ||
4b3ca79e ZJS |
60 | if (log_val) { |
61 | const char *log; | |
62 | size_t n; | |
63 | ||
64 | /* unquote */ | |
65 | n = strlen(log_val); | |
66 | if (n >= 2 && | |
67 | ((log_val[0] == '"' && log_val[n-1] == '"') || | |
68 | (log_val[0] == '\'' && log_val[n-1] == '\''))) { | |
69 | log_val[n - 1] = '\0'; | |
70 | log = log_val + 1; | |
71 | } else | |
72 | log = log_val; | |
73 | ||
74 | /* we set the udev log level here explicitly, this is supposed | |
75 | * to regulate the code in libudev/ and udev/. */ | |
3cc6b14a | 76 | r = log_set_max_level_from_string(log); |
4b3ca79e | 77 | if (r < 0) |
d7921114 ZJS |
78 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
79 | "failed to set udev log level '%s', ignoring: %m", log); | |
4b3ca79e ZJS |
80 | } |
81 | ||
82 | if (ret_children_max && children_max) { | |
83 | r = safe_atou(children_max, ret_children_max); | |
84 | if (r < 0) | |
d7921114 | 85 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
e2099267 | 86 | "failed to parse children_max=%s, ignoring: %m", children_max); |
4b3ca79e ZJS |
87 | } |
88 | ||
89 | if (ret_exec_delay_usec && exec_delay) { | |
90 | r = parse_sec(exec_delay, ret_exec_delay_usec); | |
91 | if (r < 0) | |
d7921114 | 92 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
e2099267 | 93 | "failed to parse exec_delay=%s, ignoring: %m", exec_delay); |
4b3ca79e ZJS |
94 | } |
95 | ||
96 | if (ret_event_timeout_usec && event_timeout) { | |
97 | r = parse_sec(event_timeout, ret_event_timeout_usec); | |
98 | if (r < 0) | |
d7921114 | 99 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
e2099267 | 100 | "failed to parse event_timeout=%s, ignoring: %m", event_timeout); |
4b3ca79e | 101 | } |
b237a168 | 102 | |
a14e7af1 ZJS |
103 | if (ret_resolve_name_timing && resolve_names) { |
104 | ResolveNameTiming t; | |
105 | ||
106 | t = resolve_name_timing_from_string(resolve_names); | |
107 | if (t < 0) | |
d7921114 | 108 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, |
e2099267 | 109 | "failed to parse resolve_names=%s, ignoring.", resolve_names); |
a14e7af1 ZJS |
110 | else |
111 | *ret_resolve_name_timing = t; | |
112 | } | |
e2099267 MS |
113 | |
114 | if (ret_timeout_signal && timeout_signal) { | |
115 | r = signal_from_string(timeout_signal); | |
116 | if (r < 0) | |
117 | log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, | |
118 | "failed to parse timeout_signal=%s, ignoring: %m", timeout_signal); | |
119 | else | |
120 | *ret_timeout_signal = r; | |
121 | } | |
a14e7af1 | 122 | |
b237a168 ZJS |
123 | return 0; |
124 | } | |
ed435031 ZJS |
125 | |
126 | struct DeviceMonitorData { | |
127 | const char *sysname; | |
030a0d79 | 128 | const char *devlink; |
ed435031 ZJS |
129 | sd_device *device; |
130 | }; | |
131 | ||
ce5eef65 LB |
132 | static void device_monitor_data_free(struct DeviceMonitorData *d) { |
133 | assert(d); | |
134 | ||
135 | sd_device_unref(d->device); | |
136 | } | |
137 | ||
ed435031 | 138 | static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) { |
99534007 | 139 | struct DeviceMonitorData *data = ASSERT_PTR(userdata); |
ed435031 ZJS |
140 | const char *sysname; |
141 | ||
142 | assert(device); | |
030a0d79 | 143 | assert(data->sysname || data->devlink); |
ed435031 ZJS |
144 | assert(!data->device); |
145 | ||
e13d96ca LP |
146 | /* Ignore REMOVE events here. We are waiting for initialization after all, not de-initialization. We |
147 | * might see a REMOVE event from an earlier use of the device (devices by the same name are recycled | |
148 | * by the kernel after all), which we should not get confused by. After all we cannot distinguish use | |
149 | * cycles of the devices, as the udev queue is entirely asynchronous. | |
150 | * | |
151 | * If we see a REMOVE event here for the use cycle we actually care about then we won't notice of | |
152 | * course, but that should be OK, given the timeout logic used on the wait loop: this will be noticed | |
153 | * by means of -ETIMEDOUT. Thus we won't notice immediately, but eventually, and that should be | |
154 | * sufficient for an error path that should regularly not happen. | |
155 | * | |
156 | * (And yes, we only need to special case REMOVE. It's the only "negative" event type, where a device | |
157 | * ceases to exist. All other event types are "positive": the device exists and is registered in the | |
158 | * udev database, thus whenever we see the event, we can consider it initialized.) */ | |
a1130022 | 159 | if (device_for_action(device, SD_DEVICE_REMOVE)) |
e13d96ca LP |
160 | return 0; |
161 | ||
030a0d79 LB |
162 | if (data->sysname && sd_device_get_sysname(device, &sysname) >= 0 && streq(sysname, data->sysname)) |
163 | goto found; | |
164 | ||
165 | if (data->devlink) { | |
166 | const char *devlink; | |
167 | ||
168 | FOREACH_DEVICE_DEVLINK(device, devlink) | |
169 | if (path_equal(devlink, data->devlink)) | |
170 | goto found; | |
171 | ||
172 | if (sd_device_get_devname(device, &devlink) >= 0 && path_equal(devlink, data->devlink)) | |
173 | goto found; | |
ed435031 ZJS |
174 | } |
175 | ||
176 | return 0; | |
030a0d79 LB |
177 | |
178 | found: | |
179 | data->device = sd_device_ref(device); | |
180 | return sd_event_exit(sd_device_monitor_get_event(monitor), 0); | |
ed435031 ZJS |
181 | } |
182 | ||
030a0d79 LB |
183 | static int device_wait_for_initialization_internal( |
184 | sd_device *_device, | |
185 | const char *devlink, | |
186 | const char *subsystem, | |
4f89ce0c | 187 | usec_t timeout_usec, |
030a0d79 | 188 | sd_device **ret) { |
9e3d9067 | 189 | |
ed435031 ZJS |
190 | _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; |
191 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; | |
030a0d79 LB |
192 | /* Ensure that if !_device && devlink, device gets unrefd on errors since it will be new */ |
193 | _cleanup_(sd_device_unrefp) sd_device *device = sd_device_ref(_device); | |
ce5eef65 | 194 | _cleanup_(device_monitor_data_free) struct DeviceMonitorData data = { |
030a0d79 LB |
195 | .devlink = devlink, |
196 | }; | |
ed435031 ZJS |
197 | int r; |
198 | ||
030a0d79 | 199 | assert(device || (subsystem && devlink)); |
ed435031 | 200 | |
030a0d79 LB |
201 | /* Devlink might already exist, if it does get the device to use the sysname filtering */ |
202 | if (!device && devlink) { | |
f81b3e90 YW |
203 | r = sd_device_new_from_devname(&device, devlink); |
204 | if (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)) | |
205 | return log_error_errno(r, "Failed to create sd-device object from %s: %m", devlink); | |
ed435031 ZJS |
206 | } |
207 | ||
030a0d79 LB |
208 | if (device) { |
209 | if (sd_device_get_is_initialized(device) > 0) { | |
210 | if (ret) | |
211 | *ret = sd_device_ref(device); | |
212 | return 0; | |
213 | } | |
214 | /* We need either the sysname or the devlink for filtering */ | |
215 | assert_se(sd_device_get_sysname(device, &data.sysname) >= 0 || devlink); | |
216 | } | |
ed435031 ZJS |
217 | |
218 | /* Wait until the device is initialized, so that we can get access to the ID_PATH property */ | |
219 | ||
fc40bfa7 | 220 | r = sd_event_new(&event); |
ed435031 ZJS |
221 | if (r < 0) |
222 | return log_error_errno(r, "Failed to get default event: %m"); | |
223 | ||
224 | r = sd_device_monitor_new(&monitor); | |
225 | if (r < 0) | |
226 | return log_error_errno(r, "Failed to acquire monitor: %m"); | |
227 | ||
030a0d79 | 228 | if (device && !subsystem) { |
f822c5d5 YW |
229 | r = sd_device_get_subsystem(device, &subsystem); |
230 | if (r < 0 && r != -ENOENT) | |
231 | return log_device_error_errno(device, r, "Failed to get subsystem: %m"); | |
232 | } | |
233 | ||
234 | if (subsystem) { | |
235 | r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, subsystem, NULL); | |
236 | if (r < 0) | |
237 | return log_error_errno(r, "Failed to add %s subsystem match to monitor: %m", subsystem); | |
238 | } | |
ed435031 | 239 | |
17bf3c55 YW |
240 | _cleanup_free_ char *desc = NULL; |
241 | const char *sysname = NULL; | |
242 | if (device) | |
243 | (void) sd_device_get_sysname(device, &sysname); | |
244 | ||
245 | desc = strjoin(sysname ?: subsystem, devlink ? ":" : ":initialization", devlink); | |
246 | if (desc) | |
247 | (void) sd_device_monitor_set_description(monitor, desc); | |
248 | ||
ed435031 ZJS |
249 | r = sd_device_monitor_attach_event(monitor, event); |
250 | if (r < 0) | |
251 | return log_error_errno(r, "Failed to attach event to device monitor: %m"); | |
252 | ||
253 | r = sd_device_monitor_start(monitor, device_monitor_handler, &data); | |
254 | if (r < 0) | |
255 | return log_error_errno(r, "Failed to start device monitor: %m"); | |
256 | ||
4f89ce0c YW |
257 | if (timeout_usec != USEC_INFINITY) { |
258 | r = sd_event_add_time_relative( | |
259 | event, NULL, | |
260 | CLOCK_MONOTONIC, timeout_usec, 0, | |
bac0bfc1 | 261 | NULL, INT_TO_PTR(-ETIMEDOUT)); |
1b47436e YW |
262 | if (r < 0) |
263 | return log_error_errno(r, "Failed to add timeout event source: %m"); | |
264 | } | |
265 | ||
4f89ce0c | 266 | /* Check again, maybe things changed. Udev will re-read the db if the device wasn't initialized yet. */ |
030a0d79 | 267 | if (!device && devlink) { |
f81b3e90 YW |
268 | r = sd_device_new_from_devname(&device, devlink); |
269 | if (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)) | |
270 | return log_error_errno(r, "Failed to create sd-device object from %s: %m", devlink); | |
030a0d79 LB |
271 | } |
272 | if (device && sd_device_get_is_initialized(device) > 0) { | |
ed435031 ZJS |
273 | if (ret) |
274 | *ret = sd_device_ref(device); | |
275 | return 0; | |
276 | } | |
277 | ||
278 | r = sd_event_loop(event); | |
279 | if (r < 0) | |
1b47436e | 280 | return log_error_errno(r, "Failed to wait for device to be initialized: %m"); |
ed435031 ZJS |
281 | |
282 | if (ret) | |
283 | *ret = TAKE_PTR(data.device); | |
284 | return 0; | |
285 | } | |
90ba130f | 286 | |
4f89ce0c YW |
287 | int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t timeout_usec, sd_device **ret) { |
288 | return device_wait_for_initialization_internal(device, NULL, subsystem, timeout_usec, ret); | |
030a0d79 LB |
289 | } |
290 | ||
4f89ce0c YW |
291 | int device_wait_for_devlink(const char *devlink, const char *subsystem, usec_t timeout_usec, sd_device **ret) { |
292 | return device_wait_for_initialization_internal(NULL, devlink, subsystem, timeout_usec, ret); | |
030a0d79 LB |
293 | } |
294 | ||
90ba130f YW |
295 | int device_is_renaming(sd_device *dev) { |
296 | int r; | |
297 | ||
298 | assert(dev); | |
299 | ||
300 | r = sd_device_get_property_value(dev, "ID_RENAMING", NULL); | |
b9daaedb LP |
301 | if (r == -ENOENT) |
302 | return false; | |
303 | if (r < 0) | |
90ba130f YW |
304 | return r; |
305 | ||
b9daaedb | 306 | return true; |
90ba130f | 307 | } |
a707c65b | 308 | |
a1130022 LP |
309 | bool device_for_action(sd_device *dev, sd_device_action_t a) { |
310 | sd_device_action_t b; | |
a707c65b YW |
311 | |
312 | assert(dev); | |
313 | ||
a1130022 | 314 | if (a < 0) |
a707c65b YW |
315 | return false; |
316 | ||
a1130022 LP |
317 | if (sd_device_get_action(dev, &b) < 0) |
318 | return false; | |
319 | ||
320 | return a == b; | |
a707c65b | 321 | } |
aea3253e | 322 | |
b2d9e58f | 323 | void log_device_uevent(sd_device *device, const char *str) { |
a1130022 | 324 | sd_device_action_t action = _SD_DEVICE_ACTION_INVALID; |
e9162760 | 325 | sd_id128_t event_id = SD_ID128_NULL; |
b2d9e58f YW |
326 | uint64_t seqnum = 0; |
327 | ||
328 | if (!DEBUG_LOGGING) | |
329 | return; | |
330 | ||
a1130022 LP |
331 | (void) sd_device_get_seqnum(device, &seqnum); |
332 | (void) sd_device_get_action(device, &action); | |
e9162760 YW |
333 | (void) sd_device_get_trigger_uuid(device, &event_id); |
334 | log_device_debug(device, "%s%s(SEQNUM=%"PRIu64", ACTION=%s%s%s)", | |
b2d9e58f | 335 | strempty(str), isempty(str) ? "" : " ", |
e9162760 YW |
336 | seqnum, strna(device_action_to_string(action)), |
337 | sd_id128_is_null(event_id) ? "" : ", UUID=", | |
b7416360 | 338 | sd_id128_is_null(event_id) ? "" : SD_ID128_TO_UUID_STRING(event_id)); |
b2d9e58f YW |
339 | } |
340 | ||
aea3253e YLY |
341 | int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos) { |
342 | char *i, *j; | |
aea3253e YLY |
343 | bool is_escaped; |
344 | ||
345 | /* value must be double quotated */ | |
346 | is_escaped = str[0] == 'e'; | |
347 | str += is_escaped; | |
348 | if (str[0] != '"') | |
349 | return -EINVAL; | |
350 | str++; | |
351 | ||
352 | if (!is_escaped) { | |
353 | /* unescape double quotation '\"'->'"' */ | |
354 | for (i = j = str; *i != '"'; i++, j++) { | |
355 | if (*i == '\0') | |
356 | return -EINVAL; | |
357 | if (i[0] == '\\' && i[1] == '"') | |
358 | i++; | |
359 | *j = *i; | |
360 | } | |
361 | j[0] = '\0'; | |
362 | } else { | |
363 | _cleanup_free_ char *unescaped = NULL; | |
e437538f | 364 | ssize_t l; |
aea3253e YLY |
365 | |
366 | /* find the end position of value */ | |
367 | for (i = str; *i != '"'; i++) { | |
368 | if (i[0] == '\\') | |
369 | i++; | |
370 | if (*i == '\0') | |
371 | return -EINVAL; | |
372 | } | |
373 | i[0] = '\0'; | |
374 | ||
e437538f ZJS |
375 | l = cunescape_length(str, i - str, 0, &unescaped); |
376 | if (l < 0) | |
377 | return l; | |
378 | ||
379 | assert(l <= i - str); | |
380 | memcpy(str, unescaped, l + 1); | |
aea3253e YLY |
381 | } |
382 | ||
383 | *ret_value = str; | |
384 | *ret_endpos = i + 1; | |
385 | return 0; | |
386 | } | |
5953d8b9 YW |
387 | |
388 | size_t udev_replace_whitespace(const char *str, char *to, size_t len) { | |
389 | bool is_space = false; | |
390 | size_t i, j; | |
391 | ||
392 | assert(str); | |
393 | assert(to); | |
394 | ||
395 | /* Copy from 'str' to 'to', while removing all leading and trailing whitespace, and replacing | |
396 | * each run of consecutive whitespace with a single underscore. The chars from 'str' are copied | |
397 | * up to the \0 at the end of the string, or at most 'len' chars. This appends \0 to 'to', at | |
398 | * the end of the copied characters. | |
399 | * | |
400 | * If 'len' chars are copied into 'to', the final \0 is placed at len+1 (i.e. 'to[len] = \0'), | |
401 | * so the 'to' buffer must have at least len+1 chars available. | |
402 | * | |
403 | * Note this may be called with 'str' == 'to', i.e. to replace whitespace in-place in a buffer. | |
404 | * This function can handle that situation. | |
405 | * | |
406 | * Note that only 'len' characters are read from 'str'. */ | |
407 | ||
408 | i = strspn(str, WHITESPACE); | |
409 | ||
410 | for (j = 0; j < len && i < len && str[i] != '\0'; i++) { | |
411 | if (isspace(str[i])) { | |
412 | is_space = true; | |
413 | continue; | |
414 | } | |
415 | ||
416 | if (is_space) { | |
417 | if (j + 1 >= len) | |
418 | break; | |
419 | ||
420 | to[j++] = '_'; | |
421 | is_space = false; | |
422 | } | |
423 | to[j++] = str[i]; | |
424 | } | |
425 | ||
426 | to[j] = '\0'; | |
427 | return j; | |
428 | } | |
393fcaf7 | 429 | |
e1ecfef1 YW |
430 | size_t udev_replace_ifname(char *str) { |
431 | size_t replaced = 0; | |
432 | ||
433 | assert(str); | |
434 | ||
435 | /* See ifname_valid_full(). */ | |
436 | ||
437 | for (char *p = str; *p != '\0'; p++) | |
438 | if (!ifname_valid_char(*p)) { | |
439 | *p = '_'; | |
440 | replaced++; | |
441 | } | |
442 | ||
443 | return replaced; | |
444 | } | |
445 | ||
393fcaf7 YW |
446 | size_t udev_replace_chars(char *str, const char *allow) { |
447 | size_t i = 0, replaced = 0; | |
448 | ||
449 | assert(str); | |
450 | ||
451 | /* allow chars in allow list, plain ascii, hex-escaping and valid utf8. */ | |
452 | ||
453 | while (str[i] != '\0') { | |
454 | int len; | |
455 | ||
456 | if (allow_listed_char_for_devnode(str[i], allow)) { | |
457 | i++; | |
458 | continue; | |
459 | } | |
460 | ||
461 | /* accept hex encoding */ | |
462 | if (str[i] == '\\' && str[i+1] == 'x') { | |
463 | i += 2; | |
464 | continue; | |
465 | } | |
466 | ||
467 | /* accept valid utf8 */ | |
f5fbe71d | 468 | len = utf8_encoded_valid_unichar(str + i, SIZE_MAX); |
393fcaf7 YW |
469 | if (len > 1) { |
470 | i += len; | |
471 | continue; | |
472 | } | |
473 | ||
474 | /* if space is allowed, replace whitespace with ordinary space */ | |
475 | if (isspace(str[i]) && allow && strchr(allow, ' ')) { | |
476 | str[i] = ' '; | |
477 | i++; | |
478 | replaced++; | |
479 | continue; | |
480 | } | |
481 | ||
482 | /* everything else is replaced with '_' */ | |
483 | str[i] = '_'; | |
484 | i++; | |
485 | replaced++; | |
486 | } | |
487 | return replaced; | |
488 | } | |
1223227f YW |
489 | |
490 | int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value) { | |
491 | _cleanup_(sd_device_unrefp) sd_device *dev = NULL; | |
492 | _cleanup_free_ char *temp = NULL; | |
493 | char *subsys, *sysname, *attr; | |
494 | const char *val; | |
495 | int r; | |
496 | ||
497 | assert(string); | |
498 | assert(result); | |
499 | ||
500 | /* handle "[<SUBSYSTEM>/<KERNEL>]<attribute>" format */ | |
501 | ||
502 | if (string[0] != '[') | |
503 | return -EINVAL; | |
504 | ||
505 | temp = strdup(string); | |
506 | if (!temp) | |
507 | return -ENOMEM; | |
508 | ||
509 | subsys = &temp[1]; | |
510 | ||
511 | sysname = strchr(subsys, '/'); | |
512 | if (!sysname) | |
513 | return -EINVAL; | |
514 | sysname[0] = '\0'; | |
515 | sysname = &sysname[1]; | |
516 | ||
517 | attr = strchr(sysname, ']'); | |
518 | if (!attr) | |
519 | return -EINVAL; | |
520 | attr[0] = '\0'; | |
521 | attr = &attr[1]; | |
522 | if (attr[0] == '/') | |
523 | attr = &attr[1]; | |
524 | if (attr[0] == '\0') | |
525 | attr = NULL; | |
526 | ||
527 | if (read_value && !attr) | |
528 | return -EINVAL; | |
529 | ||
530 | r = sd_device_new_from_subsystem_sysname(&dev, subsys, sysname); | |
531 | if (r < 0) | |
532 | return r; | |
533 | ||
534 | if (read_value) { | |
535 | r = sd_device_get_sysattr_value(dev, attr, &val); | |
acfc2a1d | 536 | if (r < 0 && !ERRNO_IS_PRIVILEGE(r) && r != -ENOENT) |
1223227f | 537 | return r; |
acfc2a1d | 538 | if (r >= 0) |
1223227f | 539 | strscpy(result, maxsize, val); |
acfc2a1d YW |
540 | else |
541 | result[0] = '\0'; | |
1223227f YW |
542 | log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); |
543 | } else { | |
544 | r = sd_device_get_syspath(dev, &val); | |
545 | if (r < 0) | |
546 | return r; | |
547 | ||
548 | strscpyl(result, maxsize, val, attr ? "/" : NULL, attr ?: NULL, NULL); | |
549 | log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, strempty(attr), result); | |
550 | } | |
551 | return 0; | |
552 | } | |
bee33d05 | 553 | |
a1af9668 YW |
554 | bool devpath_conflict(const char *a, const char *b) { |
555 | /* This returns true when two paths are equivalent, or one is a child of another. */ | |
556 | ||
557 | if (!a || !b) | |
558 | return false; | |
559 | ||
560 | for (; *a != '\0' && *b != '\0'; a++, b++) | |
561 | if (*a != *b) | |
562 | return false; | |
563 | ||
564 | return *a == '/' || *b == '/' || *a == *b; | |
565 | } | |
566 | ||
bee33d05 YW |
567 | int udev_queue_is_empty(void) { |
568 | return access("/run/udev/queue", F_OK) < 0 ? | |
569 | (errno == ENOENT ? true : -errno) : false; | |
570 | } | |
571 | ||
572 | int udev_queue_init(void) { | |
254d1313 | 573 | _cleanup_close_ int fd = -EBADF; |
bee33d05 YW |
574 | |
575 | fd = inotify_init1(IN_CLOEXEC); | |
576 | if (fd < 0) | |
577 | return -errno; | |
578 | ||
579 | if (inotify_add_watch(fd, "/run/udev" , IN_DELETE) < 0) | |
580 | return -errno; | |
581 | ||
582 | return TAKE_FD(fd); | |
583 | } | |
06795b02 | 584 | |
795e86b4 YW |
585 | static int device_is_power_sink(sd_device *device) { |
586 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; | |
587 | bool found_source = false, found_sink = false; | |
588 | sd_device *parent, *d; | |
589 | int r; | |
590 | ||
591 | assert(device); | |
592 | ||
593 | /* USB-C power supply device has two power roles: source or sink. See, | |
0e685823 | 594 | * https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-typec */ |
795e86b4 YW |
595 | |
596 | r = sd_device_enumerator_new(&e); | |
597 | if (r < 0) | |
598 | return r; | |
599 | ||
600 | r = sd_device_enumerator_allow_uninitialized(e); | |
601 | if (r < 0) | |
602 | return r; | |
603 | ||
604 | r = sd_device_enumerator_add_match_subsystem(e, "typec", true); | |
605 | if (r < 0) | |
606 | return r; | |
607 | ||
608 | r = sd_device_get_parent(device, &parent); | |
609 | if (r < 0) | |
610 | return r; | |
611 | ||
612 | r = sd_device_enumerator_add_match_parent(e, parent); | |
613 | if (r < 0) | |
614 | return r; | |
615 | ||
616 | FOREACH_DEVICE(e, d) { | |
617 | const char *val; | |
618 | ||
619 | r = sd_device_get_sysattr_value(d, "power_role", &val); | |
620 | if (r < 0) { | |
621 | if (r != -ENOENT) | |
622 | log_device_debug_errno(d, r, "Failed to read 'power_role' sysfs attribute, ignoring: %m"); | |
623 | continue; | |
624 | } | |
625 | ||
626 | if (strstr(val, "[source]")) { | |
627 | found_source = true; | |
628 | log_device_debug(d, "The USB type-C port is in power source mode."); | |
629 | } else if (strstr(val, "[sink]")) { | |
630 | found_sink = true; | |
631 | log_device_debug(d, "The USB type-C port is in power sink mode."); | |
632 | } | |
633 | } | |
634 | ||
635 | if (found_sink) | |
636 | log_device_debug(device, "The USB type-C device has at least one port in power sink mode."); | |
637 | else if (!found_source) | |
638 | log_device_debug(device, "The USB type-C device has no port in power source mode, assuming the device is in power sink mode."); | |
639 | else | |
640 | log_device_debug(device, "All USB type-C ports are in power source mode."); | |
641 | ||
642 | return found_sink || !found_source; | |
643 | } | |
644 | ||
1c03f7f4 YW |
645 | static bool battery_is_discharging(sd_device *d) { |
646 | const char *val; | |
647 | int r; | |
648 | ||
649 | assert(d); | |
650 | ||
651 | r = sd_device_get_sysattr_value(d, "scope", &val); | |
652 | if (r < 0) { | |
653 | if (r != -ENOENT) | |
654 | log_device_debug_errno(d, r, "Failed to read 'scope' sysfs attribute, ignoring: %m"); | |
655 | } else if (streq(val, "Device")) { | |
656 | log_device_debug(d, "The power supply is a device battery, ignoring device."); | |
657 | return false; | |
658 | } | |
659 | ||
660 | r = device_get_sysattr_bool(d, "present"); | |
661 | if (r < 0) | |
662 | log_device_debug_errno(d, r, "Failed to read 'present' sysfs attribute, assuming the battery is present: %m"); | |
663 | else if (r == 0) { | |
664 | log_device_debug(d, "The battery is not present, ignoring the power supply."); | |
665 | return false; | |
666 | } | |
667 | ||
668 | /* Possible values: "Unknown", "Charging", "Discharging", "Not charging", "Full" */ | |
669 | r = sd_device_get_sysattr_value(d, "status", &val); | |
670 | if (r < 0) { | |
671 | log_device_debug_errno(d, r, "Failed to read 'status' sysfs attribute, assuming the battery is discharging: %m"); | |
672 | return true; | |
673 | } | |
674 | if (!streq(val, "Discharging")) { | |
675 | log_device_debug(d, "The battery status is '%s', assuming the battery is not used as a power source of this machine.", val); | |
676 | return false; | |
677 | } | |
678 | ||
679 | return true; | |
680 | } | |
681 | ||
06795b02 | 682 | int on_ac_power(void) { |
01d4ad3b | 683 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; |
1c03f7f4 | 684 | bool found_ac_online = false, found_discharging_battery = false; |
01d4ad3b | 685 | sd_device *d; |
06795b02 YW |
686 | int r; |
687 | ||
01d4ad3b YW |
688 | r = sd_device_enumerator_new(&e); |
689 | if (r < 0) | |
690 | return r; | |
06795b02 | 691 | |
01d4ad3b YW |
692 | r = sd_device_enumerator_allow_uninitialized(e); |
693 | if (r < 0) | |
694 | return r; | |
06795b02 | 695 | |
01d4ad3b YW |
696 | r = sd_device_enumerator_add_match_subsystem(e, "power_supply", true); |
697 | if (r < 0) | |
698 | return r; | |
06795b02 | 699 | |
01d4ad3b | 700 | FOREACH_DEVICE(e, d) { |
4a52514b ZJS |
701 | /* See |
702 | * https://github.com/torvalds/linux/blob/4eef766b7d4d88f0b984781bc1bcb574a6eafdc7/include/linux/power_supply.h#L176 | |
703 | * for defined power source types. Also see: | |
704 | * https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-power */ | |
06795b02 | 705 | |
4a52514b | 706 | const char *val; |
01d4ad3b YW |
707 | r = sd_device_get_sysattr_value(d, "type", &val); |
708 | if (r < 0) { | |
406fbeca | 709 | log_device_debug_errno(d, r, "Failed to read 'type' sysfs attribute, ignoring device: %m"); |
06795b02 | 710 | continue; |
01d4ad3b | 711 | } |
06795b02 | 712 | |
795e86b4 YW |
713 | /* Ignore USB-C power supply in source mode. See issue #21988. */ |
714 | if (streq(val, "USB")) { | |
715 | r = device_is_power_sink(d); | |
716 | if (r <= 0) { | |
717 | if (r < 0) | |
406fbeca | 718 | log_device_debug_errno(d, r, "Failed to determine the current power role, ignoring device: %m"); |
795e86b4 | 719 | else |
406fbeca | 720 | log_device_debug(d, "USB power supply is in source mode, ignoring device."); |
795e86b4 YW |
721 | continue; |
722 | } | |
723 | } | |
724 | ||
8676bdb7 | 725 | if (streq(val, "Battery")) { |
1c03f7f4 YW |
726 | if (battery_is_discharging(d)) { |
727 | found_discharging_battery = true; | |
728 | log_device_debug(d, "The power supply is a battery and currently discharging."); | |
3c69e94a | 729 | } |
06795b02 | 730 | continue; |
01d4ad3b | 731 | } |
06795b02 | 732 | |
4a52514b | 733 | r = device_get_sysattr_unsigned(d, "online", NULL); |
01d4ad3b | 734 | if (r < 0) { |
8676bdb7 | 735 | log_device_debug_errno(d, r, "Failed to query 'online' sysfs attribute, ignoring device: %m"); |
01d4ad3b | 736 | continue; |
4a52514b ZJS |
737 | } else if (r > 0) /* At least 1 and 2 are defined as different types of 'online' */ |
738 | found_ac_online = true; | |
01d4ad3b | 739 | |
4a52514b | 740 | log_device_debug(d, "The power supply is currently %s.", r > 0 ? "online" : "offline"); |
06795b02 YW |
741 | } |
742 | ||
4a52514b ZJS |
743 | if (found_ac_online) { |
744 | log_debug("Found at least one online non-battery power supply, system is running on AC."); | |
745 | return true; | |
1c03f7f4 YW |
746 | } else if (found_discharging_battery) { |
747 | log_debug("Found at least one discharging battery and no online power sources, assuming system is running from battery."); | |
4a52514b ZJS |
748 | return false; |
749 | } else { | |
1c03f7f4 | 750 | log_debug("No power supply reported online and no discharging battery found, assuming system is running on AC."); |
4a52514b ZJS |
751 | return true; |
752 | } | |
06795b02 | 753 | } |
f92c5bb1 YW |
754 | |
755 | bool udev_available(void) { | |
756 | static int cache = -1; | |
757 | ||
758 | /* The service systemd-udevd is started only when /sys is read write. | |
759 | * See systemd-udevd.service: ConditionPathIsReadWrite=/sys | |
760 | * Also, our container interface (http://systemd.io/CONTAINER_INTERFACE/) states that /sys must | |
761 | * be mounted in read-only mode in containers. */ | |
762 | ||
763 | if (cache >= 0) | |
764 | return cache; | |
765 | ||
9fa31df6 | 766 | return (cache = (path_is_read_only_fs("/sys/") <= 0)); |
f92c5bb1 | 767 | } |