]> git.ipfire.org Git - thirdparty/systemd.git/blame - udev_rules.c
udevd: create leading directories for /dev/.udev/uevent_seqnum
[thirdparty/systemd.git] / udev_rules.c
CommitLineData
2232cac8 1/*
e5e322bc 2 * udev_rules.c
2232cac8 3 *
2232cac8 4 * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
fd9efc00 5 * Copyright (C) 2003-2005 Kay Sievers <kay.sievers@vrfy.org>
2232cac8 6 *
2232cac8
GKH
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation version 2 of the License.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 675 Mass Ave, Cambridge, MA 02139, USA.
19 *
20 */
21
22#include <stddef.h>
23#include <stdlib.h>
24#include <string.h>
25#include <stdio.h>
26#include <fcntl.h>
27#include <ctype.h>
28#include <unistd.h>
29#include <errno.h>
27f877e6 30#include <syslog.h>
c27e6911 31#include <sys/wait.h>
97c0448d 32#include <sys/stat.h>
2232cac8 33
2232cac8 34#include "udev.h"
e5e322bc 35#include "udev_rules.h"
2232cac8 36
a27cd06c 37
a27cd06c
KS
38/* extract possible {attr} and move str behind it */
39static char *get_format_attribute(char **str)
40{
41 char *pos;
42 char *attr = NULL;
43
44 if (*str[0] == '{') {
45 pos = strchr(*str, '}');
46 if (pos == NULL) {
6b493a20 47 err("missing closing brace for format");
a27cd06c
KS
48 return NULL;
49 }
50 pos[0] = '\0';
51 attr = *str+1;
52 *str = pos+1;
53 dbg("attribute='%s', str='%s'", attr, *str);
54 }
55 return attr;
56}
57
58/* extract possible format length and move str behind it*/
59static int get_format_len(char **str)
60{
61 int num;
62 char *tail;
63
64 if (isdigit(*str[0])) {
65 num = (int) strtoul(*str, &tail, 10);
aebef544 66 if (num > 0) {
a27cd06c
KS
67 *str = tail;
68 dbg("format length=%i", num);
69 return num;
70 } else {
6b493a20 71 err("format parsing error '%s'", *str);
a27cd06c
KS
72 }
73 }
74 return -1;
75}
76
bd0ed2ff
KS
77static int get_key(char **line, char **key, char **value)
78{
79 char *linepos;
80 char *temp;
81
82 linepos = *line;
83 if (!linepos)
84 return -1;
85
86 if (strchr(linepos, '\\')) {
87 dbg("escaped characters are not supported, skip");
88 return -1;
89 }
90
91 /* skip whitespace */
92 while (isspace(linepos[0]))
93 linepos++;
94
95 /* get the key */
96 *key = linepos;
97 while (1) {
98 linepos++;
99 if (linepos[0] == '\0')
100 return -1;
101 if (isspace(linepos[0]))
102 break;
103 if (linepos[0] == '=')
104 break;
105 }
106
107 /* terminate key */
108 linepos[0] = '\0';
109 linepos++;
110
111 /* skip whitespace */
112 while (isspace(linepos[0]))
113 linepos++;
114
115 /* get the value*/
bd0ed2ff
KS
116 if (linepos[0] == '"') {
117 linepos++;
118 temp = strchr(linepos, '"');
b8476286
KS
119 if (!temp) {
120 dbg("missing closing quote");
bd0ed2ff 121 return -1;
b8476286
KS
122 }
123 dbg("value is quoted");
bd0ed2ff
KS
124 temp[0] = '\0';
125 } else if (linepos[0] == '\'') {
126 linepos++;
127 temp = strchr(linepos, '\'');
b8476286
KS
128 if (!temp) {
129 dbg("missing closing quote");
bd0ed2ff 130 return -1;
b8476286
KS
131 }
132 dbg("value is quoted");
bd0ed2ff 133 temp[0] = '\0';
b8476286
KS
134 } else if (linepos[0] == '\0') {
135 dbg("value is empty");
bd0ed2ff
KS
136 } else {
137 temp = linepos;
138 while (temp[0] && !isspace(temp[0]))
139 temp++;
140 temp[0] = '\0';
141 }
142 *value = linepos;
143
144 return 0;
145}
146
b8476286 147static int import_keys_into_env(struct udevice *udev, const char *buf, size_t bufsize)
bd0ed2ff
KS
148{
149 char line[LINE_SIZE];
be4bedd1 150 const char *bufline;
bd0ed2ff
KS
151 char *linepos;
152 char *variable;
153 char *value;
bd0ed2ff
KS
154 size_t cur;
155 size_t count;
156 int lineno;
bd0ed2ff 157
b8476286 158 /* loop through the whole buffer */
bd0ed2ff
KS
159 lineno = 0;
160 cur = 0;
161 while (cur < bufsize) {
162 count = buf_get_line(buf, bufsize, cur);
163 bufline = &buf[cur];
164 cur += count+1;
165 lineno++;
166
167 if (count >= sizeof(line)) {
168 err("line too long, conf line skipped %s, line %d", udev_config_filename, lineno);
169 continue;
170 }
171
172 /* eat the whitespace */
173 while ((count > 0) && isspace(bufline[0])) {
174 bufline++;
175 count--;
176 }
177 if (count == 0)
178 continue;
179
180 /* see if this is a comment */
181 if (bufline[0] == COMMENT_CHARACTER)
182 continue;
183
13d11705
KS
184 memcpy(line, bufline, count);
185 line[count] = '\0';
bd0ed2ff
KS
186
187 linepos = line;
188 if (get_key(&linepos, &variable, &value) == 0) {
319c6700 189 dbg("import '%s=%s'", variable, value);
b8476286 190 name_list_key_add(&udev->env_list, variable, value);
319c6700 191 setenv(variable, value, 1);
bd0ed2ff
KS
192 }
193 }
194
be4bedd1
KS
195 return 0;
196}
197
b8476286 198static int import_file_into_env(struct udevice *udev, const char *filename)
be4bedd1
KS
199{
200 char *buf;
201 size_t bufsize;
202
203 if (file_map(filename, &buf, &bufsize) != 0) {
ff3e4bed 204 err("can't open '%s': %s", filename, strerror(errno));
be4bedd1
KS
205 return -1;
206 }
b8476286 207 import_keys_into_env(udev, buf, bufsize);
bd0ed2ff 208 file_unmap(buf, bufsize);
be4bedd1
KS
209
210 return 0;
bd0ed2ff
KS
211}
212
319c6700
KS
213static int import_program_into_env(struct udevice *udev, const char *program)
214{
215 char result[1024];
216 size_t reslen;
217
1aa1e248 218 if (run_program(program, udev->dev->subsystem, result, sizeof(result), &reslen, (udev_log_priority >= LOG_INFO)) != 0)
319c6700 219 return -1;
b8476286 220 return import_keys_into_env(udev, result, reslen);
319c6700
KS
221}
222
1aa1e248 223static int import_parent_into_env(struct udevice *udev, const char *filter)
0bfb84e1 224{
1aa1e248 225 struct sysfs_device *dev_parent;
0bfb84e1
KS
226 int rc = -1;
227
1aa1e248
KS
228 dev_parent = sysfs_device_get_parent(udev->dev);
229 if (dev_parent != NULL) {
230 struct udevice *udev_parent;
0bfb84e1
KS
231 struct name_entry *name_loop;
232
1aa1e248
KS
233 dbg("found parent '%s', get the node name", dev_parent->devpath);
234 udev_parent = udev_device_init();
235 if (udev_parent == NULL)
236 return -1;
0bfb84e1 237 /* import the udev_db of the parent */
1aa1e248
KS
238 if (udev_db_get_device(udev_parent, dev_parent->devpath) == 0) {
239 dbg("import stored parent env '%s'", udev_parent->name);
240 list_for_each_entry(name_loop, &udev_parent->env_list, node) {
0bfb84e1
KS
241 char name[NAME_SIZE];
242 char *pos;
243
244 strlcpy(name, name_loop->name, sizeof(name));
245 pos = strchr(name, '=');
246 if (pos) {
247 pos[0] = '\0';
248 pos++;
249 if (strcmp_pattern(filter, name) == 0) {
250 dbg("import key '%s'", name_loop->name);
251 name_list_add(&udev->env_list, name_loop->name, 0);
252 setenv(name, pos, 1);
253 } else
254 dbg("skip key '%s'", name_loop->name);
255 }
256 }
257 rc = 0;
258 } else
259 dbg("parent not found in database");
1aa1e248 260 udev_device_cleanup(udev_parent);
0bfb84e1
KS
261 }
262
263 return rc;
264}
265
2983db97
KS
266static int match_name_and_get_number(const char *base, const char *devname)
267{
268 size_t baselen;
269 char *endptr;
270 int num;
271
272 baselen = strlen(base);
273 if (strncmp(base, devname, baselen) != 0)
274 return -1;
275 if (devname[baselen] == '\0')
276 return 0;
277 if (!isdigit(devname[baselen]))
278 return -1;
279 num = strtoul(&devname[baselen], &endptr, 10);
280 if (endptr[0] != '\0')
281 return -1;
282 return num;
283}
284
285/* finds the lowest positive device number such that <name>N isn't present in the udevdb
286 * if <name> doesn't exist, 0 is returned, N otherwise */
287static int find_free_number(const char *base, const char *devpath)
0a8dd7f3 288{
314edf3c 289 char db_devpath[PATH_SIZE];
63f61c5c 290 char filename[PATH_SIZE];
1aa1e248 291 struct udevice *udev_db;
2b41e68a 292 int num = 0;
79102489
KS
293 static int warn = 1;
294
295 if (warn) {
00a07448 296 err("%%e is deprecated, will be removed and is unlikely to work correctly. Don't use it.");
79102489
KS
297 warn = 0;
298 }
0a8dd7f3 299
2983db97 300 /* check if the device already owns a matching name */
1aa1e248
KS
301 udev_db = udev_device_init();
302 if (udev_db == NULL)
303 return -1;
304 if (udev_db_get_device(udev_db, devpath) == 0) {
2983db97
KS
305 struct name_entry *name_loop;
306 int devnum;
307
1aa1e248 308 devnum = match_name_and_get_number(base, udev_db->name);
2983db97
KS
309 if (devnum >= 0) {
310 num = devnum;
311 dbg("device '%s', already has the node '%s' with num %u, use it", devpath, base, num);
312 goto out;
313 }
1aa1e248 314 list_for_each_entry(name_loop, &udev_db->symlink_list, node) {
2983db97
KS
315 devnum = match_name_and_get_number(base, name_loop->name);
316 if (devnum >= 0) {
317 num = devnum;
318 dbg("device '%s', already has a symlink '%s' with num %u, use it", devpath, base, num);
319 goto out;
320 }
321 }
322 }
323
324 /* just search the database again and again until a free name is found */
325 strlcpy(filename, base, sizeof(filename));
0a8dd7f3 326 while (1) {
2b41e68a 327 dbg("look for existing node '%s'", filename);
7104c558 328 if (udev_db_lookup_name(filename, db_devpath, sizeof(db_devpath)) != 0) {
2b41e68a 329 dbg("free num=%d", num);
314edf3c 330 break;
2b41e68a 331 }
0a8dd7f3 332
2b41e68a 333 num++;
314edf3c
KS
334 if (num > 100000) {
335 err("find_free_number aborted at num=%d", num);
336 num = -1;
337 break;
2b41e68a 338 }
2983db97 339 snprintf(filename, sizeof(filename), "%s%d", base, num);
63f61c5c 340 filename[sizeof(filename)-1] = '\0';
2b41e68a 341 }
314edf3c 342
2983db97 343out:
1aa1e248 344 udev_device_cleanup(udev_db);
314edf3c 345 return num;
0a8dd7f3
DZ
346}
347
c86be870 348#define WAIT_LOOP_PER_SECOND 50
b2fe4b9a
KS
349static int wait_for_sysfs(struct udevice *udev, const char *file, int timeout)
350{
656ba91e
KS
351 char devicepath[PATH_SIZE];
352 char filepath[PATH_SIZE];
b2fe4b9a
KS
353 struct stat stats;
354 int loop = timeout * WAIT_LOOP_PER_SECOND;
355
656ba91e
KS
356 strlcpy(devicepath, sysfs_path, sizeof(devicepath));
357 strlcat(devicepath, udev->dev->devpath, sizeof(devicepath));
358 strlcpy(filepath, devicepath, sizeof(filepath));
359 strlcat(filepath, "/", sizeof(filepath));
360 strlcat(filepath, file, sizeof(filepath));
b2fe4b9a 361
656ba91e 362 dbg("will wait %i sec for '%s'", timeout, filepath);
b2fe4b9a 363 while (--loop) {
656ba91e
KS
364 /* lookup file */
365 if (stat(filepath, &stats) == 0) {
366 info("file '%s' appeared after %i loops", filepath, (timeout * WAIT_LOOP_PER_SECOND) - loop-1);
b2fe4b9a
KS
367 return 0;
368 }
656ba91e
KS
369 /* make sure the device does not have disappeared in the meantime */
370 if (stat(devicepath, &stats) != 0) {
371 info("device disappeared while waiting for '%s'", filepath);
372 return -2;
373 }
374 info("wait for '%s' for %i mseconds", filepath, 1000 / WAIT_LOOP_PER_SECOND);
b2fe4b9a
KS
375 usleep(1000 * 1000 / WAIT_LOOP_PER_SECOND);
376 }
656ba91e 377 err("waiting for '%s' failed", filepath);
b2fe4b9a
KS
378 return -1;
379}
380
a9bd2ed8 381void udev_rules_apply_format(struct udevice *udev, char *string, size_t maxsize)
f3b04a2e 382{
63f61c5c
KS
383 char temp[PATH_SIZE];
384 char temp2[PATH_SIZE];
bd0ed2ff 385 char *head, *tail, *pos, *cpos, *attr, *rest;
63ead27c 386 int len;
88f09368 387 int i;
764ce7f2 388 int count;
0a8dd7f3 389 unsigned int next_free_number;
bf5d2964
KS
390 enum subst_type {
391 SUBST_UNKNOWN,
392 SUBST_DEVPATH,
bf5d2964
KS
393 SUBST_KERNEL_NUMBER,
394 SUBST_KERNEL_NAME,
03b24b71 395 SUBST_ID,
bf5d2964
KS
396 SUBST_MAJOR,
397 SUBST_MINOR,
398 SUBST_RESULT,
399 SUBST_SYSFS,
400 SUBST_ENUM,
401 SUBST_PARENT,
402 SUBST_TEMP_NODE,
403 SUBST_ROOT,
404 SUBST_MODALIAS,
bd0ed2ff 405 SUBST_ENV,
bf5d2964
KS
406 };
407 static const struct subst_map {
408 char *name;
409 char fmt;
410 enum subst_type type;
411 } map[] = {
412 { .name = "devpath", .fmt = 'p', .type = SUBST_DEVPATH },
bf5d2964
KS
413 { .name = "number", .fmt = 'n', .type = SUBST_KERNEL_NUMBER },
414 { .name = "kernel", .fmt = 'k', .type = SUBST_KERNEL_NAME },
03b24b71 415 { .name = "id", .fmt = 'b', .type = SUBST_ID },
bf5d2964
KS
416 { .name = "major", .fmt = 'M', .type = SUBST_MAJOR },
417 { .name = "minor", .fmt = 'm', .type = SUBST_MINOR },
418 { .name = "result", .fmt = 'c', .type = SUBST_RESULT },
419 { .name = "sysfs", .fmt = 's', .type = SUBST_SYSFS },
420 { .name = "enum", .fmt = 'e', .type = SUBST_ENUM },
421 { .name = "parent", .fmt = 'P', .type = SUBST_PARENT },
422 { .name = "tempnode", .fmt = 'N', .type = SUBST_TEMP_NODE },
423 { .name = "root", .fmt = 'r', .type = SUBST_ROOT },
424 { .name = "modalias", .fmt = 'A', .type = SUBST_MODALIAS },
bd0ed2ff 425 { .name = "env", .fmt = 'E', .type = SUBST_ENV },
853ccc43 426 { NULL, '\0', 0 }
bf5d2964
KS
427 };
428 enum subst_type type;
429 const struct subst_map *subst;
430
431 head = string;
f3b04a2e 432 while (1) {
bf5d2964
KS
433 len = -1;
434 while (head[0] != '\0') {
435 if (head[0] == '$') {
436 /* substitute named variable */
437 if (head[1] == '\0')
438 break;
439 if (head[1] == '$') {
440 strlcpy(temp, head+2, sizeof(temp));
441 strlcpy(head+1, temp, maxsize);
442 head++;
443 continue;
444 }
445 head[0] = '\0';
446 for (subst = map; subst->name; subst++) {
447 if (strncasecmp(&head[1], subst->name, strlen(subst->name)) == 0) {
448 type = subst->type;
449 tail = head + strlen(subst->name)+1;
450 dbg("will substitute format name '%s'", subst->name);
451 goto found;
452 }
453 }
454 }
455 else if (head[0] == '%') {
456 /* substitute format char */
457 if (head[1] == '\0')
458 break;
459 if (head[1] == '%') {
460 strlcpy(temp, head+2, sizeof(temp));
461 strlcpy(head+1, temp, maxsize);
462 head++;
463 continue;
464 }
465 head[0] = '\0';
466 tail = head+1;
467 len = get_format_len(&tail);
468 for (subst = map; subst->name; subst++) {
469 if (tail[0] == subst->fmt) {
470 type = subst->type;
471 tail++;
472 dbg("will substitute format char '%c'", subst->fmt);
473 goto found;
474 }
475 }
476 }
477 head++;
478 }
479 break;
480found:
a27cd06c 481 attr = get_format_attribute(&tail);
bf5d2964 482 strlcpy(temp, tail, sizeof(temp));
1aa1e248 483 dbg("format=%i, string='%s', tail='%s'", type ,string, tail);
a27cd06c 484
bf5d2964
KS
485 switch (type) {
486 case SUBST_DEVPATH:
1aa1e248
KS
487 strlcat(string, udev->dev->devpath, maxsize);
488 dbg("substitute devpath '%s'", udev->dev->devpath);
a27cd06c 489 break;
bf5d2964 490 case SUBST_KERNEL_NAME:
1aa1e248
KS
491 strlcat(string, udev->dev->kernel_name, maxsize);
492 dbg("substitute kernel name '%s'", udev->dev->kernel_name);
a27cd06c 493 break;
bf5d2964 494 case SUBST_KERNEL_NUMBER:
1aa1e248
KS
495 strlcat(string, udev->dev->kernel_number, maxsize);
496 dbg("substitute kernel number '%s'", udev->dev->kernel_number);
a27cd06c 497 break;
03b24b71
KS
498 case SUBST_ID:
499 if (udev->dev_parent != NULL) {
500 strlcat(string, udev->dev_parent->kernel_name, maxsize);
501 dbg("substitute id '%s'", udev->dev_parent->kernel_name);
502 }
503 break;
bf5d2964 504 case SUBST_MAJOR:
03fd7a3a 505 sprintf(temp2, "%d", major(udev->devt));
63f61c5c 506 strlcat(string, temp2, maxsize);
03fd7a3a 507 dbg("substitute major number '%s'", temp2);
a27cd06c 508 break;
bf5d2964
KS
509 case SUBST_MINOR:
510 sprintf(temp2, "%d", minor(udev->devt));
511 strlcat(string, temp2, maxsize);
512 dbg("substitute minor number '%s'", temp2);
513 break;
514 case SUBST_RESULT:
b821330f 515 if (udev->program_result[0] == '\0')
f3b04a2e 516 break;
88f09368 517 /* get part part of the result string */
63ead27c 518 i = 0;
88f09368 519 if (attr != NULL)
558f80ba 520 i = strtoul(attr, &rest, 10);
88f09368 521 if (i > 0) {
03fd7a3a
KS
522 dbg("request part #%d of result string", i);
523 cpos = udev->program_result;
524 while (--i) {
525 while (cpos[0] != '\0' && !isspace(cpos[0]))
526 cpos++;
527 while (isspace(cpos[0]))
528 cpos++;
b1c5e333 529 }
9fe3f9a9 530 if (i > 0) {
6b493a20 531 err("requested part of result string not found");
9fe3f9a9 532 break;
a27cd06c 533 }
63f61c5c 534 strlcpy(temp2, cpos, sizeof(temp2));
03fd7a3a
KS
535 /* %{2+}c copies the whole string from the second part on */
536 if (rest[0] != '+') {
537 cpos = strchr(temp2, ' ');
538 if (cpos)
539 cpos[0] = '\0';
540 }
63f61c5c 541 strlcat(string, temp2, maxsize);
27c3403d 542 dbg("substitute part of result string '%s'", temp2);
a27cd06c 543 } else {
63f61c5c 544 strlcat(string, udev->program_result, maxsize);
a27cd06c 545 dbg("substitute result string '%s'", udev->program_result);
f3b04a2e 546 }
f3b04a2e 547 break;
bf5d2964 548 case SUBST_SYSFS:
5e39f90b 549 if (attr == NULL) {
a27cd06c 550 dbg("missing attribute");
5e39f90b 551 break;
1aa1e248
KS
552 } else {
553 struct sysfs_device *dev_parent;
554 const char *value;
555
556 dev_parent = udev->dev;
557 do {
558 dbg("looking at '%s'", dev_parent->devpath);
559 value = sysfs_attr_get_value(dev_parent->devpath, attr);
560 if (value != NULL) {
561 strlcpy(temp2, value, sizeof(temp2));
98bbc835 562 break;
1aa1e248
KS
563 }
564 dev_parent = sysfs_device_get_parent(dev_parent);
565 } while (dev_parent != NULL);
566
567 /* strip trailing whitespace of sysfs value */
568 i = strlen(temp2);
569 while (i > 0 && isspace(temp2[i-1]))
570 temp2[--i] = '\0';
571 count = replace_untrusted_chars(temp2);
572 if (count)
573 info("%i untrusted character(s) replaced" , count);
574 strlcat(string, temp2, maxsize);
575 dbg("substitute sysfs value '%s'", temp2);
5e39f90b 576 }
a27cd06c 577 break;
bf5d2964 578 case SUBST_ENUM:
1aa1e248 579 next_free_number = find_free_number(string, udev->dev->devpath);
0a8dd7f3 580 if (next_free_number > 0) {
2b41e68a 581 sprintf(temp2, "%d", next_free_number);
63f61c5c 582 strlcat(string, temp2, maxsize);
0a8dd7f3
DZ
583 }
584 break;
bf5d2964 585 case SUBST_PARENT:
1aa1e248
KS
586 {
587 struct sysfs_device *dev_parent;
588
589 dev_parent = sysfs_device_get_parent(udev->dev);
590 if (dev_parent != NULL) {
591 struct udevice *udev_parent;
592
593 dbg("found parent '%s', get the node name", dev_parent->devpath);
594 udev_parent = udev_device_init();
595 if (udev_parent != NULL) {
596 /* lookup the name in the udev_db with the DEVPATH of the parent */
597 if (udev_db_get_device(udev_parent, dev_parent->devpath) == 0) {
598 strlcat(string, udev_parent->name, maxsize);
599 dbg("substitute parent node name'%s'", udev_parent->name);
600 } else
601 dbg("parent not found in database");
602 udev_device_cleanup(udev_parent);
603 }
604 }
69aa6dfb
KS
605 }
606 break;
bf5d2964 607 case SUBST_TEMP_NODE:
c1ab0461
KS
608 if (udev->tmp_node[0] == '\0') {
609 dbg("create temporary device node for callout");
63f61c5c
KS
610 snprintf(udev->tmp_node, sizeof(udev->tmp_node), "%s/.tmp-%u-%u",
611 udev_root, major(udev->devt), minor(udev->devt));
612 udev->tmp_node[sizeof(udev->tmp_node)-1] = '\0';
a4d5ca64 613 udev_node_mknod(udev, udev->tmp_node, udev->devt, 0600, 0, 0);
c1ab0461 614 }
63f61c5c 615 strlcat(string, udev->tmp_node, maxsize);
c1ab0461
KS
616 dbg("substitute temporary device node name '%s'", udev->tmp_node);
617 break;
bf5d2964 618 case SUBST_ROOT:
63f61c5c 619 strlcat(string, udev_root, maxsize);
69aa6dfb
KS
620 dbg("substitute udev_root '%s'", udev_root);
621 break;
bf5d2964 622 case SUBST_MODALIAS:
1aa1e248
KS
623 {
624 const char *value;
79102489
KS
625 static int warn = 1;
626
627 if (warn) {
628 err("$modalias is deprecated, use $env{MODALIAS} or "
629 "$sysfs{modalias} instead.");
630 warn = 0;
631 }
1aa1e248
KS
632
633 value = sysfs_attr_get_value(udev->dev->devpath, "modalias");
634 if (value != NULL) {
635 strlcat(string, value, maxsize);
636 dbg("substitute MODALIAS '%s'", temp2);
637 }
638 }
bf5d2964 639 break;
bd0ed2ff
KS
640 case SUBST_ENV:
641 if (attr == NULL) {
642 dbg("missing attribute");
643 break;
644 }
645 pos = getenv(attr);
0bfb84e1 646 if (pos == NULL) {
12997964 647 dbg("env '%s' not available", attr);
bd0ed2ff 648 break;
0bfb84e1 649 }
bd0ed2ff 650 dbg("substitute env '%s=%s'", attr, pos);
ef520ba2 651 strlcat(string, pos, maxsize);
bd0ed2ff 652 break;
a27cd06c 653 default:
bf5d2964 654 err("unknown substitution type=%i", type);
a27cd06c
KS
655 break;
656 }
bf5d2964
KS
657 /* possibly truncate to format-char specified length */
658 if (len != -1) {
659 head[len] = '\0';
660 dbg("truncate to %i chars, subtitution string becomes '%s'", len, head);
661 }
bf5d2964 662 strlcat(string, temp, maxsize);
f3b04a2e
GKH
663 }
664}
665
8bd41f36
KS
666static char *key_val(struct udev_rule *rule, struct key *key)
667{
668 return rule->buf + key->val_off;
669}
670
671static char *key_pair_name(struct udev_rule *rule, struct key_pair *pair)
672{
673 return rule->buf + pair->key_name_off;
674}
675
676static int match_key(const char *key_name, struct udev_rule *rule, struct key *key, const char *val)
a0e5382d
KS
677{
678 int match;
0cd4ac47 679 char value[PATH_SIZE];
8bd41f36 680 char *key_value;
0cd4ac47 681 char *pos;
a0e5382d 682
d59c84ef
KS
683 if (key->operation != KEY_OP_MATCH &&
684 key->operation != KEY_OP_NOMATCH)
a0e5382d
KS
685 return 0;
686
0cd4ac47
KS
687 strlcpy(value, rule->buf + key->val_off, sizeof(value));
688 key_value = value;
8bd41f36 689
0cd4ac47
KS
690 dbg("key %s value='%s'", key_name, key_value);
691 while (key_value) {
692 pos = strchr(key_value, '|');
693 if (pos) {
694 pos[0] = '\0';
695 pos++;
696 }
697 dbg("match %s '%s' <-> '%s'", key_name, key_value, val);
698 match = (strcmp_pattern(key_value, val) == 0);
699 if (match && (key->operation != KEY_OP_NOMATCH)) {
700 dbg("%s is true (matching value)", key_name);
701 return 0;
702 }
703 if (!match && (key->operation == KEY_OP_NOMATCH)) {
704 dbg("%s is true (non-matching value)", key_name);
705 return 0;
706 }
707 key_value = pos;
a0e5382d 708 }
0cd4ac47 709 dbg("%s is false", key_name);
a0e5382d
KS
710 return -1;
711}
712
2983db97 713/* match a single rule against a given device and possibly its parent devices */
1aa1e248 714static int match_rule(struct udevice *udev, struct udev_rule *rule)
dac056aa 715{
5618b561 716 int i;
aa341f21 717
8bd41f36 718 if (match_key("ACTION", rule, &rule->action, udev->action))
03b24b71 719 goto nomatch;
821d0ec8 720
1aa1e248 721 if (match_key("KERNEL", rule, &rule->kernel_name, udev->dev->kernel_name))
03b24b71 722 goto nomatch;
ca1cc0fe 723
1aa1e248 724 if (match_key("SUBSYSTEM", rule, &rule->subsystem, udev->dev->subsystem))
03b24b71 725 goto nomatch;
6818c51d 726
1aa1e248 727 if (match_key("DEVPATH", rule, &rule->devpath, udev->dev->devpath))
03b24b71 728 goto nomatch;
bf5d2964 729
d59c84ef
KS
730 /* compare NAME against a previously assigned value */
731 if (match_key("NAME", rule, &rule->name, udev->name))
732 goto nomatch;
733
8bd41f36 734 if (rule->modalias.operation != KEY_OP_UNSET) {
1aa1e248 735 const char *value;
79102489
KS
736 static int warn = 1;
737
738 if (warn) {
739 err("MODALIAS is deprecated, use ENV{MODALIAS} or SYSFS{modalias} instead.");
740 warn = 0;
741 }
bf5d2964 742
1aa1e248
KS
743 value = sysfs_attr_get_value(udev->dev->devpath, "modalias");
744 if (value == NULL) {
63698391 745 dbg("MODALIAS value not found");
03b24b71 746 goto nomatch;
bf5d2964 747 }
8bd41f36 748 if (match_key("MODALIAS", rule, &rule->modalias, value))
03b24b71 749 goto nomatch;
bf5d2964
KS
750 }
751
5618b561
KS
752 for (i = 0; i < rule->env.count; i++) {
753 struct key_pair *pair = &rule->env.keys[i];
3e5958de 754
5618b561 755 /* we only check for matches, assignments will be handled later */
d59c84ef
KS
756 if (pair->key.operation == KEY_OP_MATCH ||
757 pair->key.operation == KEY_OP_NOMATCH) {
8bd41f36
KS
758 const char *key_name = key_pair_name(rule, pair);
759 const char *value = getenv(key_name);
3e5958de 760
3e5958de 761 if (!value) {
5618b561 762 dbg("ENV{'%s'} is not set, treat as empty", key_name);
c609f627 763 value = "";
3e5958de 764 }
8bd41f36 765 if (match_key("ENV", rule, &pair->key, value))
03b24b71 766 goto nomatch;
3e5958de 767 }
3e5958de
KS
768 }
769
b2fe4b9a 770 if (rule->wait_for_sysfs.operation != KEY_OP_UNSET) {
f8db897f 771 int found;
b2fe4b9a 772
f8db897f
KS
773 found = (wait_for_sysfs(udev, key_val(rule, &rule->wait_for_sysfs), 3) == 0);
774 if (!found && (rule->wait_for_sysfs.operation != KEY_OP_NOMATCH)) {
775 dbg("WAIT_FOR_SYSFS failed");
776 goto nomatch;
b2fe4b9a 777 }
b2fe4b9a
KS
778 }
779
03b24b71
KS
780 /* walk up the chain of parent devices and find a match */
781 udev->dev_parent = udev->dev;
03a9875b 782 while (1) {
2092fbcd 783 /* check for matching driver */
d59c84ef
KS
784 if (match_key("DRIVER", rule, &rule->driver, udev->dev_parent->driver))
785 goto try_parent;
2092fbcd 786
03b24b71 787 /* check for matching subsystem/bus value */
d59c84ef
KS
788 if (match_key("BUS", rule, &rule->bus, udev->dev_parent->subsystem))
789 goto try_parent;
82b16983 790
03b24b71 791 /* check for matching bus id (device name) */
d59c84ef
KS
792 if (match_key("ID", rule, &rule->id, udev->dev_parent->kernel_name))
793 goto try_parent;
ca1cc0fe 794
ac28b86d 795 /* check for matching sysfs pairs */
8bd41f36 796 if (rule->sysfs.count) {
8bd41f36
KS
797 dbg("check %i SYSFS keys", rule->sysfs.count);
798 for (i = 0; i < rule->sysfs.count; i++) {
799 struct key_pair *pair = &rule->sysfs.keys[i];
800 const char *key_name = key_pair_name(rule, pair);
801 const char *key_value = key_val(rule, &pair->key);
1aa1e248
KS
802 const char *value;
803 char val[VALUE_SIZE];
bb1a77d3 804 size_t len;
3e5958de 805
03b24b71 806 value = sysfs_attr_get_value(udev->dev_parent->devpath, key_name);
9538b16d
KS
807 if (value == NULL)
808 value = sysfs_attr_get_value(udev->dev->devpath, key_name);
1aa1e248 809 if (value == NULL)
db949b02 810 goto try_parent;
1aa1e248 811 strlcpy(val, value, sizeof(val));
db949b02
KS
812
813 /* strip trailing whitespace of value, if not asked to match for it */
8bd41f36 814 len = strlen(key_value);
1aa1e248
KS
815 if (len > 0 && !isspace(key_value[len-1])) {
816 len = strlen(val);
817 while (len > 0 && isspace(val[len-1]))
818 val[--len] = '\0';
819 dbg("removed %zi trailing whitespace chars from '%s'", strlen(val)-len, val);
db949b02
KS
820 }
821
1aa1e248 822 if (match_key("SYSFS", rule, &pair->key, val))
a0e5382d 823 goto try_parent;
ac28b86d 824 }
8bd41f36 825 dbg("all %i SYSFS keys matched", rule->sysfs.count);
ac28b86d
KS
826 }
827
1aa1e248 828 /* found matching device */
e57e7bc1 829 break;
c5118442 830try_parent:
03b24b71 831 /* move to parent device */
c5118442 832 dbg("try parent sysfs device");
03b24b71
KS
833 udev->dev_parent = sysfs_device_get_parent(udev->dev_parent);
834 if (udev->dev_parent == NULL)
835 goto nomatch;
836 dbg("looking at dev_parent->devpath='%s'", udev->dev_parent->devpath);
837 dbg("looking at dev_parent->bus_kernel_name='%s'", udev->dev_parent->kernel_name);
724257d9 838 }
82b16983 839
e57e7bc1 840 /* execute external program */
8bd41f36 841 if (rule->program.operation != KEY_OP_UNSET) {
63f61c5c 842 char program[PATH_SIZE];
be4bedd1 843 char result[PATH_SIZE];
e57e7bc1 844
8bd41f36 845 strlcpy(program, key_val(rule, &rule->program), sizeof(program));
a9bd2ed8 846 udev_rules_apply_format(udev, program, sizeof(program));
1aa1e248 847 if (run_program(program, udev->dev->subsystem, result, sizeof(result), NULL, (udev_log_priority >= LOG_INFO)) != 0) {
0cd4ac47 848 dbg("PROGRAM is false");
27f877e6 849 udev->program_result[0] = '\0';
8bd41f36 850 if (rule->program.operation != KEY_OP_NOMATCH)
03b24b71 851 goto nomatch;
28ce66de 852 } else {
764ce7f2
KS
853 int count;
854
63698391 855 dbg("PROGRAM matches");
b2c6818d 856 remove_trailing_chars(result, '\n');
764ce7f2
KS
857 count = replace_untrusted_chars(result);
858 if (count)
859 info("%i untrusted character(s) replaced" , count);
be4bedd1
KS
860 dbg("result is '%s'", result);
861 strlcpy(udev->program_result, result, sizeof(udev->program_result));
63698391 862 dbg("PROGRAM returned successful");
8bd41f36 863 if (rule->program.operation == KEY_OP_NOMATCH)
03b24b71 864 goto nomatch;
e57e7bc1 865 }
63698391 866 dbg("PROGRAM key is true");
e57e7bc1
KS
867 }
868
869 /* check for matching result of external program */
8bd41f36 870 if (match_key("RESULT", rule, &rule->result, udev->program_result))
03b24b71 871 goto nomatch;
e57e7bc1 872
5618b561
KS
873 /* import variables returned from program or or file into environment */
874 if (rule->import.operation != KEY_OP_UNSET) {
875 char import[PATH_SIZE];
876 int rc = -1;
877
878 strlcpy(import, key_val(rule, &rule->import), sizeof(import));
a9bd2ed8 879 udev_rules_apply_format(udev, import, sizeof(import));
5618b561
KS
880 dbg("check for IMPORT import='%s'", import);
881 if (rule->import_type == IMPORT_PROGRAM) {
882 rc = import_program_into_env(udev, import);
883 } else if (rule->import_type == IMPORT_FILE) {
884 dbg("import file import='%s'", import);
885 rc = import_file_into_env(udev, import);
1aa1e248 886 } else if (rule->import_type == IMPORT_PARENT) {
5618b561 887 dbg("import parent import='%s'", import);
1aa1e248 888 rc = import_parent_into_env(udev, import);
5618b561 889 }
1aa1e248 890 if (rc != 0) {
5618b561
KS
891 dbg("IMPORT failed");
892 if (rule->import.operation != KEY_OP_NOMATCH)
03b24b71 893 goto nomatch;
5618b561
KS
894 } else
895 dbg("IMPORT '%s' imported", key_val(rule, &rule->import));
896 dbg("IMPORT key is true");
897 }
898
899 /* rule matches, if we have ENV assignments export it */
900 for (i = 0; i < rule->env.count; i++) {
901 struct key_pair *pair = &rule->env.keys[i];
902
903 if (pair->key.operation == KEY_OP_ASSIGN) {
904 const char *key_name = key_pair_name(rule, pair);
905 const char *value = key_val(rule, &pair->key);
7ba2d2e6
KS
906 char *key_value = name_list_key_add(&udev->env_list, key_name, value);
907 if (key_value == NULL)
908 break;
5618b561 909
7ba2d2e6
KS
910 udev_rules_apply_format(udev, key_value, NAME_SIZE);
911 putenv(key_value);
912 dbg("export ENV '%s'", key_value);
5618b561
KS
913 }
914 }
915
e57e7bc1
KS
916 return 0;
917
03b24b71 918nomatch:
82b16983 919 return -1;
724257d9
GKH
920}
921
1aa1e248 922int udev_rules_get_name(struct udev_rules *rules, struct udevice *udev)
724257d9 923{
e5e322bc 924 struct udev_rule *rule;
613ffbeb 925 int name_set = 0;
724257d9 926
1aa1e248
KS
927 dbg("udev->dev->devpath='%s'", udev->dev->devpath);
928 dbg("udev->dev->kernel_name='%s'", udev->dev->kernel_name);
724257d9
GKH
929
930 /* look for a matching rule to apply */
8bd41f36 931 udev_rules_iter_init(rules);
6bf0ffe8 932 while (1) {
8bd41f36 933 rule = udev_rules_iter_next(rules);
6bf0ffe8
KS
934 if (rule == NULL)
935 break;
936
d59c84ef
KS
937 if (name_set &&
938 (rule->name.operation == KEY_OP_ASSIGN ||
939 rule->name.operation == KEY_OP_ASSIGN_FINAL ||
940 rule->name.operation == KEY_OP_ADD)) {
bf5d2964
KS
941 dbg("node name already set, rule ignored");
942 continue;
943 }
944
724257d9 945 dbg("process rule");
1aa1e248 946 if (match_rule(udev, rule) == 0) {
fd9efc00 947 /* apply options */
e5e322bc 948 if (rule->ignore_device) {
1aa1e248 949 info("rule applied, '%s' is ignored", udev->dev->kernel_name);
821d0ec8
KS
950 udev->ignore_device = 1;
951 return 0;
fd9efc00 952 }
e5e322bc 953 if (rule->ignore_remove) {
b821330f 954 udev->ignore_remove = 1;
e5e322bc 955 dbg("remove event should be ignored");
fd9efc00
KS
956 }
957 /* apply all_partitions option only at a main block device */
1aa1e248
KS
958 if (rule->partitions &&
959 strcmp(udev->dev->subsystem, "block") == 0 && udev->dev->kernel_number[0] == '\0') {
e5e322bc 960 udev->partitions = rule->partitions;
fd9efc00
KS
961 dbg("creation of partition nodes requested");
962 }
963
38875577 964 /* apply permissions */
c974742b
KS
965 if (!udev->mode_final && rule->mode != 0000) {
966 if (rule->mode_operation == KEY_OP_ASSIGN_FINAL)
967 udev->mode_final = 1;
e5e322bc 968 udev->mode = rule->mode;
1aa1e248 969 dbg("applied mode=%#o to '%s'", rule->mode, udev->dev->kernel_name);
38875577 970 }
8bd41f36
KS
971 if (!udev->owner_final && rule->owner.operation != KEY_OP_UNSET) {
972 if (rule->owner.operation == KEY_OP_ASSIGN_FINAL)
c974742b 973 udev->owner_final = 1;
8bd41f36 974 strlcpy(udev->owner, key_val(rule, &rule->owner), sizeof(udev->owner));
a9bd2ed8 975 udev_rules_apply_format(udev, udev->owner, sizeof(udev->owner));
1aa1e248 976 dbg("applied owner='%s' to '%s'", udev->owner, udev->dev->kernel_name);
38875577 977 }
8bd41f36
KS
978 if (!udev->group_final && rule->group.operation != KEY_OP_UNSET) {
979 if (rule->group.operation == KEY_OP_ASSIGN_FINAL)
c974742b 980 udev->group_final = 1;
8bd41f36 981 strlcpy(udev->group, key_val(rule, &rule->group), sizeof(udev->group));
a9bd2ed8 982 udev_rules_apply_format(udev, udev->group, sizeof(udev->group));
1aa1e248 983 dbg("applied group='%s' to '%s'", udev->group, udev->dev->kernel_name);
38875577
MB
984 }
985
9ed47a9f 986 /* collect symlinks */
8bd41f36 987 if (!udev->symlink_final && rule->symlink.operation != KEY_OP_UNSET) {
63f61c5c 988 char temp[PATH_SIZE];
e48fc108 989 char *pos, *next;
764ce7f2 990 int count;
ddd5b5dc 991
8bd41f36 992 if (rule->symlink.operation == KEY_OP_ASSIGN_FINAL)
c974742b 993 udev->symlink_final = 1;
8bd41f36 994 if (rule->symlink.operation == KEY_OP_ASSIGN || rule->symlink.operation == KEY_OP_ASSIGN_FINAL) {
c07669bd 995 info("reset symlink list");
fb179207 996 name_list_cleanup(&udev->symlink_list);
995aec87 997 }
8bd41f36 998 strlcpy(temp, key_val(rule, &rule->symlink), sizeof(temp));
a9bd2ed8 999 udev_rules_apply_format(udev, temp, sizeof(temp));
764ce7f2
KS
1000 count = replace_untrusted_chars(temp);
1001 if (count)
1002 info("%i untrusted character(s) replaced" , count);
1003 dbg("rule applied, added symlink(s) '%s'", temp);
8bd41f36
KS
1004
1005 /* add multiple symlinks separated by spaces */
1006 pos = temp;
7b2bdb4b
KS
1007 while (isspace(pos[0]))
1008 pos++;
1009 next = strchr(pos, ' ');
8bd41f36
KS
1010 while (next) {
1011 next[0] = '\0';
6b493a20 1012 info("add symlink '%s'", pos);
e48fc108 1013 name_list_add(&udev->symlink_list, pos, 0);
7b2bdb4b
KS
1014 while (isspace(next[1]))
1015 next++;
8bd41f36
KS
1016 pos = &next[1];
1017 next = strchr(pos, ' ');
e48fc108 1018 }
7b2bdb4b
KS
1019 if (pos[0] != '\0') {
1020 info("add symlink '%s'", pos);
1021 name_list_add(&udev->symlink_list, pos, 0);
1022 }
97ed02ee 1023 }
1024
821d0ec8 1025 /* set name, later rules with name set will be ignored */
d59c84ef
KS
1026 if (rule->name.operation == KEY_OP_ASSIGN ||
1027 rule->name.operation == KEY_OP_ASSIGN_FINAL ||
1028 rule->name.operation == KEY_OP_ADD) {
764ce7f2 1029 int count;
2c027162 1030
613ffbeb 1031 name_set = 1;
8bd41f36 1032 strlcpy(udev->name, key_val(rule, &rule->name), sizeof(udev->name));
a9bd2ed8 1033 udev_rules_apply_format(udev, udev->name, sizeof(udev->name));
764ce7f2
KS
1034 count = replace_untrusted_chars(udev->name);
1035 if (count)
1036 info("%i untrusted character(s) replaced", count);
8bd41f36 1037
1aa1e248
KS
1038 info("rule applied, '%s' becomes '%s'", udev->dev->kernel_name, udev->name);
1039 if (strcmp(udev->dev->subsystem, "net") != 0)
8bd41f36
KS
1040 dbg("name, '%s' is going to have owner='%s', group='%s', mode=%#o partitions=%i",
1041 udev->name, udev->owner, udev->group, udev->mode, udev->partitions);
821d0ec8 1042 }
5f72c470 1043
8bd41f36 1044 if (!udev->run_final && rule->run.operation != KEY_OP_UNSET) {
8bd41f36 1045 if (rule->run.operation == KEY_OP_ASSIGN_FINAL)
c974742b 1046 udev->run_final = 1;
8bd41f36 1047 if (rule->run.operation == KEY_OP_ASSIGN || rule->run.operation == KEY_OP_ASSIGN_FINAL) {
c07669bd 1048 info("reset run list");
fb179207 1049 name_list_cleanup(&udev->run_list);
995aec87 1050 }
2c027162
KS
1051 dbg("add run '%s'", key_val(rule, &rule->run));
1052 name_list_add(&udev->run_list, key_val(rule, &rule->run), 0);
97ed02ee 1053 }
3b6ed8bb
KS
1054
1055 if (rule->last_rule) {
1056 dbg("last rule to be applied");
1057 break;
1058 }
594dd610
KS
1059
1060 if (rule->goto_label.operation != KEY_OP_UNSET) {
1061 dbg("moving forward to label '%s'", key_val(rule, &rule->goto_label));
1062 udev_rules_iter_label(rules, key_val(rule, &rule->goto_label));
1063 }
724257d9 1064 }
ac28b86d 1065 }
c2405f50 1066
613ffbeb 1067 if (!name_set) {
1aa1e248 1068 strlcpy(udev->name, udev->dev->kernel_name, sizeof(udev->name));
4937afa4 1069 info("no node name set, will use kernel name '%s'", udev->name);
b821330f 1070 }
7bd22a78 1071
c1ab0461
KS
1072 if (udev->tmp_node[0] != '\0') {
1073 dbg("removing temporary device node");
1074 unlink_secure(udev->tmp_node);
1075 udev->tmp_node[0] = '\0';
1076 }
1077
120d45d0 1078 return 0;
185a35a4 1079}
821d0ec8 1080
1aa1e248 1081int udev_rules_get_run(struct udev_rules *rules, struct udevice *udev)
821d0ec8
KS
1082{
1083 struct udev_rule *rule;
821d0ec8 1084
1aa1e248 1085 dbg("udev->kernel_name='%s'", udev->dev->kernel_name);
761e5b47 1086
821d0ec8 1087 /* look for a matching rule to apply */
8bd41f36 1088 udev_rules_iter_init(rules);
6bf0ffe8 1089 while (1) {
8bd41f36 1090 rule = udev_rules_iter_next(rules);
6bf0ffe8
KS
1091 if (rule == NULL)
1092 break;
821d0ec8 1093
6bf0ffe8 1094 dbg("process rule");
8bd41f36
KS
1095 if (rule->name.operation != KEY_OP_UNSET || rule->symlink.operation != KEY_OP_UNSET ||
1096 rule->mode_operation != KEY_OP_UNSET || rule->owner.operation != KEY_OP_UNSET || rule->group.operation != KEY_OP_UNSET) {
821d0ec8
KS
1097 dbg("skip rule that names a device");
1098 continue;
1099 }
1100
1aa1e248 1101 if (match_rule(udev, rule) == 0) {
c07669bd 1102 if (rule->ignore_device) {
1aa1e248 1103 info("rule applied, '%s' is ignored", udev->dev->kernel_name);
c07669bd
KS
1104 udev->ignore_device = 1;
1105 return 0;
821d0ec8 1106 }
821d0ec8 1107
8bd41f36 1108 if (!udev->run_final && rule->run.operation != KEY_OP_UNSET) {
8bd41f36 1109 if (rule->run.operation == KEY_OP_ASSIGN || rule->run.operation == KEY_OP_ASSIGN_FINAL) {
c07669bd 1110 info("reset run list");
fb179207 1111 name_list_cleanup(&udev->run_list);
821d0ec8 1112 }
2c027162
KS
1113 dbg("add run '%s'", key_val(rule, &rule->run));
1114 name_list_add(&udev->run_list, key_val(rule, &rule->run), 0);
8bd41f36 1115 if (rule->run.operation == KEY_OP_ASSIGN_FINAL)
c07669bd 1116 break;
821d0ec8 1117 }
821d0ec8 1118
c07669bd
KS
1119 if (rule->last_rule) {
1120 dbg("last rule to be applied");
1121 break;
1122 }
761e5b47
KS
1123
1124 if (rule->goto_label.operation != KEY_OP_UNSET) {
1125 dbg("moving forward to label '%s'", key_val(rule, &rule->goto_label));
1126 udev_rules_iter_label(rules, key_val(rule, &rule->goto_label));
1127 }
821d0ec8
KS
1128 }
1129 }
1130
1131 return 0;
1132}