Branch data Line data Source code
1 : : /* SPDX-License-Identifier: GPL-2.0+ */
2 : :
3 : : #include <ctype.h>
4 : : #include <errno.h>
5 : : #include <fcntl.h>
6 : : #include <net/if.h>
7 : : #include <stddef.h>
8 : : #include <stdio.h>
9 : : #include <stdlib.h>
10 : : #include <sys/wait.h>
11 : : #include <unistd.h>
12 : :
13 : : #include "sd-event.h"
14 : :
15 : : #include "alloc-util.h"
16 : : #include "device-private.h"
17 : : #include "device-util.h"
18 : : #include "fd-util.h"
19 : : #include "fs-util.h"
20 : : #include "format-util.h"
21 : : #include "libudev-util.h"
22 : : #include "netlink-util.h"
23 : : #include "parse-util.h"
24 : : #include "path-util.h"
25 : : #include "process-util.h"
26 : : #include "rlimit-util.h"
27 : : #include "signal-util.h"
28 : : #include "stdio-util.h"
29 : : #include "string-util.h"
30 : : #include "strv.h"
31 : : #include "strxcpyx.h"
32 : : #include "udev-builtin.h"
33 : : #include "udev-event.h"
34 : : #include "udev-node.h"
35 : : #include "udev-util.h"
36 : : #include "udev-watch.h"
37 : : #include "user-util.h"
38 : :
39 : : typedef struct Spawn {
40 : : sd_device *device;
41 : : const char *cmd;
42 : : pid_t pid;
43 : : usec_t timeout_warn_usec;
44 : : usec_t timeout_usec;
45 : : usec_t event_birth_usec;
46 : : bool accept_failure;
47 : : int fd_stdout;
48 : : int fd_stderr;
49 : : char *result;
50 : : size_t result_size;
51 : : size_t result_len;
52 : : } Spawn;
53 : :
54 : 0 : UdevEvent *udev_event_new(sd_device *dev, usec_t exec_delay_usec, sd_netlink *rtnl) {
55 : : UdevEvent *event;
56 : :
57 [ # # ]: 0 : assert(dev);
58 : :
59 : 0 : event = new(UdevEvent, 1);
60 [ # # ]: 0 : if (!event)
61 : 0 : return NULL;
62 : :
63 : 0 : *event = (UdevEvent) {
64 : 0 : .dev = sd_device_ref(dev),
65 : 0 : .birth_usec = now(CLOCK_MONOTONIC),
66 : : .exec_delay_usec = exec_delay_usec,
67 : 0 : .rtnl = sd_netlink_ref(rtnl),
68 : : .uid = UID_INVALID,
69 : : .gid = GID_INVALID,
70 : : .mode = MODE_INVALID,
71 : : };
72 : :
73 : 0 : return event;
74 : : }
75 : :
76 : 0 : UdevEvent *udev_event_free(UdevEvent *event) {
77 [ # # ]: 0 : if (!event)
78 : 0 : return NULL;
79 : :
80 : 0 : sd_device_unref(event->dev);
81 : 0 : sd_device_unref(event->dev_db_clone);
82 : 0 : sd_netlink_unref(event->rtnl);
83 : 0 : ordered_hashmap_free_free_key(event->run_list);
84 : 0 : ordered_hashmap_free_free_free(event->seclabel_list);
85 : 0 : free(event->program_result);
86 : 0 : free(event->name);
87 : :
88 : 0 : return mfree(event);
89 : : }
90 : :
91 : : typedef enum {
92 : : FORMAT_SUBST_DEVNODE,
93 : : FORMAT_SUBST_ATTR,
94 : : FORMAT_SUBST_ENV,
95 : : FORMAT_SUBST_KERNEL,
96 : : FORMAT_SUBST_KERNEL_NUMBER,
97 : : FORMAT_SUBST_DRIVER,
98 : : FORMAT_SUBST_DEVPATH,
99 : : FORMAT_SUBST_ID,
100 : : FORMAT_SUBST_MAJOR,
101 : : FORMAT_SUBST_MINOR,
102 : : FORMAT_SUBST_RESULT,
103 : : FORMAT_SUBST_PARENT,
104 : : FORMAT_SUBST_NAME,
105 : : FORMAT_SUBST_LINKS,
106 : : FORMAT_SUBST_ROOT,
107 : : FORMAT_SUBST_SYS,
108 : : _FORMAT_SUBST_TYPE_MAX,
109 : : _FORMAT_SUBST_TYPE_INVALID = -1
110 : : } FormatSubstitutionType;
111 : :
112 : : struct subst_map_entry {
113 : : const char *name;
114 : : const char fmt;
115 : : FormatSubstitutionType type;
116 : : };
117 : :
118 : : static const struct subst_map_entry map[] = {
119 : : { .name = "devnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE },
120 : : { .name = "tempnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, /* deprecated */
121 : : { .name = "attr", .fmt = 's', .type = FORMAT_SUBST_ATTR },
122 : : { .name = "sysfs", .fmt = 's', .type = FORMAT_SUBST_ATTR }, /* deprecated */
123 : : { .name = "env", .fmt = 'E', .type = FORMAT_SUBST_ENV },
124 : : { .name = "kernel", .fmt = 'k', .type = FORMAT_SUBST_KERNEL },
125 : : { .name = "number", .fmt = 'n', .type = FORMAT_SUBST_KERNEL_NUMBER },
126 : : { .name = "driver", .fmt = 'd', .type = FORMAT_SUBST_DRIVER },
127 : : { .name = "devpath", .fmt = 'p', .type = FORMAT_SUBST_DEVPATH },
128 : : { .name = "id", .fmt = 'b', .type = FORMAT_SUBST_ID },
129 : : { .name = "major", .fmt = 'M', .type = FORMAT_SUBST_MAJOR },
130 : : { .name = "minor", .fmt = 'm', .type = FORMAT_SUBST_MINOR },
131 : : { .name = "result", .fmt = 'c', .type = FORMAT_SUBST_RESULT },
132 : : { .name = "parent", .fmt = 'P', .type = FORMAT_SUBST_PARENT },
133 : : { .name = "name", .fmt = 'D', .type = FORMAT_SUBST_NAME },
134 : : { .name = "links", .fmt = 'L', .type = FORMAT_SUBST_LINKS },
135 : : { .name = "root", .fmt = 'r', .type = FORMAT_SUBST_ROOT },
136 : : { .name = "sys", .fmt = 'S', .type = FORMAT_SUBST_SYS },
137 : : };
138 : :
139 : 0 : static const char *format_type_to_string(FormatSubstitutionType t) {
140 [ # # ]: 0 : for (size_t i = 0; i < ELEMENTSOF(map); i++)
141 [ # # ]: 0 : if (map[i].type == t)
142 : 0 : return map[i].name;
143 : 0 : return NULL;
144 : : }
145 : :
146 : 0 : static char format_type_to_char(FormatSubstitutionType t) {
147 [ # # ]: 0 : for (size_t i = 0; i < ELEMENTSOF(map); i++)
148 [ # # ]: 0 : if (map[i].type == t)
149 : 0 : return map[i].fmt;
150 : 0 : return '\0';
151 : : }
152 : :
153 : 0 : static int get_subst_type(const char **str, bool strict, FormatSubstitutionType *ret_type, char ret_attr[static UTIL_PATH_SIZE]) {
154 : 0 : const char *p = *str, *q = NULL;
155 : : size_t i;
156 : :
157 [ # # ]: 0 : assert(str);
158 [ # # ]: 0 : assert(*str);
159 [ # # ]: 0 : assert(ret_type);
160 [ # # ]: 0 : assert(ret_attr);
161 : :
162 [ # # ]: 0 : if (*p == '$') {
163 : 0 : p++;
164 [ # # ]: 0 : if (*p == '$') {
165 : 0 : *str = p;
166 : 0 : return 0;
167 : : }
168 [ # # ]: 0 : for (i = 0; i < ELEMENTSOF(map); i++)
169 [ # # ]: 0 : if ((q = startswith(p, map[i].name)))
170 : 0 : break;
171 [ # # ]: 0 : } else if (*p == '%') {
172 : 0 : p++;
173 [ # # ]: 0 : if (*p == '%') {
174 : 0 : *str = p;
175 : 0 : return 0;
176 : : }
177 : :
178 [ # # ]: 0 : for (i = 0; i < ELEMENTSOF(map); i++)
179 [ # # ]: 0 : if (*p == map[i].fmt) {
180 : 0 : q = p + 1;
181 : 0 : break;
182 : : }
183 : : } else
184 : 0 : return 0;
185 [ # # ]: 0 : if (!q)
186 : : /* When 'strict' flag is set, then '$' and '%' must be escaped. */
187 [ # # ]: 0 : return strict ? -EINVAL : 0;
188 : :
189 [ # # ]: 0 : if (*q == '{') {
190 : : const char *start, *end;
191 : : size_t len;
192 : :
193 : 0 : start = q + 1;
194 : 0 : end = strchr(start, '}');
195 [ # # ]: 0 : if (!end)
196 : 0 : return -EINVAL;
197 : :
198 : 0 : len = end - start;
199 [ # # # # ]: 0 : if (len == 0 || len >= UTIL_PATH_SIZE)
200 : 0 : return -EINVAL;
201 : :
202 : 0 : strnscpy(ret_attr, UTIL_PATH_SIZE, start, len);
203 : 0 : q = end + 1;
204 : : } else
205 : 0 : *ret_attr = '\0';
206 : :
207 : 0 : *str = q;
208 : 0 : *ret_type = map[i].type;
209 : 0 : return 1;
210 : : }
211 : :
212 : 0 : static int safe_atou_optional_plus(const char *s, unsigned *ret) {
213 : : const char *p;
214 : : int r;
215 : :
216 [ # # ]: 0 : assert(s);
217 [ # # ]: 0 : assert(ret);
218 : :
219 : : /* Returns 1 if plus, 0 if no plus, negative on error */
220 : :
221 : 0 : p = endswith(s, "+");
222 [ # # ]: 0 : if (p)
223 : 0 : s = strndupa(s, p - s);
224 : :
225 : 0 : r = safe_atou(s, ret);
226 [ # # ]: 0 : if (r < 0)
227 : 0 : return r;
228 : :
229 : 0 : return !!p;
230 : : }
231 : :
232 : 0 : static ssize_t udev_event_subst_format(
233 : : UdevEvent *event,
234 : : FormatSubstitutionType type,
235 : : const char *attr,
236 : : char *dest,
237 : : size_t l) {
238 : 0 : sd_device *parent, *dev = event->dev;
239 : 0 : const char *val = NULL;
240 : 0 : char *s = dest;
241 : : int r;
242 : :
243 [ # # # # : 0 : switch (type) {
# # # # #
# # # # #
# # ]
244 : 0 : case FORMAT_SUBST_DEVPATH:
245 : 0 : r = sd_device_get_devpath(dev, &val);
246 [ # # ]: 0 : if (r < 0)
247 : 0 : return r;
248 : 0 : l = strpcpy(&s, l, val);
249 : 0 : break;
250 : 0 : case FORMAT_SUBST_KERNEL:
251 : 0 : r = sd_device_get_sysname(dev, &val);
252 [ # # ]: 0 : if (r < 0)
253 : 0 : return r;
254 : 0 : l = strpcpy(&s, l, val);
255 : 0 : break;
256 : 0 : case FORMAT_SUBST_KERNEL_NUMBER:
257 : 0 : r = sd_device_get_sysnum(dev, &val);
258 [ # # ]: 0 : if (r == -ENOENT)
259 : 0 : goto null_terminate;
260 [ # # ]: 0 : if (r < 0)
261 : 0 : return r;
262 : 0 : l = strpcpy(&s, l, val);
263 : 0 : break;
264 : 0 : case FORMAT_SUBST_ID:
265 [ # # ]: 0 : if (!event->dev_parent)
266 : 0 : goto null_terminate;
267 : 0 : r = sd_device_get_sysname(event->dev_parent, &val);
268 [ # # ]: 0 : if (r < 0)
269 : 0 : return r;
270 : 0 : l = strpcpy(&s, l, val);
271 : 0 : break;
272 : 0 : case FORMAT_SUBST_DRIVER:
273 [ # # ]: 0 : if (!event->dev_parent)
274 : 0 : goto null_terminate;
275 : 0 : r = sd_device_get_driver(event->dev_parent, &val);
276 [ # # ]: 0 : if (r == -ENOENT)
277 : 0 : goto null_terminate;
278 [ # # ]: 0 : if (r < 0)
279 : 0 : return r;
280 : 0 : l = strpcpy(&s, l, val);
281 : 0 : break;
282 : 0 : case FORMAT_SUBST_MAJOR:
283 : : case FORMAT_SUBST_MINOR: {
284 : : dev_t devnum;
285 : :
286 : 0 : r = sd_device_get_devnum(dev, &devnum);
287 [ # # # # ]: 0 : if (r < 0 && r != -ENOENT)
288 : 0 : return r;
289 [ # # # # ]: 0 : l = strpcpyf(&s, l, "%u", r < 0 ? 0 : type == FORMAT_SUBST_MAJOR ? major(devnum) : minor(devnum));
290 : 0 : break;
291 : : }
292 : 0 : case FORMAT_SUBST_RESULT: {
293 : 0 : unsigned index = 0; /* 0 means whole string */
294 : : bool has_plus;
295 : :
296 [ # # ]: 0 : if (!event->program_result)
297 : 0 : goto null_terminate;
298 : :
299 [ # # ]: 0 : if (!isempty(attr)) {
300 : 0 : r = safe_atou_optional_plus(attr, &index);
301 [ # # ]: 0 : if (r < 0)
302 : 0 : return r;
303 : :
304 : 0 : has_plus = r;
305 : : }
306 : :
307 [ # # ]: 0 : if (index == 0)
308 : 0 : l = strpcpy(&s, l, event->program_result);
309 : : else {
310 : : const char *start, *p;
311 : : unsigned i;
312 : :
313 : 0 : p = skip_leading_chars(event->program_result, NULL);
314 : :
315 [ # # ]: 0 : for (i = 1; i < index; i++) {
316 [ # # # # ]: 0 : while (*p && !strchr(WHITESPACE, *p))
317 : 0 : p++;
318 : 0 : p = skip_leading_chars(p, NULL);
319 [ # # ]: 0 : if (*p == '\0')
320 : 0 : break;
321 : : }
322 [ # # ]: 0 : if (i != index) {
323 [ # # # # : 0 : log_device_debug(dev, "requested part of result string not found");
# # ]
324 : 0 : goto null_terminate;
325 : : }
326 : :
327 : 0 : start = p;
328 : : /* %c{2+} copies the whole string from the second part on */
329 [ # # ]: 0 : if (has_plus)
330 : 0 : l = strpcpy(&s, l, start);
331 : : else {
332 [ # # # # ]: 0 : while (*p && !strchr(WHITESPACE, *p))
333 : 0 : p++;
334 : 0 : l = strnpcpy(&s, l, start, p - start);
335 : : }
336 : : }
337 : 0 : break;
338 : : }
339 : 0 : case FORMAT_SUBST_ATTR: {
340 : : char vbuf[UTIL_NAME_SIZE];
341 : : int count;
342 : :
343 [ # # ]: 0 : if (isempty(attr))
344 : 0 : return -EINVAL;
345 : :
346 : : /* try to read the value specified by "[dmi/id]product_name" */
347 [ # # ]: 0 : if (util_resolve_subsys_kernel(attr, vbuf, sizeof(vbuf), true) == 0)
348 : 0 : val = vbuf;
349 : :
350 : : /* try to read the attribute the device */
351 [ # # ]: 0 : if (!val)
352 : 0 : (void) sd_device_get_sysattr_value(dev, attr, &val);
353 : :
354 : : /* try to read the attribute of the parent device, other matches have selected */
355 [ # # # # : 0 : if (!val && event->dev_parent && event->dev_parent != dev)
# # ]
356 : 0 : (void) sd_device_get_sysattr_value(event->dev_parent, attr, &val);
357 : :
358 [ # # ]: 0 : if (!val)
359 : 0 : goto null_terminate;
360 : :
361 : : /* strip trailing whitespace, and replace unwanted characters */
362 [ # # ]: 0 : if (val != vbuf)
363 : 0 : strscpy(vbuf, sizeof(vbuf), val);
364 : 0 : delete_trailing_chars(vbuf, NULL);
365 : 0 : count = util_replace_chars(vbuf, UDEV_ALLOWED_CHARS_INPUT);
366 [ # # ]: 0 : if (count > 0)
367 [ # # # # : 0 : log_device_debug(dev, "%i character(s) replaced", count);
# # ]
368 : 0 : l = strpcpy(&s, l, vbuf);
369 : 0 : break;
370 : : }
371 : 0 : case FORMAT_SUBST_PARENT:
372 : 0 : r = sd_device_get_parent(dev, &parent);
373 [ # # ]: 0 : if (r == -ENODEV)
374 : 0 : goto null_terminate;
375 [ # # ]: 0 : if (r < 0)
376 : 0 : return r;
377 : 0 : r = sd_device_get_devname(parent, &val);
378 [ # # ]: 0 : if (r == -ENOENT)
379 : 0 : goto null_terminate;
380 [ # # ]: 0 : if (r < 0)
381 : 0 : return r;
382 : 0 : l = strpcpy(&s, l, val + STRLEN("/dev/"));
383 : 0 : break;
384 : 0 : case FORMAT_SUBST_DEVNODE:
385 : 0 : r = sd_device_get_devname(dev, &val);
386 [ # # ]: 0 : if (r == -ENOENT)
387 : 0 : goto null_terminate;
388 [ # # ]: 0 : if (r < 0)
389 : 0 : return r;
390 : 0 : l = strpcpy(&s, l, val);
391 : 0 : break;
392 : 0 : case FORMAT_SUBST_NAME:
393 [ # # ]: 0 : if (event->name)
394 : 0 : l = strpcpy(&s, l, event->name);
395 [ # # ]: 0 : else if (sd_device_get_devname(dev, &val) >= 0)
396 : 0 : l = strpcpy(&s, l, val + STRLEN("/dev/"));
397 : : else {
398 : 0 : r = sd_device_get_sysname(dev, &val);
399 [ # # ]: 0 : if (r < 0)
400 : 0 : return r;
401 : 0 : l = strpcpy(&s, l, val);
402 : : }
403 : 0 : break;
404 : 0 : case FORMAT_SUBST_LINKS:
405 [ # # ]: 0 : FOREACH_DEVICE_DEVLINK(dev, val)
406 [ # # ]: 0 : if (s == dest)
407 : 0 : l = strpcpy(&s, l, val + STRLEN("/dev/"));
408 : : else
409 : 0 : l = strpcpyl(&s, l, " ", val + STRLEN("/dev/"), NULL);
410 [ # # ]: 0 : if (s == dest)
411 : 0 : goto null_terminate;
412 : 0 : break;
413 : 0 : case FORMAT_SUBST_ROOT:
414 : 0 : l = strpcpy(&s, l, "/dev");
415 : 0 : break;
416 : 0 : case FORMAT_SUBST_SYS:
417 : 0 : l = strpcpy(&s, l, "/sys");
418 : 0 : break;
419 : 0 : case FORMAT_SUBST_ENV:
420 [ # # ]: 0 : if (isempty(attr))
421 : 0 : return -EINVAL;
422 : 0 : r = sd_device_get_property_value(dev, attr, &val);
423 [ # # ]: 0 : if (r == -ENOENT)
424 : 0 : goto null_terminate;
425 [ # # ]: 0 : if (r < 0)
426 : 0 : return r;
427 : 0 : l = strpcpy(&s, l, val);
428 : 0 : break;
429 : 0 : default:
430 : 0 : assert_not_reached("Unknown format substitution type");
431 : : }
432 : :
433 : 0 : return s - dest;
434 : :
435 : 0 : null_terminate:
436 : 0 : *s = '\0';
437 : 0 : return 0;
438 : : }
439 : :
440 : 0 : ssize_t udev_event_apply_format(UdevEvent *event,
441 : : const char *src, char *dest, size_t size,
442 : : bool replace_whitespace) {
443 : 0 : const char *s = src;
444 : : int r;
445 : :
446 [ # # ]: 0 : assert(event);
447 [ # # ]: 0 : assert(event->dev);
448 [ # # ]: 0 : assert(src);
449 [ # # ]: 0 : assert(dest);
450 [ # # ]: 0 : assert(size > 0);
451 : :
452 [ # # ]: 0 : while (*s) {
453 : : FormatSubstitutionType type;
454 : : char attr[UTIL_PATH_SIZE];
455 : : ssize_t subst_len;
456 : :
457 : 0 : r = get_subst_type(&s, false, &type, attr);
458 [ # # ]: 0 : if (r < 0)
459 [ # # # # : 0 : return log_device_warning_errno(event->dev, r, "Invalid format string, ignoring: %s", src);
# # ]
460 [ # # ]: 0 : if (r == 0) {
461 [ # # ]: 0 : if (size < 2) /* need space for this char and the terminating NUL */
462 : 0 : break;
463 : 0 : *dest++ = *s++;
464 : 0 : size--;
465 : 0 : continue;
466 : : }
467 : :
468 : 0 : subst_len = udev_event_subst_format(event, type, attr, dest, size);
469 [ # # ]: 0 : if (subst_len < 0)
470 [ # # # # : 0 : return log_device_warning_errno(event->dev, subst_len,
# # ]
471 : : "Failed to substitute variable '$%s' or apply format '%%%c', ignoring: %m",
472 : : format_type_to_string(type), format_type_to_char(type));
473 : :
474 : : /* FORMAT_SUBST_RESULT handles spaces itself */
475 [ # # # # ]: 0 : if (replace_whitespace && type != FORMAT_SUBST_RESULT)
476 : : /* util_replace_whitespace can replace in-place,
477 : : * and does nothing if subst_len == 0 */
478 : 0 : subst_len = util_replace_whitespace(dest, dest, subst_len);
479 : :
480 : 0 : dest += subst_len;
481 : 0 : size -= subst_len;
482 : : }
483 : :
484 [ # # ]: 0 : assert(size >= 1);
485 : 0 : *dest = '\0';
486 : 0 : return size;
487 : : }
488 : :
489 : 0 : int udev_check_format(const char *value, size_t *offset, const char **hint) {
490 : : FormatSubstitutionType type;
491 : 0 : const char *s = value;
492 : : char attr[UTIL_PATH_SIZE];
493 : : int r;
494 : :
495 [ # # ]: 0 : while (*s) {
496 : 0 : r = get_subst_type(&s, true, &type, attr);
497 [ # # ]: 0 : if (r < 0) {
498 [ # # ]: 0 : if (offset)
499 : 0 : *offset = s - value;
500 [ # # ]: 0 : if (hint)
501 : 0 : *hint = "invalid substitution type";
502 : 0 : return r;
503 [ # # ]: 0 : } else if (r == 0) {
504 : 0 : s++;
505 : 0 : continue;
506 : : }
507 : :
508 [ # # # # : 0 : if (IN_SET(type, FORMAT_SUBST_ATTR, FORMAT_SUBST_ENV) && isempty(attr)) {
# # ]
509 [ # # ]: 0 : if (offset)
510 : 0 : *offset = s - value;
511 [ # # ]: 0 : if (hint)
512 : 0 : *hint = "attribute value missing";
513 : 0 : return -EINVAL;
514 : : }
515 : :
516 [ # # # # ]: 0 : if (type == FORMAT_SUBST_RESULT && !isempty(attr)) {
517 : : unsigned i;
518 : :
519 : 0 : r = safe_atou_optional_plus(attr, &i);
520 [ # # ]: 0 : if (r < 0) {
521 [ # # ]: 0 : if (offset)
522 : 0 : *offset = s - value;
523 [ # # ]: 0 : if (hint)
524 : 0 : *hint = "attribute value not a valid number";
525 : 0 : return r;
526 : : }
527 : : }
528 : : }
529 : :
530 : 0 : return 0;
531 : : }
532 : :
533 : 0 : static int on_spawn_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
534 : 0 : Spawn *spawn = userdata;
535 : : char buf[4096], *p;
536 : : size_t size;
537 : : ssize_t l;
538 : :
539 [ # # ]: 0 : assert(spawn);
540 [ # # # # ]: 0 : assert(fd == spawn->fd_stdout || fd == spawn->fd_stderr);
541 [ # # # # ]: 0 : assert(!spawn->result || spawn->result_len < spawn->result_size);
542 : :
543 [ # # # # ]: 0 : if (fd == spawn->fd_stdout && spawn->result) {
544 : 0 : p = spawn->result + spawn->result_len;
545 : 0 : size = spawn->result_size - spawn->result_len;
546 : : } else {
547 : 0 : p = buf;
548 : 0 : size = sizeof(buf);
549 : : }
550 : :
551 : 0 : l = read(fd, p, size - 1);
552 [ # # ]: 0 : if (l < 0) {
553 [ # # ]: 0 : if (errno != EAGAIN)
554 [ # # # # : 0 : log_device_error_errno(spawn->device, errno,
# # ]
555 : : "Failed to read stdout of '%s': %m", spawn->cmd);
556 : :
557 : 0 : return 0;
558 : : }
559 : :
560 : 0 : p[l] = '\0';
561 [ # # # # ]: 0 : if (fd == spawn->fd_stdout && spawn->result)
562 : 0 : spawn->result_len += l;
563 : :
564 : : /* Log output only if we watch stderr. */
565 [ # # # # ]: 0 : if (l > 0 && spawn->fd_stderr >= 0) {
566 [ # # ]: 0 : _cleanup_strv_free_ char **v = NULL;
567 : : char **q;
568 : :
569 : 0 : v = strv_split_newlines(p);
570 [ # # ]: 0 : if (!v)
571 : 0 : return 0;
572 : :
573 [ # # # # ]: 0 : STRV_FOREACH(q, v)
574 [ # # # # : 0 : log_device_debug(spawn->device, "'%s'(%s) '%s'", spawn->cmd,
# # # # ]
575 : : fd == spawn->fd_stdout ? "out" : "err", *q);
576 : : }
577 : :
578 : 0 : return 0;
579 : : }
580 : :
581 : 0 : static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
582 : 0 : Spawn *spawn = userdata;
583 : : char timeout[FORMAT_TIMESPAN_MAX];
584 : :
585 [ # # ]: 0 : assert(spawn);
586 : :
587 : 0 : kill_and_sigcont(spawn->pid, SIGKILL);
588 : :
589 [ # # # # : 0 : log_device_error(spawn->device, "Spawned process '%s' ["PID_FMT"] timed out after %s, killing",
# # ]
590 : : spawn->cmd, spawn->pid,
591 : : format_timespan(timeout, sizeof(timeout), spawn->timeout_usec, USEC_PER_SEC));
592 : :
593 : 0 : return 1;
594 : : }
595 : :
596 : 0 : static int on_spawn_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) {
597 : 0 : Spawn *spawn = userdata;
598 : : char timeout[FORMAT_TIMESPAN_MAX];
599 : :
600 [ # # ]: 0 : assert(spawn);
601 : :
602 [ # # # # : 0 : log_device_warning(spawn->device, "Spawned process '%s' ["PID_FMT"] is taking longer than %s to complete",
# # ]
603 : : spawn->cmd, spawn->pid,
604 : : format_timespan(timeout, sizeof(timeout), spawn->timeout_warn_usec, USEC_PER_SEC));
605 : :
606 : 0 : return 1;
607 : : }
608 : :
609 : 0 : static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) {
610 : 0 : Spawn *spawn = userdata;
611 : 0 : int ret = -EIO;
612 : :
613 [ # # ]: 0 : assert(spawn);
614 : :
615 [ # # # ]: 0 : switch (si->si_code) {
616 : 0 : case CLD_EXITED:
617 [ # # ]: 0 : if (si->si_status == 0)
618 [ # # # # : 0 : log_device_debug(spawn->device, "Process '%s' succeeded.", spawn->cmd);
# # ]
619 : : else
620 [ # # # # : 0 : log_device_full(spawn->device, spawn->accept_failure ? LOG_DEBUG : LOG_WARNING, 0,
# # # # ]
621 : : "Process '%s' failed with exit code %i.", spawn->cmd, si->si_status);
622 : 0 : ret = si->si_status;
623 : 0 : break;
624 : 0 : case CLD_KILLED:
625 : : case CLD_DUMPED:
626 [ # # # # : 0 : log_device_error(spawn->device, "Process '%s' terminated by signal %s.", spawn->cmd, signal_to_string(si->si_status));
# # ]
627 : 0 : break;
628 : 0 : default:
629 [ # # # # : 0 : log_device_error(spawn->device, "Process '%s' failed due to unknown reason.", spawn->cmd);
# # ]
630 : : }
631 : :
632 : 0 : sd_event_exit(sd_event_source_get_event(s), ret);
633 : 0 : return 1;
634 : : }
635 : :
636 : 0 : static int spawn_wait(Spawn *spawn) {
637 : 0 : _cleanup_(sd_event_unrefp) sd_event *e = NULL;
638 : : int r;
639 : :
640 [ # # ]: 0 : assert(spawn);
641 : :
642 : 0 : r = sd_event_new(&e);
643 [ # # ]: 0 : if (r < 0)
644 : 0 : return r;
645 : :
646 [ # # ]: 0 : if (spawn->timeout_usec > 0) {
647 : : usec_t usec, age_usec;
648 : :
649 : 0 : usec = now(CLOCK_MONOTONIC);
650 : 0 : age_usec = usec - spawn->event_birth_usec;
651 [ # # ]: 0 : if (age_usec < spawn->timeout_usec) {
652 [ # # ]: 0 : if (spawn->timeout_warn_usec > 0 &&
653 [ # # ]: 0 : spawn->timeout_warn_usec < spawn->timeout_usec &&
654 [ # # ]: 0 : spawn->timeout_warn_usec > age_usec) {
655 : 0 : spawn->timeout_warn_usec -= age_usec;
656 : :
657 : 0 : r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC,
658 : 0 : usec + spawn->timeout_warn_usec, USEC_PER_SEC,
659 : : on_spawn_timeout_warning, spawn);
660 [ # # ]: 0 : if (r < 0)
661 : 0 : return r;
662 : : }
663 : :
664 : 0 : spawn->timeout_usec -= age_usec;
665 : :
666 : 0 : r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC,
667 : 0 : usec + spawn->timeout_usec, USEC_PER_SEC, on_spawn_timeout, spawn);
668 [ # # ]: 0 : if (r < 0)
669 : 0 : return r;
670 : : }
671 : : }
672 : :
673 [ # # ]: 0 : if (spawn->fd_stdout >= 0) {
674 : 0 : r = sd_event_add_io(e, NULL, spawn->fd_stdout, EPOLLIN, on_spawn_io, spawn);
675 [ # # ]: 0 : if (r < 0)
676 : 0 : return r;
677 : : }
678 : :
679 [ # # ]: 0 : if (spawn->fd_stderr >= 0) {
680 : 0 : r = sd_event_add_io(e, NULL, spawn->fd_stderr, EPOLLIN, on_spawn_io, spawn);
681 [ # # ]: 0 : if (r < 0)
682 : 0 : return r;
683 : : }
684 : :
685 : 0 : r = sd_event_add_child(e, NULL, spawn->pid, WEXITED, on_spawn_sigchld, spawn);
686 [ # # ]: 0 : if (r < 0)
687 : 0 : return r;
688 : :
689 : 0 : return sd_event_loop(e);
690 : : }
691 : :
692 : 0 : int udev_event_spawn(UdevEvent *event,
693 : : usec_t timeout_usec,
694 : : bool accept_failure,
695 : : const char *cmd,
696 : : char *result, size_t ressize) {
697 : 0 : _cleanup_close_pair_ int outpipe[2] = {-1, -1}, errpipe[2] = {-1, -1};
698 : 0 : _cleanup_strv_free_ char **argv = NULL;
699 : 0 : char **envp = NULL;
700 : : Spawn spawn;
701 : : pid_t pid;
702 : : int r;
703 : :
704 [ # # ]: 0 : assert(event);
705 [ # # ]: 0 : assert(event->dev);
706 [ # # # # ]: 0 : assert(result || ressize == 0);
707 : :
708 : : /* pipes from child to parent */
709 [ # # # # ]: 0 : if (result || log_get_max_level() >= LOG_INFO)
710 [ # # ]: 0 : if (pipe2(outpipe, O_NONBLOCK|O_CLOEXEC) != 0)
711 [ # # # # : 0 : return log_device_error_errno(event->dev, errno,
# # ]
712 : : "Failed to create pipe for command '%s': %m", cmd);
713 : :
714 [ # # ]: 0 : if (log_get_max_level() >= LOG_INFO)
715 [ # # ]: 0 : if (pipe2(errpipe, O_NONBLOCK|O_CLOEXEC) != 0)
716 [ # # # # : 0 : return log_device_error_errno(event->dev, errno,
# # ]
717 : : "Failed to create pipe for command '%s': %m", cmd);
718 : :
719 : 0 : argv = strv_split_full(cmd, NULL, SPLIT_QUOTES|SPLIT_RELAX);
720 [ # # ]: 0 : if (!argv)
721 : 0 : return log_oom();
722 : :
723 [ # # ]: 0 : if (isempty(argv[0]))
724 [ # # # # : 0 : return log_device_error_errno(event->dev, SYNTHETIC_ERRNO(EINVAL),
# # ]
725 : : "Invalid command '%s'", cmd);
726 : :
727 : : /* allow programs in /usr/lib/udev/ to be called without the path */
728 [ # # ]: 0 : if (!path_is_absolute(argv[0])) {
729 : : char *program;
730 : :
731 : 0 : program = path_join(UDEVLIBEXECDIR, argv[0]);
732 [ # # ]: 0 : if (!program)
733 : 0 : return log_oom();
734 : :
735 : 0 : free_and_replace(argv[0], program);
736 : : }
737 : :
738 : 0 : r = device_get_properties_strv(event->dev, &envp);
739 [ # # ]: 0 : if (r < 0)
740 [ # # # # : 0 : return log_device_error_errno(event->dev, r, "Failed to get device properties");
# # ]
741 : :
742 [ # # # # : 0 : log_device_debug(event->dev, "Starting '%s'", cmd);
# # ]
743 : :
744 : 0 : r = safe_fork("(spawn)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
745 [ # # ]: 0 : if (r < 0)
746 [ # # # # : 0 : return log_device_error_errno(event->dev, r,
# # ]
747 : : "Failed to fork() to execute command '%s': %m", cmd);
748 [ # # ]: 0 : if (r == 0) {
749 [ # # ]: 0 : if (rearrange_stdio(-1, outpipe[WRITE_END], errpipe[WRITE_END]) < 0)
750 : 0 : _exit(EXIT_FAILURE);
751 : :
752 : 0 : (void) close_all_fds(NULL, 0);
753 : 0 : (void) rlimit_nofile_safe();
754 : :
755 : 0 : execve(argv[0], argv, envp);
756 : 0 : _exit(EXIT_FAILURE);
757 : : }
758 : :
759 : : /* parent closed child's ends of pipes */
760 : 0 : outpipe[WRITE_END] = safe_close(outpipe[WRITE_END]);
761 : 0 : errpipe[WRITE_END] = safe_close(errpipe[WRITE_END]);
762 : :
763 : 0 : spawn = (Spawn) {
764 : 0 : .device = event->dev,
765 : : .cmd = cmd,
766 : : .pid = pid,
767 : : .accept_failure = accept_failure,
768 : 0 : .timeout_warn_usec = udev_warn_timeout(timeout_usec),
769 : : .timeout_usec = timeout_usec,
770 : 0 : .event_birth_usec = event->birth_usec,
771 : 0 : .fd_stdout = outpipe[READ_END],
772 : 0 : .fd_stderr = errpipe[READ_END],
773 : : .result = result,
774 : : .result_size = ressize,
775 : : };
776 : 0 : r = spawn_wait(&spawn);
777 [ # # ]: 0 : if (r < 0)
778 [ # # # # : 0 : return log_device_error_errno(event->dev, r,
# # ]
779 : : "Failed to wait for spawned command '%s': %m", cmd);
780 : :
781 [ # # ]: 0 : if (result)
782 : 0 : result[spawn.result_len] = '\0';
783 : :
784 : 0 : return r; /* 0 for success, and positive if the program failed */
785 : : }
786 : :
787 : 0 : static int rename_netif(UdevEvent *event) {
788 : 0 : sd_device *dev = event->dev;
789 : : const char *oldname;
790 : : int ifindex, r;
791 : :
792 [ # # ]: 0 : if (!event->name)
793 : 0 : return 0; /* No new name is requested. */
794 : :
795 : 0 : r = sd_device_get_sysname(dev, &oldname);
796 [ # # ]: 0 : if (r < 0)
797 [ # # # # : 0 : return log_device_error_errno(dev, r, "Failed to get sysname: %m");
# # ]
798 : :
799 [ # # ]: 0 : if (streq(event->name, oldname))
800 : 0 : return 0; /* The interface name is already requested name. */
801 : :
802 [ # # ]: 0 : if (!device_for_action(dev, DEVICE_ACTION_ADD))
803 : 0 : return 0; /* Rename the interface only when it is added. */
804 : :
805 : 0 : r = sd_device_get_ifindex(dev, &ifindex);
806 [ # # ]: 0 : if (r == -ENOENT)
807 : 0 : return 0; /* Device is not a network interface. */
808 [ # # ]: 0 : if (r < 0)
809 [ # # # # : 0 : return log_device_error_errno(dev, r, "Failed to get ifindex: %m");
# # ]
810 : :
811 : 0 : r = rtnl_set_link_name(&event->rtnl, ifindex, event->name);
812 [ # # ]: 0 : if (r < 0)
813 [ # # # # : 0 : return log_device_error_errno(dev, r, "Failed to rename network interface %i from '%s' to '%s': %m",
# # ]
814 : : ifindex, oldname, event->name);
815 : :
816 : : /* Set ID_RENAMING boolean property here, and drop it in the corresponding move uevent later. */
817 : 0 : r = device_add_property(dev, "ID_RENAMING", "1");
818 [ # # ]: 0 : if (r < 0)
819 [ # # # # : 0 : return log_device_warning_errno(dev, r, "Failed to add 'ID_RENAMING' property: %m");
# # ]
820 : :
821 : 0 : r = device_rename(dev, event->name);
822 [ # # ]: 0 : if (r < 0)
823 [ # # # # : 0 : return log_device_warning_errno(dev, r, "Failed to update properties with new name '%s': %m", event->name);
# # ]
824 : :
825 [ # # # # : 0 : log_device_debug(dev, "Network interface %i is renamed from '%s' to '%s'", ifindex, oldname, event->name);
# # ]
826 : :
827 : 0 : return 1;
828 : : }
829 : :
830 : 0 : static int update_devnode(UdevEvent *event) {
831 : 0 : sd_device *dev = event->dev;
832 : : int r;
833 : :
834 : 0 : r = sd_device_get_devnum(dev, NULL);
835 [ # # ]: 0 : if (r == -ENOENT)
836 : 0 : return 0;
837 [ # # ]: 0 : if (r < 0)
838 [ # # # # : 0 : return log_device_error_errno(dev, r, "Failed to get devnum: %m");
# # ]
839 : :
840 : : /* remove/update possible left-over symlinks from old database entry */
841 [ # # ]: 0 : if (event->dev_db_clone)
842 : 0 : (void) udev_node_update_old_links(dev, event->dev_db_clone);
843 : :
844 [ # # ]: 0 : if (!uid_is_valid(event->uid)) {
845 : 0 : r = device_get_devnode_uid(dev, &event->uid);
846 [ # # # # ]: 0 : if (r < 0 && r != -ENOENT)
847 [ # # # # : 0 : return log_device_error_errno(dev, r, "Failed to get devnode UID: %m");
# # ]
848 : : }
849 : :
850 [ # # ]: 0 : if (!gid_is_valid(event->gid)) {
851 : 0 : r = device_get_devnode_gid(dev, &event->gid);
852 [ # # # # ]: 0 : if (r < 0 && r != -ENOENT)
853 [ # # # # : 0 : return log_device_error_errno(dev, r, "Failed to get devnode GID: %m");
# # ]
854 : : }
855 : :
856 [ # # ]: 0 : if (event->mode == MODE_INVALID) {
857 : 0 : r = device_get_devnode_mode(dev, &event->mode);
858 [ # # # # ]: 0 : if (r < 0 && r != -ENOENT)
859 [ # # # # : 0 : return log_device_error_errno(dev, r, "Failed to get devnode mode: %m");
# # ]
860 : : }
861 [ # # # # : 0 : if (event->mode == MODE_INVALID && gid_is_valid(event->gid) && event->gid > 0)
# # ]
862 : : /* If group is set, but mode is not set, "upgrade" mode for the group. */
863 : 0 : event->mode = 0660;
864 : :
865 : 0 : bool apply_mac = device_for_action(dev, DEVICE_ACTION_ADD);
866 : :
867 : 0 : return udev_node_add(dev, apply_mac, event->mode, event->uid, event->gid, event->seclabel_list);
868 : : }
869 : :
870 : 0 : static void event_execute_rules_on_remove(
871 : : UdevEvent *event,
872 : : usec_t timeout_usec,
873 : : Hashmap *properties_list,
874 : : UdevRules *rules) {
875 : :
876 : 0 : sd_device *dev = event->dev;
877 : : int r;
878 : :
879 : 0 : r = device_read_db_internal(dev, true);
880 [ # # ]: 0 : if (r < 0)
881 [ # # # # : 0 : log_device_debug_errno(dev, r, "Failed to read database under /run/udev/data/: %m");
# # ]
882 : :
883 : 0 : r = device_tag_index(dev, NULL, false);
884 [ # # ]: 0 : if (r < 0)
885 [ # # # # : 0 : log_device_debug_errno(dev, r, "Failed to remove corresponding tag files under /run/udev/tag/, ignoring: %m");
# # ]
886 : :
887 : 0 : r = device_delete_db(dev);
888 [ # # ]: 0 : if (r < 0)
889 [ # # # # : 0 : log_device_debug_errno(dev, r, "Failed to delete database under /run/udev/data/, ignoring: %m");
# # ]
890 : :
891 [ # # ]: 0 : if (sd_device_get_devnum(dev, NULL) >= 0)
892 : 0 : (void) udev_watch_end(dev);
893 : :
894 : 0 : (void) udev_rules_apply_to_event(rules, event, timeout_usec, properties_list);
895 : :
896 [ # # ]: 0 : if (sd_device_get_devnum(dev, NULL) >= 0)
897 : 0 : (void) udev_node_remove(dev);
898 : 0 : }
899 : :
900 : 0 : static int udev_event_on_move(UdevEvent *event) {
901 : 0 : sd_device *dev = event->dev;
902 : : int r;
903 : :
904 [ # # # # ]: 0 : if (event->dev_db_clone &&
905 : 0 : sd_device_get_devnum(dev, NULL) < 0) {
906 : 0 : r = device_copy_properties(dev, event->dev_db_clone);
907 [ # # ]: 0 : if (r < 0)
908 [ # # # # : 0 : log_device_debug_errno(dev, r, "Failed to copy properties from cloned sd_device object, ignoring: %m");
# # ]
909 : : }
910 : :
911 : : /* Drop previously added property */
912 : 0 : r = device_add_property(dev, "ID_RENAMING", NULL);
913 [ # # ]: 0 : if (r < 0)
914 [ # # # # : 0 : return log_device_debug_errno(dev, r, "Failed to remove 'ID_RENAMING' property: %m");
# # ]
915 : :
916 : 0 : return 0;
917 : : }
918 : :
919 : 0 : int udev_event_execute_rules(UdevEvent *event,
920 : : usec_t timeout_usec,
921 : : Hashmap *properties_list,
922 : : UdevRules *rules) {
923 : : const char *subsystem;
924 : : DeviceAction action;
925 : : sd_device *dev;
926 : : int r;
927 : :
928 [ # # ]: 0 : assert(event);
929 [ # # ]: 0 : assert(rules);
930 : :
931 : 0 : dev = event->dev;
932 : :
933 : 0 : r = sd_device_get_subsystem(dev, &subsystem);
934 [ # # ]: 0 : if (r < 0)
935 [ # # # # : 0 : return log_device_error_errno(dev, r, "Failed to get subsystem: %m");
# # ]
936 : :
937 : 0 : r = device_get_action(dev, &action);
938 [ # # ]: 0 : if (r < 0)
939 [ # # # # : 0 : return log_device_error_errno(dev, r, "Failed to get ACTION: %m");
# # ]
940 : :
941 [ # # ]: 0 : if (action == DEVICE_ACTION_REMOVE) {
942 : 0 : event_execute_rules_on_remove(event, timeout_usec, properties_list, rules);
943 : 0 : return 0;
944 : : }
945 : :
946 : 0 : r = device_clone_with_db(dev, &event->dev_db_clone);
947 [ # # ]: 0 : if (r < 0)
948 [ # # # # : 0 : return log_device_debug_errno(dev, r, "Failed to clone sd_device object: %m");
# # ]
949 : :
950 [ # # # # ]: 0 : if (event->dev_db_clone && sd_device_get_devnum(dev, NULL) >= 0)
951 : : /* Disable watch during event processing. */
952 : 0 : (void) udev_watch_end(event->dev_db_clone);
953 : :
954 [ # # ]: 0 : if (action == DEVICE_ACTION_MOVE) {
955 : 0 : r = udev_event_on_move(event);
956 [ # # ]: 0 : if (r < 0)
957 : 0 : return r;
958 : : }
959 : :
960 : 0 : r = udev_rules_apply_to_event(rules, event, timeout_usec, properties_list);
961 [ # # ]: 0 : if (r < 0)
962 [ # # # # : 0 : return log_device_debug_errno(dev, r, "Failed to apply udev rules: %m");
# # ]
963 : :
964 : 0 : r = rename_netif(event);
965 [ # # ]: 0 : if (r < 0)
966 : 0 : return r;
967 : :
968 : 0 : r = update_devnode(event);
969 [ # # ]: 0 : if (r < 0)
970 : 0 : return r;
971 : :
972 : : /* preserve old, or get new initialization timestamp */
973 : 0 : r = device_ensure_usec_initialized(dev, event->dev_db_clone);
974 [ # # ]: 0 : if (r < 0)
975 [ # # # # : 0 : return log_device_debug_errno(dev, r, "Failed to set initialization timestamp: %m");
# # ]
976 : :
977 : : /* (re)write database file */
978 : 0 : r = device_tag_index(dev, event->dev_db_clone, true);
979 [ # # ]: 0 : if (r < 0)
980 [ # # # # : 0 : return log_device_debug_errno(dev, r, "Failed to update tags under /run/udev/tag/: %m");
# # ]
981 : :
982 : 0 : r = device_update_db(dev);
983 [ # # ]: 0 : if (r < 0)
984 [ # # # # : 0 : return log_device_debug_errno(dev, r, "Failed to update database under /run/udev/data/: %m");
# # ]
985 : :
986 : 0 : device_set_is_initialized(dev);
987 : :
988 : 0 : event->dev_db_clone = sd_device_unref(event->dev_db_clone);
989 : :
990 : 0 : return 0;
991 : : }
992 : :
993 : 0 : void udev_event_execute_run(UdevEvent *event, usec_t timeout_usec) {
994 : : const char *command;
995 : : void *val;
996 : : Iterator i;
997 : : int r;
998 : :
999 [ # # ]: 0 : ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list, i) {
1000 : 0 : UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val);
1001 : :
1002 [ # # ]: 0 : if (builtin_cmd != _UDEV_BUILTIN_INVALID) {
1003 [ # # # # : 0 : log_device_debug(event->dev, "Running built-in command \"%s\"", command);
# # ]
1004 : 0 : r = udev_builtin_run(event->dev, builtin_cmd, command, false);
1005 [ # # ]: 0 : if (r < 0)
1006 [ # # # # : 0 : log_device_debug_errno(event->dev, r, "Failed to run built-in command \"%s\", ignoring: %m", command);
# # ]
1007 : : } else {
1008 [ # # ]: 0 : if (event->exec_delay_usec > 0) {
1009 : : char buf[FORMAT_TIMESPAN_MAX];
1010 : :
1011 [ # # # # : 0 : log_device_debug(event->dev, "Delaying execution of \"%s\" for %s.",
# # ]
1012 : : command, format_timespan(buf, sizeof(buf), event->exec_delay_usec, USEC_PER_SEC));
1013 : 0 : (void) usleep(event->exec_delay_usec);
1014 : : }
1015 : :
1016 [ # # # # : 0 : log_device_debug(event->dev, "Running command \"%s\"", command);
# # ]
1017 : 0 : r = udev_event_spawn(event, timeout_usec, false, command, NULL, 0);
1018 [ # # ]: 0 : if (r > 0) /* returned value is positive when program fails */
1019 [ # # # # : 0 : log_device_debug(event->dev, "Command \"%s\" returned %d (error), ignoring.", command, r);
# # ]
1020 : : }
1021 : : }
1022 : 0 : }
|