]>
Commit | Line | Data |
---|---|---|
2232cac8 | 1 | /* |
e5e322bc | 2 | * udev_rules.c |
2232cac8 GKH |
3 | * |
4 | * Userspace devfs | |
5 | * | |
6 | * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com> | |
fd9efc00 | 7 | * Copyright (C) 2003-2005 Kay Sievers <kay.sievers@vrfy.org> |
2232cac8 GKH |
8 | * |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify it | |
11 | * under the terms of the GNU General Public License as published by the | |
12 | * Free Software Foundation version 2 of the License. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, but | |
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License along | |
20 | * with this program; if not, write to the Free Software Foundation, Inc., | |
21 | * 675 Mass Ave, Cambridge, MA 02139, USA. | |
22 | * | |
23 | */ | |
24 | ||
25 | #include <stddef.h> | |
26 | #include <stdlib.h> | |
27 | #include <string.h> | |
28 | #include <stdio.h> | |
29 | #include <fcntl.h> | |
30 | #include <ctype.h> | |
31 | #include <unistd.h> | |
32 | #include <errno.h> | |
c27e6911 | 33 | #include <sys/wait.h> |
dac056aa | 34 | #include <sys/stat.h> |
2232cac8 | 35 | |
c80da508 | 36 | #include "libsysfs/sysfs/libsysfs.h" |
2232cac8 | 37 | #include "list.h" |
63f61c5c | 38 | #include "udev_libc_wrapper.h" |
2232cac8 | 39 | #include "udev.h" |
9af5bb2f | 40 | #include "udev_utils.h" |
2232cac8 | 41 | #include "udev_version.h" |
54988802 | 42 | #include "logging.h" |
e5e322bc | 43 | #include "udev_rules.h" |
02fa9ae5 | 44 | #include "udev_db.h" |
2232cac8 | 45 | |
a27cd06c KS |
46 | static struct sysfs_attribute *find_sysfs_attribute(struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device, char *attr); |
47 | ||
9f1da361 KS |
48 | /* compare string with pattern (supports * ? [0-9] [!A-Z]) */ |
49 | static int strcmp_pattern(const char *p, const char *s) | |
c124eafa | 50 | { |
8a08e4b1 KS |
51 | if (s[0] == '\0') { |
52 | while (p[0] == '*') | |
9f1da361 | 53 | p++; |
8a08e4b1 | 54 | return (p[0] != '\0'); |
9f1da361 | 55 | } |
8a08e4b1 | 56 | switch (p[0]) { |
9f1da361 KS |
57 | case '[': |
58 | { | |
59 | int not = 0; | |
60 | p++; | |
8a08e4b1 | 61 | if (p[0] == '!') { |
9f1da361 KS |
62 | not = 1; |
63 | p++; | |
64 | } | |
8a08e4b1 | 65 | while ((p[0] != '\0') && (p[0] != ']')) { |
9f1da361 KS |
66 | int match = 0; |
67 | if (p[1] == '-') { | |
8a08e4b1 | 68 | if ((s[0] >= p[0]) && (s[0] <= p[2])) |
9f1da361 KS |
69 | match = 1; |
70 | p += 3; | |
71 | } else { | |
8a08e4b1 | 72 | match = (p[0] == s[0]); |
9f1da361 KS |
73 | p++; |
74 | } | |
75 | if (match ^ not) { | |
8a08e4b1 | 76 | while ((p[0] != '\0') && (p[0] != ']')) |
9f1da361 | 77 | p++; |
8a08e4b1 KS |
78 | if (p[0] == ']') |
79 | return strcmp_pattern(p+1, s+1); | |
9f1da361 KS |
80 | } |
81 | } | |
82 | } | |
83 | break; | |
84 | case '*': | |
85 | if (strcmp_pattern(p, s+1)) | |
86 | return strcmp_pattern(p+1, s); | |
87 | return 0; | |
88 | case '\0': | |
8a08e4b1 | 89 | if (s[0] == '\0') { |
9f1da361 KS |
90 | return 0; |
91 | } | |
92 | break; | |
93 | default: | |
8a08e4b1 | 94 | if ((p[0] == s[0]) || (p[0] == '?')) |
9f1da361 KS |
95 | return strcmp_pattern(p+1, s+1); |
96 | break; | |
97 | } | |
98 | return 1; | |
c124eafa KS |
99 | } |
100 | ||
a27cd06c KS |
101 | /* extract possible {attr} and move str behind it */ |
102 | static char *get_format_attribute(char **str) | |
103 | { | |
104 | char *pos; | |
105 | char *attr = NULL; | |
106 | ||
107 | if (*str[0] == '{') { | |
108 | pos = strchr(*str, '}'); | |
109 | if (pos == NULL) { | |
110 | dbg("missing closing brace for format"); | |
111 | return NULL; | |
112 | } | |
113 | pos[0] = '\0'; | |
114 | attr = *str+1; | |
115 | *str = pos+1; | |
116 | dbg("attribute='%s', str='%s'", attr, *str); | |
117 | } | |
118 | return attr; | |
119 | } | |
120 | ||
121 | /* extract possible format length and move str behind it*/ | |
122 | static int get_format_len(char **str) | |
123 | { | |
124 | int num; | |
125 | char *tail; | |
126 | ||
127 | if (isdigit(*str[0])) { | |
128 | num = (int) strtoul(*str, &tail, 10); | |
aebef544 | 129 | if (num > 0) { |
a27cd06c KS |
130 | *str = tail; |
131 | dbg("format length=%i", num); | |
132 | return num; | |
133 | } else { | |
134 | dbg("format parsing error '%s'", *str); | |
135 | } | |
136 | } | |
137 | return -1; | |
138 | } | |
139 | ||
0a8dd7f3 DZ |
140 | /** Finds the lowest positive N such that <name>N isn't present in |
141 | * $(udevroot) either as a file or a symlink. | |
142 | * | |
143 | * @param name Name to check for | |
144 | * @return 0 if <name> didn't exist and N otherwise. | |
145 | */ | |
2b41e68a | 146 | static int find_free_number(struct udevice *udev, const char *name) |
0a8dd7f3 | 147 | { |
63f61c5c KS |
148 | char devpath[PATH_SIZE]; |
149 | char filename[PATH_SIZE]; | |
2b41e68a | 150 | int num = 0; |
0a8dd7f3 | 151 | |
63f61c5c | 152 | strlcpy(filename, name, sizeof(filename)); |
0a8dd7f3 | 153 | while (1) { |
2b41e68a | 154 | dbg("look for existing node '%s'", filename); |
63f61c5c | 155 | if (udev_db_search_name(devpath, sizeof(devpath), filename) != 0) { |
2b41e68a KS |
156 | dbg("free num=%d", num); |
157 | return num; | |
158 | } | |
0a8dd7f3 | 159 | |
2b41e68a KS |
160 | num++; |
161 | if (num > 1000) { | |
162 | info("find_free_number gone crazy (num=%d), aborted", num); | |
163 | return -1; | |
164 | } | |
63f61c5c KS |
165 | snprintf(filename, sizeof(filename), "%s%d", name, num); |
166 | filename[sizeof(filename)-1] = '\0'; | |
2b41e68a | 167 | } |
0a8dd7f3 DZ |
168 | } |
169 | ||
831f800d | 170 | static void apply_format(struct udevice *udev, char *string, size_t maxsize, |
b821330f | 171 | struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device) |
f3b04a2e | 172 | { |
63f61c5c KS |
173 | char temp[PATH_SIZE]; |
174 | char temp2[PATH_SIZE]; | |
03fd7a3a | 175 | char *tail, *pos, *cpos, *attr, *rest; |
63ead27c | 176 | int len; |
88f09368 | 177 | int i; |
a27cd06c KS |
178 | char c; |
179 | struct sysfs_attribute *tmpattr; | |
0a8dd7f3 | 180 | unsigned int next_free_number; |
69aa6dfb | 181 | struct sysfs_class_device *class_dev_parent; |
f3b04a2e | 182 | |
8ffb636f | 183 | pos = string; |
f3b04a2e | 184 | while (1) { |
a36a3c3a KS |
185 | pos = strchr(pos, '%'); |
186 | if (pos == NULL) | |
a27cd06c | 187 | break; |
b1c5e333 | 188 | |
a36a3c3a KS |
189 | pos[0] = '\0'; |
190 | tail = pos+1; | |
191 | len = get_format_len(&tail); | |
192 | c = tail[0]; | |
63f61c5c | 193 | strlcpy(temp, tail+1, sizeof(temp)); |
a36a3c3a KS |
194 | tail = temp; |
195 | dbg("format=%c, string='%s', tail='%s'",c , string, tail); | |
a27cd06c KS |
196 | attr = get_format_attribute(&tail); |
197 | ||
198 | switch (c) { | |
c1ab0461 | 199 | case 'p': |
63f61c5c | 200 | strlcat(string, udev->devpath, maxsize); |
c1ab0461 KS |
201 | dbg("substitute kernel name '%s'", udev->kernel_name); |
202 | break; | |
a27cd06c | 203 | case 'b': |
63f61c5c | 204 | strlcat(string, udev->bus_id, maxsize); |
a27cd06c KS |
205 | dbg("substitute bus_id '%s'", udev->bus_id); |
206 | break; | |
207 | case 'k': | |
63f61c5c | 208 | strlcat(string, udev->kernel_name, maxsize); |
a27cd06c KS |
209 | dbg("substitute kernel name '%s'", udev->kernel_name); |
210 | break; | |
211 | case 'n': | |
63f61c5c | 212 | strlcat(string, udev->kernel_number, maxsize); |
a27cd06c | 213 | dbg("substitute kernel number '%s'", udev->kernel_number); |
f3b04a2e | 214 | break; |
a27cd06c | 215 | case 'm': |
03fd7a3a | 216 | sprintf(temp2, "%d", minor(udev->devt)); |
63f61c5c | 217 | strlcat(string, temp2, maxsize); |
03fd7a3a | 218 | dbg("substitute minor number '%s'", temp2); |
a27cd06c | 219 | break; |
c58e36c0 | 220 | case 'M': |
03fd7a3a | 221 | sprintf(temp2, "%d", major(udev->devt)); |
63f61c5c | 222 | strlcat(string, temp2, maxsize); |
03fd7a3a | 223 | dbg("substitute major number '%s'", temp2); |
a27cd06c KS |
224 | break; |
225 | case 'c': | |
b821330f | 226 | if (udev->program_result[0] == '\0') |
f3b04a2e | 227 | break; |
88f09368 | 228 | /* get part part of the result string */ |
63ead27c | 229 | i = 0; |
88f09368 | 230 | if (attr != NULL) |
558f80ba | 231 | i = strtoul(attr, &rest, 10); |
88f09368 | 232 | if (i > 0) { |
03fd7a3a KS |
233 | dbg("request part #%d of result string", i); |
234 | cpos = udev->program_result; | |
235 | while (--i) { | |
236 | while (cpos[0] != '\0' && !isspace(cpos[0])) | |
237 | cpos++; | |
238 | while (isspace(cpos[0])) | |
239 | cpos++; | |
b1c5e333 | 240 | } |
9fe3f9a9 KS |
241 | if (i > 0) { |
242 | dbg("requested part of result string not found"); | |
243 | break; | |
a27cd06c | 244 | } |
63f61c5c | 245 | strlcpy(temp2, cpos, sizeof(temp2)); |
03fd7a3a KS |
246 | /* %{2+}c copies the whole string from the second part on */ |
247 | if (rest[0] != '+') { | |
248 | cpos = strchr(temp2, ' '); | |
249 | if (cpos) | |
250 | cpos[0] = '\0'; | |
251 | } | |
63f61c5c | 252 | strlcat(string, temp2, maxsize); |
27c3403d | 253 | dbg("substitute part of result string '%s'", temp2); |
a27cd06c | 254 | } else { |
63f61c5c | 255 | strlcat(string, udev->program_result, maxsize); |
a27cd06c | 256 | dbg("substitute result string '%s'", udev->program_result); |
f3b04a2e | 257 | } |
f3b04a2e | 258 | break; |
a27cd06c | 259 | case 's': |
5e39f90b KS |
260 | if (!class_dev) |
261 | break; | |
262 | if (attr == NULL) { | |
a27cd06c | 263 | dbg("missing attribute"); |
5e39f90b KS |
264 | break; |
265 | } | |
266 | tmpattr = find_sysfs_attribute(class_dev, sysfs_device, attr); | |
267 | if (tmpattr == NULL) { | |
268 | dbg("sysfa attribute '%s' not found", attr); | |
269 | break; | |
270 | } | |
271 | /* strip trailing whitespace of matching value */ | |
272 | if (isspace(tmpattr->value[strlen(tmpattr->value)-1])) { | |
273 | i = len = strlen(tmpattr->value); | |
274 | while (i > 0 && isspace(tmpattr->value[i-1])) | |
275 | i--; | |
276 | if (i < len) { | |
277 | tmpattr->value[i] = '\0'; | |
278 | dbg("remove %i trailing whitespace chars from '%s'", | |
279 | len - i, tmpattr->value); | |
280 | } | |
a27cd06c | 281 | } |
63f61c5c | 282 | strlcat(string, tmpattr->value, maxsize); |
5e39f90b | 283 | dbg("substitute sysfs value '%s'", tmpattr->value); |
a27cd06c KS |
284 | break; |
285 | case '%': | |
63f61c5c | 286 | strlcat(string, "%", maxsize); |
a36a3c3a | 287 | pos++; |
a27cd06c | 288 | break; |
0a8dd7f3 DZ |
289 | case 'e': |
290 | next_free_number = find_free_number(udev, string); | |
291 | if (next_free_number > 0) { | |
2b41e68a | 292 | sprintf(temp2, "%d", next_free_number); |
63f61c5c | 293 | strlcat(string, temp2, maxsize); |
0a8dd7f3 DZ |
294 | } |
295 | break; | |
69aa6dfb | 296 | case 'P': |
8f2f6e42 KS |
297 | if (!class_dev) |
298 | break; | |
69aa6dfb KS |
299 | class_dev_parent = sysfs_get_classdev_parent(class_dev); |
300 | if (class_dev_parent != NULL) { | |
301 | struct udevice udev_parent; | |
302 | ||
303 | dbg("found parent '%s', get the node name", class_dev_parent->path); | |
e48fc108 | 304 | udev_init_device(&udev_parent, NULL, NULL); |
69aa6dfb | 305 | /* lookup the name in the udev_db with the DEVPATH of the parent */ |
e48fc108 | 306 | if (udev_db_get_device(&udev_parent, &class_dev_parent->path[strlen(sysfs_path)]) == 0) { |
63f61c5c | 307 | strlcat(string, udev_parent.name, maxsize); |
69aa6dfb KS |
308 | dbg("substitute parent node name'%s'", udev_parent.name); |
309 | } else | |
310 | dbg("parent not found in database"); | |
e48fc108 | 311 | udev_cleanup_device(&udev_parent); |
69aa6dfb KS |
312 | } |
313 | break; | |
c1ab0461 KS |
314 | case 'N': |
315 | if (udev->tmp_node[0] == '\0') { | |
316 | dbg("create temporary device node for callout"); | |
63f61c5c KS |
317 | snprintf(udev->tmp_node, sizeof(udev->tmp_node), "%s/.tmp-%u-%u", |
318 | udev_root, major(udev->devt), minor(udev->devt)); | |
319 | udev->tmp_node[sizeof(udev->tmp_node)-1] = '\0'; | |
7e720bd4 | 320 | udev_make_node(udev, udev->tmp_node, udev->devt, 0600, 0, 0); |
c1ab0461 | 321 | } |
63f61c5c | 322 | strlcat(string, udev->tmp_node, maxsize); |
c1ab0461 KS |
323 | dbg("substitute temporary device node name '%s'", udev->tmp_node); |
324 | break; | |
69aa6dfb | 325 | case 'r': |
63f61c5c | 326 | strlcat(string, udev_root, maxsize); |
69aa6dfb KS |
327 | dbg("substitute udev_root '%s'", udev_root); |
328 | break; | |
a27cd06c KS |
329 | default: |
330 | dbg("unknown substitution type '%%%c'", c); | |
331 | break; | |
332 | } | |
63ead27c KS |
333 | /* truncate to specified length */ |
334 | if (len > 0) | |
335 | pos[len] = '\0'; | |
336 | ||
63f61c5c | 337 | strlcat(string, tail, maxsize); |
f3b04a2e GKH |
338 | } |
339 | } | |
340 | ||
af4b05d4 | 341 | static int execute_program(struct udevice *udev, const char *path, char *value, int len) |
c27e6911 PM |
342 | { |
343 | int retval; | |
35b38379 | 344 | int count; |
c27e6911 PM |
345 | int status; |
346 | int fds[2]; | |
347 | pid_t pid; | |
20524642 | 348 | char *pos; |
63f61c5c KS |
349 | char arg[PATH_SIZE]; |
350 | char *argv[(sizeof(arg) / 2) + 1]; | |
bc434511 | 351 | int i; |
c27e6911 | 352 | |
63f61c5c | 353 | strlcpy(arg, path, sizeof(arg)); |
35b38379 KS |
354 | i = 0; |
355 | if (strchr(path, ' ')) { | |
35b38379 KS |
356 | pos = arg; |
357 | while (pos != NULL) { | |
358 | if (pos[0] == '\'') { | |
359 | /* don't separate if in apostrophes */ | |
360 | pos++; | |
361 | argv[i] = strsep(&pos, "\'"); | |
a75e2c14 | 362 | while (pos && pos[0] == ' ') |
35b38379 | 363 | pos++; |
a75e2c14 | 364 | } else { |
35b38379 KS |
365 | argv[i] = strsep(&pos, " "); |
366 | } | |
367 | dbg("arg[%i] '%s'", i, argv[i]); | |
368 | i++; | |
369 | } | |
f608f8ac KS |
370 | argv[i] = NULL; |
371 | dbg("execute '%s' with parsed arguments", arg); | |
372 | } else { | |
373 | argv[0] = arg; | |
af4b05d4 | 374 | argv[1] = udev->subsystem; |
f608f8ac KS |
375 | argv[2] = NULL; |
376 | dbg("execute '%s' with subsystem '%s' argument", arg, argv[1]); | |
35b38379 | 377 | } |
f608f8ac | 378 | |
c27e6911 PM |
379 | retval = pipe(fds); |
380 | if (retval != 0) { | |
381 | dbg("pipe failed"); | |
382 | return -1; | |
383 | } | |
35b38379 | 384 | |
c27e6911 | 385 | pid = fork(); |
2a25816f KS |
386 | switch(pid) { |
387 | case 0: | |
8f43a65e | 388 | /* child */ |
6e3e3c34 HH |
389 | /* dup2 write side of pipe to STDOUT */ |
390 | dup2(fds[1], STDOUT_FILENO); | |
f608f8ac | 391 | retval = execv(arg, argv); |
35b38379 | 392 | |
dde05ccb | 393 | info(FIELD_PROGRAM " execution of '%s' failed", path); |
2a25816f KS |
394 | exit(1); |
395 | case -1: | |
396 | dbg("fork failed"); | |
397 | return -1; | |
398 | default: | |
8f43a65e | 399 | /* parent reads from fds[0] */ |
c27e6911 PM |
400 | close(fds[1]); |
401 | retval = 0; | |
35b38379 | 402 | i = 0; |
c27e6911 | 403 | while (1) { |
35b38379 KS |
404 | count = read(fds[0], value + i, len - i-1); |
405 | if (count <= 0) | |
c27e6911 | 406 | break; |
35b38379 KS |
407 | |
408 | i += count; | |
409 | if (i >= len-1) { | |
ac28b86d | 410 | dbg("result len %d too short", len); |
c27e6911 | 411 | retval = -1; |
35b38379 | 412 | break; |
c27e6911 PM |
413 | } |
414 | } | |
35b38379 KS |
415 | |
416 | if (count < 0) { | |
417 | dbg("read failed with '%s'", strerror(errno)); | |
c27e6911 PM |
418 | retval = -1; |
419 | } | |
420 | ||
bbbe503e | 421 | if (i > 0 && value[i-1] == '\n') |
35b38379 KS |
422 | i--; |
423 | value[i] = '\0'; | |
424 | dbg("result is '%s'", value); | |
425 | ||
426 | close(fds[0]); | |
e920fed3 | 427 | waitpid(pid, &status, 0); |
35b38379 | 428 | |
c27e6911 | 429 | if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) { |
ac28b86d | 430 | dbg("exec program status 0x%x", status); |
c27e6911 PM |
431 | retval = -1; |
432 | } | |
433 | } | |
434 | return retval; | |
435 | } | |
436 | ||
a27cd06c | 437 | static struct sysfs_attribute *find_sysfs_attribute(struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device, char *attr) |
ca1cc0fe GKH |
438 | { |
439 | struct sysfs_attribute *tmpattr = NULL; | |
a8b01705 GKH |
440 | char *c; |
441 | ||
a27cd06c | 442 | dbg("look for device attribute '%s'", attr); |
a8b01705 | 443 | /* try to find the attribute in the class device directory */ |
a27cd06c | 444 | tmpattr = sysfs_get_classdev_attr(class_dev, attr); |
a8b01705 | 445 | if (tmpattr) |
a27cd06c | 446 | goto attr_found; |
a8b01705 GKH |
447 | |
448 | /* look in the class device directory if present */ | |
449 | if (sysfs_device) { | |
a27cd06c | 450 | tmpattr = sysfs_get_device_attr(sysfs_device, attr); |
a8b01705 | 451 | if (tmpattr) |
a27cd06c | 452 | goto attr_found; |
a8b01705 | 453 | } |
a8b01705 | 454 | |
a27cd06c KS |
455 | return NULL; |
456 | ||
457 | attr_found: | |
458 | c = strchr(tmpattr->value, '\n'); | |
459 | if (c != NULL) | |
460 | c[0] = '\0'; | |
461 | ||
462 | dbg("found attribute '%s'", tmpattr->path); | |
463 | return tmpattr; | |
464 | } | |
465 | ||
466 | static int compare_sysfs_attribute(struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device, struct sysfs_pair *pair) | |
467 | { | |
468 | struct sysfs_attribute *tmpattr; | |
d5f91372 KS |
469 | int i; |
470 | int len; | |
a27cd06c KS |
471 | |
472 | if ((pair == NULL) || (pair->file[0] == '\0') || (pair->value == '\0')) | |
473 | return -ENODEV; | |
474 | ||
475 | tmpattr = find_sysfs_attribute(class_dev, sysfs_device, pair->file); | |
476 | if (tmpattr == NULL) | |
477 | return -ENODEV; | |
478 | ||
d5f91372 KS |
479 | /* strip trailing whitespace of value, if not asked to match for it */ |
480 | if (! isspace(pair->value[strlen(pair->value)-1])) { | |
481 | i = len = strlen(tmpattr->value); | |
482 | while (i > 0 && isspace(tmpattr->value[i-1])) | |
483 | i--; | |
484 | if (i < len) { | |
485 | tmpattr->value[i] = '\0'; | |
486 | dbg("remove %i trailing whitespace chars from '%s'", | |
487 | len - i, tmpattr->value); | |
488 | } | |
489 | } | |
490 | ||
a8b01705 GKH |
491 | dbg("compare attribute '%s' value '%s' with '%s'", |
492 | pair->file, tmpattr->value, pair->value); | |
493 | if (strcmp_pattern(pair->value, tmpattr->value) != 0) | |
494 | return -ENODEV; | |
495 | ||
496 | dbg("found matching attribute '%s' with value '%s'", | |
497 | pair->file, pair->value); | |
498 | return 0; | |
499 | } | |
500 | ||
e5e322bc | 501 | static int match_sysfs_pairs(struct udev_rule *rule, struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device) |
a8b01705 GKH |
502 | { |
503 | struct sysfs_pair *pair; | |
a8b01705 | 504 | int i; |
137af0cc | 505 | |
ac28b86d | 506 | for (i = 0; i < MAX_SYSFS_PAIRS; ++i) { |
e5e322bc | 507 | pair = &rule->sysfs_pair[i]; |
ac28b86d KS |
508 | if ((pair->file[0] == '\0') || (pair->value[0] == '\0')) |
509 | break; | |
510 | if (compare_sysfs_attribute(class_dev, sysfs_device, pair) != 0) { | |
511 | dbg("sysfs attribute doesn't match"); | |
512 | return -ENODEV; | |
ca1cc0fe | 513 | } |
ca1cc0fe | 514 | } |
ac28b86d KS |
515 | |
516 | return 0; | |
ca1cc0fe GKH |
517 | } |
518 | ||
e5e322bc | 519 | static int match_id(struct udev_rule *rule, struct sysfs_device *sysfs_device) |
ca1cc0fe | 520 | { |
63f61c5c | 521 | char path[PATH_SIZE]; |
5e39f90b | 522 | char *temp; |
ca1cc0fe | 523 | |
63f61c5c | 524 | strlcpy(path, sysfs_device->path, sizeof(path)); |
ac28b86d | 525 | temp = strrchr(path, '/'); |
3a8c51a7 | 526 | temp++; |
e5e322bc KS |
527 | dbg("search '%s' in '%s', path='%s'", rule->id, temp, path); |
528 | if (strcmp_pattern(rule->id, temp) != 0) | |
ac28b86d | 529 | return -ENODEV; |
9ed47a9f KS |
530 | |
531 | return 0; | |
ca1cc0fe GKH |
532 | } |
533 | ||
e5e322bc | 534 | static int match_rule(struct udevice *udev, struct udev_rule *rule, |
5f72c470 | 535 | struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device) |
dac056aa | 536 | { |
e5e322bc KS |
537 | if (rule->kernel[0] != '\0') { |
538 | dbg("check for " FIELD_KERNEL " rule->kernel='%s' class_dev->name='%s'", | |
539 | rule->kernel, class_dev->name); | |
540 | if (strcmp_pattern(rule->kernel, class_dev->name) != 0) { | |
03a9875b KS |
541 | dbg(FIELD_KERNEL " is not matching"); |
542 | goto exit; | |
ac28b86d | 543 | } |
03a9875b KS |
544 | dbg(FIELD_KERNEL " matches"); |
545 | } | |
ca1cc0fe | 546 | |
e5e322bc KS |
547 | if (rule->subsystem[0] != '\0') { |
548 | dbg("check for " FIELD_SUBSYSTEM " rule->subsystem='%s' class_dev->name='%s'", | |
549 | rule->subsystem, class_dev->name); | |
550 | if (strcmp_pattern(rule->subsystem, udev->subsystem) != 0) { | |
03a9875b KS |
551 | dbg(FIELD_SUBSYSTEM " is not matching"); |
552 | goto exit; | |
6818c51d | 553 | } |
03a9875b KS |
554 | dbg(FIELD_SUBSYSTEM " matches"); |
555 | } | |
6818c51d | 556 | |
e57e7bc1 | 557 | /* walk up the chain of physical devices and find a match */ |
03a9875b | 558 | while (1) { |
2092fbcd | 559 | /* check for matching driver */ |
e5e322bc | 560 | if (rule->driver[0] != '\0') { |
2b40452a KS |
561 | if (sysfs_device == NULL) { |
562 | dbg("device has no sysfs_device"); | |
563 | goto try_parent; | |
564 | } | |
e5e322bc KS |
565 | dbg("check for " FIELD_DRIVER " rule->driver='%s' sysfs_device->driver_name='%s'", |
566 | rule->driver, sysfs_device->driver_name); | |
567 | if (strcmp_pattern(rule->driver, sysfs_device->driver_name) != 0) { | |
2092fbcd KS |
568 | dbg(FIELD_DRIVER " is not matching"); |
569 | goto try_parent; | |
2092fbcd | 570 | } |
e57e7bc1 | 571 | dbg(FIELD_DRIVER " matches"); |
2092fbcd KS |
572 | } |
573 | ||
82b16983 | 574 | /* check for matching bus value */ |
e5e322bc | 575 | if (rule->bus[0] != '\0') { |
82b16983 | 576 | if (sysfs_device == NULL) { |
2b40452a | 577 | dbg("device has no sysfs_device"); |
82b16983 KS |
578 | goto try_parent; |
579 | } | |
e5e322bc KS |
580 | dbg("check for " FIELD_BUS " rule->bus='%s' sysfs_device->bus='%s'", |
581 | rule->bus, sysfs_device->bus); | |
582 | if (strcmp_pattern(rule->bus, sysfs_device->bus) != 0) { | |
82b16983 KS |
583 | dbg(FIELD_BUS " is not matching"); |
584 | goto try_parent; | |
585 | } | |
586 | dbg(FIELD_BUS " matches"); | |
587 | } | |
588 | ||
ac28b86d | 589 | /* check for matching bus id */ |
e5e322bc | 590 | if (rule->id[0] != '\0') { |
2b40452a KS |
591 | if (sysfs_device == NULL) { |
592 | dbg("device has no sysfs_device"); | |
593 | goto try_parent; | |
594 | } | |
ac28b86d | 595 | dbg("check " FIELD_ID); |
e5e322bc | 596 | if (match_id(rule, sysfs_device) != 0) { |
ac28b86d | 597 | dbg(FIELD_ID " is not matching"); |
c5118442 | 598 | goto try_parent; |
ac28b86d | 599 | } |
e57e7bc1 | 600 | dbg(FIELD_ID " matches"); |
ac28b86d | 601 | } |
ca1cc0fe | 602 | |
ac28b86d | 603 | /* check for matching sysfs pairs */ |
e5e322bc | 604 | if (rule->sysfs_pair[0].file[0] != '\0') { |
ac28b86d | 605 | dbg("check " FIELD_SYSFS " pairs"); |
e5e322bc | 606 | if (match_sysfs_pairs(rule, class_dev, sysfs_device) != 0) { |
ac28b86d | 607 | dbg(FIELD_SYSFS " is not matching"); |
c5118442 | 608 | goto try_parent; |
ac28b86d | 609 | } |
e57e7bc1 | 610 | dbg(FIELD_SYSFS " matches"); |
ac28b86d KS |
611 | } |
612 | ||
e57e7bc1 KS |
613 | /* found matching physical device */ |
614 | break; | |
c5118442 KS |
615 | try_parent: |
616 | dbg("try parent sysfs device"); | |
724257d9 GKH |
617 | sysfs_device = sysfs_get_device_parent(sysfs_device); |
618 | if (sysfs_device == NULL) | |
82b16983 | 619 | goto exit; |
724257d9 GKH |
620 | dbg("sysfs_device->path='%s'", sysfs_device->path); |
621 | dbg("sysfs_device->bus_id='%s'", sysfs_device->bus_id); | |
724257d9 | 622 | } |
82b16983 | 623 | |
e57e7bc1 | 624 | /* execute external program */ |
e5e322bc | 625 | if (rule->program[0] != '\0') { |
63f61c5c | 626 | char program[PATH_SIZE]; |
e57e7bc1 KS |
627 | |
628 | dbg("check " FIELD_PROGRAM); | |
e5e322bc | 629 | strlcpy(program, rule->program, sizeof(program)); |
e57e7bc1 | 630 | apply_format(udev, program, sizeof(program), class_dev, sysfs_device); |
63f61c5c | 631 | if (execute_program(udev, program, udev->program_result, sizeof(udev->program_result)) != 0) { |
e57e7bc1 KS |
632 | dbg(FIELD_PROGRAM " returned nonzero"); |
633 | goto try_parent; | |
634 | } | |
635 | dbg(FIELD_PROGRAM " returned successful"); | |
636 | } | |
637 | ||
638 | /* check for matching result of external program */ | |
e5e322bc KS |
639 | if (rule->result[0] != '\0') { |
640 | dbg("check for " FIELD_RESULT "rule->result='%s', udev->program_result='%s'", | |
641 | rule->result, udev->program_result); | |
642 | if (strcmp_pattern(rule->result, udev->program_result) != 0) { | |
e57e7bc1 KS |
643 | dbg(FIELD_RESULT " is not matching"); |
644 | goto try_parent; | |
645 | } | |
646 | dbg(FIELD_RESULT " matches"); | |
647 | } | |
648 | ||
649 | /* rule matches */ | |
650 | return 0; | |
651 | ||
82b16983 KS |
652 | exit: |
653 | return -1; | |
724257d9 GKH |
654 | } |
655 | ||
e5e322bc | 656 | int udev_rules_get_name(struct udevice *udev, struct sysfs_class_device *class_dev) |
724257d9 | 657 | { |
707680b1 | 658 | struct sysfs_class_device *class_dev_parent; |
724257d9 | 659 | struct sysfs_device *sysfs_device = NULL; |
e5e322bc | 660 | struct udev_rule *rule; |
724257d9 | 661 | |
707680b1 KS |
662 | dbg("class_dev->name='%s'", class_dev->name); |
663 | ||
664 | /* Figure out where the "device"-symlink is at. For char devices this will | |
665 | * always be in the class_dev->path. On block devices, only the main block | |
666 | * device will have the device symlink in it's path. All partition devices | |
667 | * need to look at the symlink in its parent directory. | |
668 | */ | |
669 | class_dev_parent = sysfs_get_classdev_parent(class_dev); | |
670 | if (class_dev_parent != NULL) { | |
671 | dbg("given class device has a parent, use this instead"); | |
672 | sysfs_device = sysfs_get_classdev_device(class_dev_parent); | |
673 | } else { | |
674 | sysfs_device = sysfs_get_classdev_device(class_dev); | |
675 | } | |
724257d9 | 676 | |
724257d9 | 677 | if (sysfs_device) { |
7a947ce5 | 678 | dbg("found devices device: path='%s', bus_id='%s', bus='%s'", |
707680b1 | 679 | sysfs_device->path, sysfs_device->bus_id, sysfs_device->bus); |
63f61c5c | 680 | strlcpy(udev->bus_id, sysfs_device->bus_id, sizeof(udev->bus_id)); |
724257d9 GKH |
681 | } |
682 | ||
9ed47a9f | 683 | dbg("udev->kernel_name='%s'", udev->kernel_name); |
724257d9 GKH |
684 | |
685 | /* look for a matching rule to apply */ | |
e5e322bc | 686 | list_for_each_entry(rule, &udev_rule_list, node) { |
724257d9 | 687 | dbg("process rule"); |
e5e322bc | 688 | if (match_rule(udev, rule, class_dev, sysfs_device) == 0) { |
e9390146 | 689 | |
fd9efc00 | 690 | /* apply options */ |
e5e322bc | 691 | if (rule->ignore_device) { |
fd9efc00 | 692 | info("configured rule in '%s[%i]' applied, '%s' is ignored", |
e5e322bc | 693 | rule->config_file, rule->config_line, udev->kernel_name); |
fd9efc00 KS |
694 | return -1; |
695 | } | |
e5e322bc | 696 | if (rule->ignore_remove) { |
b821330f | 697 | udev->ignore_remove = 1; |
e5e322bc | 698 | dbg("remove event should be ignored"); |
fd9efc00 KS |
699 | } |
700 | /* apply all_partitions option only at a main block device */ | |
e5e322bc KS |
701 | if (rule->partitions && udev->type == DEV_BLOCK && udev->kernel_number[0] == '\0') { |
702 | udev->partitions = rule->partitions; | |
fd9efc00 KS |
703 | dbg("creation of partition nodes requested"); |
704 | } | |
705 | ||
38875577 | 706 | /* apply permissions */ |
e5e322bc KS |
707 | if (rule->mode != 0000) { |
708 | udev->mode = rule->mode; | |
38875577 MB |
709 | dbg("applied mode=%#o to '%s'", udev->mode, udev->kernel_name); |
710 | } | |
e5e322bc KS |
711 | if (rule->owner[0] != '\0') { |
712 | strlcpy(udev->owner, rule->owner, sizeof(udev->owner)); | |
38875577 MB |
713 | apply_format(udev, udev->owner, sizeof(udev->owner), class_dev, sysfs_device); |
714 | dbg("applied owner='%s' to '%s'", udev->owner, udev->kernel_name); | |
715 | } | |
e5e322bc KS |
716 | if (rule->group[0] != '\0') { |
717 | strlcpy(udev->group, rule->group, sizeof(udev->group)); | |
38875577 MB |
718 | apply_format(udev, udev->group, sizeof(udev->group), class_dev, sysfs_device); |
719 | dbg("applied group='%s' to '%s'", udev->group, udev->kernel_name); | |
720 | } | |
721 | ||
9ed47a9f | 722 | /* collect symlinks */ |
e5e322bc | 723 | if (rule->symlink[0] != '\0') { |
63f61c5c | 724 | char temp[PATH_SIZE]; |
e48fc108 | 725 | char *pos, *next; |
ddd5b5dc | 726 | |
3908058c | 727 | info("configured rule in '%s[%i]' applied, added symlink '%s'", |
e5e322bc KS |
728 | rule->config_file, rule->config_line, rule->symlink); |
729 | strlcpy(temp, rule->symlink, sizeof(temp)); | |
ddd5b5dc | 730 | apply_format(udev, temp, sizeof(temp), class_dev, sysfs_device); |
e48fc108 KS |
731 | |
732 | /* add multiple symlinks separated by spaces */ | |
733 | pos = temp; | |
734 | next = strchr(temp, ' '); | |
735 | while (next) { | |
736 | next[0] = '\0'; | |
737 | dbg("add symlink '%s'", pos); | |
738 | name_list_add(&udev->symlink_list, pos, 0); | |
739 | pos = &next[1]; | |
740 | next = strchr(pos, ' '); | |
741 | } | |
742 | dbg("add symlink '%s'", pos); | |
743 | name_list_add(&udev->symlink_list, pos, 0); | |
97ed02ee | 744 | } |
745 | ||
5f72c470 | 746 | /* rule matches */ |
e5e322bc | 747 | if (rule->name[0] != '\0') { |
e9390146 | 748 | info("configured rule in '%s[%i]' applied, '%s' becomes '%s'", |
e5e322bc | 749 | rule->config_file, rule->config_line, udev->kernel_name, rule->name); |
5f72c470 | 750 | |
e5e322bc | 751 | strlcpy(udev->name, rule->name, sizeof(udev->name)); |
5f72c470 | 752 | apply_format(udev, udev->name, sizeof(udev->name), class_dev, sysfs_device); |
e5e322bc KS |
753 | strlcpy(udev->config_file, rule->config_file, sizeof(udev->config_file)); |
754 | udev->config_line = rule->config_line; | |
65ab1334 | 755 | |
e6764498 | 756 | if (udev->type != DEV_NET) |
fd9efc00 KS |
757 | dbg("name, '%s' is going to have owner='%s', group='%s', mode=%#o partitions=%i", |
758 | udev->name, udev->owner, udev->group, udev->mode, udev->partitions); | |
5f72c470 | 759 | |
b821330f | 760 | break; |
97ed02ee | 761 | } |
724257d9 | 762 | } |
ac28b86d | 763 | } |
c2405f50 | 764 | |
b821330f KS |
765 | if (udev->name[0] == '\0') { |
766 | /* no rule matched, so we use the kernel name */ | |
63f61c5c | 767 | strlcpy(udev->name, udev->kernel_name, sizeof(udev->name)); |
b821330f KS |
768 | dbg("no rule found, use kernel name '%s'", udev->name); |
769 | } | |
7bd22a78 | 770 | |
c1ab0461 KS |
771 | if (udev->tmp_node[0] != '\0') { |
772 | dbg("removing temporary device node"); | |
773 | unlink_secure(udev->tmp_node); | |
774 | udev->tmp_node[0] = '\0'; | |
775 | } | |
776 | ||
120d45d0 | 777 | return 0; |
185a35a4 | 778 | } |