chiark / gitweb /
prepare RELEASE-NOTES
[elogind.git] / udev_rules_parse.c
1 /*
2  * udev_rules_parse.c
3  *
4  * Userspace devfs
5  *
6  * Copyright (C) 2003,2004 Greg Kroah-Hartman <greg@kroah.com>
7  * Copyright (C) 2003-2005 Kay Sievers <kay.sievers@vrfy.org>
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 <ctype.h>
30 #include <unistd.h>
31 #include <sys/stat.h>
32 #include <errno.h>
33
34 #include "udev_libc_wrapper.h"
35 #include "udev.h"
36 #include "udev_utils.h"
37 #include "logging.h"
38 #include "udev_rules.h"
39
40 /* rules parsed from .rules files*/
41 static LIST_HEAD(rules_list);
42 static struct list_head *rules_list_current;
43
44 /* mapped compiled rules stored on disk */
45 static struct udev_rule *rules_array = NULL;
46 static size_t rules_array_current;
47 static size_t rules_array_size = 0;
48
49 static size_t rules_count = 0;
50
51 int udev_rules_iter_init(void)
52 {
53         rules_list_current = rules_list.next;
54         rules_array_current = 0;
55
56         return 0;
57 }
58
59 struct udev_rule *udev_rules_iter_next(void)
60 {
61         static struct udev_rule *rule;
62
63         if (rules_array) {
64                 if (rules_array_current >= rules_count)
65                         return NULL;
66                 rule = &rules_array[rules_array_current];
67                 rules_array_current++;
68         } else {
69                 dbg("head=%p current=%p next=%p", &rules_list, rules_list_current, rules_list_current->next);
70                 if (rules_list_current == &rules_list)
71                         return NULL;
72                 rule = list_entry(rules_list_current, struct udev_rule, node);
73                 rules_list_current = rules_list_current->next;
74         }
75         return rule;
76 }
77
78 static int add_rule_to_list(struct udev_rule *rule)
79 {
80         struct udev_rule *tmp_rule;
81
82         tmp_rule = malloc(sizeof(*tmp_rule));
83         if (tmp_rule == NULL)
84                 return -ENOMEM;
85         memcpy(tmp_rule, rule, sizeof(struct udev_rule));
86         list_add_tail(&tmp_rule->node, &rules_list);
87
88         dbg("name='%s', symlink='%s', bus='%s', id='%s', "
89             "sysfs_file[0]='%s', sysfs_value[0]='%s', "
90             "kernel='%s', program='%s', result='%s', "
91             "owner='%s', group='%s', mode=%#o, "
92             "all_partions=%u, ignore_remove=%u, ignore_device=%u, last_rule=%u",
93             rule->name, rule->symlink, rule->bus, rule->id,
94             rule->sysfs_pair[0].name, rule->sysfs_pair[0].value,
95             rule->kernel, rule->program, rule->result, rule->owner, rule->group, rule->mode,
96             rule->partitions, rule->ignore_remove, rule->ignore_device, rule->last_rule);
97
98         return 0;
99 }
100
101 static int get_key(char **line, char **key, enum key_operation *operation, char **value)
102 {
103         char *linepos;
104         char *temp;
105
106         linepos = *line;
107         if (!linepos)
108                 return -1;
109
110         /* skip whitespace */
111         while (isspace(linepos[0]) || linepos[0] == ',')
112                 linepos++;
113
114         /* get the key */
115         *key = linepos;
116         while (1) {
117                 linepos++;
118                 if (linepos[0] == '\0')
119                         return -1;
120                 if (isspace(linepos[0]))
121                         break;
122                 if (linepos[0] == '=')
123                         break;
124                 if (linepos[0] == '+')
125                         break;
126                 if (linepos[0] == '!')
127                         break;
128                 if (linepos[0] == ':')
129                         break;
130         }
131
132         /* remember end of key */
133         temp = linepos;
134
135         /* skip whitespace after key */
136         while (isspace(linepos[0]))
137                 linepos++;
138
139         /* get operation type */
140         if (linepos[0] == '=' && linepos[1] == '=') {
141                 *operation = KEY_OP_MATCH;
142                 linepos += 2;
143                 dbg("operator=match");
144         } else if (linepos[0] == '!' && linepos[1] == '=') {
145                 *operation = KEY_OP_NOMATCH;
146                 linepos += 2;
147                 dbg("operator=nomatch");
148         } else if (linepos[0] == '+' && linepos[1] == '=') {
149                 *operation = KEY_OP_ADD;
150                 linepos += 2;
151                 dbg("operator=add");
152         } else if (linepos[0] == '=') {
153                 *operation = KEY_OP_ASSIGN;
154                 linepos++;
155                 dbg("operator=assign");
156         } else if (linepos[0] == ':' && linepos[1] == '=') {
157                 *operation = KEY_OP_ASSIGN_FINAL;
158                 linepos += 2;
159                 dbg("operator=assign_final");
160         } else
161                 return -1;
162
163         /* terminate key */
164         temp[0] = '\0';
165         dbg("key='%s'", *key);
166
167         /* skip whitespace after operator */
168         while (isspace(linepos[0]))
169                 linepos++;
170
171         /* get the value*/
172         if (linepos[0] == '"')
173                 linepos++;
174         else
175                 return -1;
176         *value = linepos;
177
178         temp = strchr(linepos, '"');
179         if (!temp)
180                 return -1;
181         temp[0] = '\0';
182         temp++;
183         dbg("value='%s'", *value);
184
185         /* move line to next key */
186         *line = temp;
187
188         return 0;
189 }
190
191 /* extract possible KEY{attr} */
192 static char *get_key_attribute(char *str)
193 {
194         char *pos;
195         char *attr;
196
197         attr = strchr(str, '{');
198         if (attr != NULL) {
199                 attr++;
200                 pos = strchr(attr, '}');
201                 if (pos == NULL) {
202                         err("missing closing brace for format");
203                         return NULL;
204                 }
205                 pos[0] = '\0';
206                 dbg("attribute='%s'", attr);
207                 return attr;
208         }
209
210         return NULL;
211 }
212
213 static int rules_parse(const char *filename)
214 {
215         char line[LINE_SIZE];
216         char *bufline;
217         int lineno;
218         char *linepos;
219         char *attr;
220         char *buf;
221         size_t bufsize;
222         size_t cur;
223         size_t count;
224         int program_given = 0;
225         int valid;
226         int retval = 0;
227         struct udev_rule rule;
228
229         if (file_map(filename, &buf, &bufsize) != 0) {
230                 err("can't open '%s' as rules file", filename);
231                 return -1;
232         }
233         dbg("reading '%s' as rules file", filename);
234
235         /* loop through the whole file */
236         cur = 0;
237         lineno = 0;
238         while (cur < bufsize) {
239                 unsigned int i, j;
240
241                 count = buf_get_line(buf, bufsize, cur);
242                 bufline = &buf[cur];
243                 cur += count+1;
244                 lineno++;
245
246                 if (count >= sizeof(line)) {
247                         info("line too long, rule skipped %s, line %d", filename, lineno);
248                         continue;
249                 }
250
251                 /* eat the whitespace */
252                 while ((count > 0) && isspace(bufline[0])) {
253                         bufline++;
254                         count--;
255                 }
256                 if (count == 0)
257                         continue;
258
259                 /* see if this is a comment */
260                 if (bufline[0] == COMMENT_CHARACTER)
261                         continue;
262
263                 /* skip backslash and newline from multi line rules */
264                 for (i = j = 0; i < count; i++) {
265                         if (bufline[i] == '\\' && bufline[i+1] == '\n')
266                                 continue;
267
268                         line[j++] = bufline[i];
269                 }
270                 line[j] = '\0';
271                 dbg("read '%s'", line);
272
273                 /* get all known keys */
274                 memset(&rule, 0x00, sizeof(struct udev_rule));
275                 linepos = line;
276                 valid = 0;
277
278                 while (1) {
279                         char *key;
280                         char *value;
281                         enum key_operation operation = KEY_OP_UNSET;
282
283                         retval = get_key(&linepos, &key, &operation, &value);
284                         if (retval)
285                                 break;
286
287                         if (strcasecmp(key, KEY_KERNEL) == 0) {
288                                 strlcpy(rule.kernel, value, sizeof(rule.kernel));
289                                 rule.kernel_operation = operation;
290                                 valid = 1;
291                                 continue;
292                         }
293
294                         if (strcasecmp(key, KEY_SUBSYSTEM) == 0) {
295                                 strlcpy(rule.subsystem, value, sizeof(rule.subsystem));
296                                 rule.subsystem_operation = operation;
297                                 valid = 1;
298                                 continue;
299                         }
300
301                         if (strcasecmp(key, KEY_ACTION) == 0) {
302                                 strlcpy(rule.action, value, sizeof(rule.action));
303                                 rule.action_operation = operation;
304                                 valid = 1;
305                                 continue;
306                         }
307
308                         if (strcasecmp(key, KEY_DEVPATH) == 0) {
309                                 strlcpy(rule.devpath, value, sizeof(rule.devpath));
310                                 rule.devpath_operation = operation;
311                                 valid = 1;
312                                 continue;
313                         }
314
315                         if (strcasecmp(key, KEY_BUS) == 0) {
316                                 strlcpy(rule.bus, value, sizeof(rule.bus));
317                                 rule.bus_operation = operation;
318                                 valid = 1;
319                                 continue;
320                         }
321
322                         if (strcasecmp(key, KEY_ID) == 0) {
323                                 strlcpy(rule.id, value, sizeof(rule.id));
324                                 rule.id_operation = operation;
325                                 valid = 1;
326                                 continue;
327                         }
328
329                         if (strncasecmp(key, KEY_SYSFS, sizeof(KEY_SYSFS)-1) == 0) {
330                                 struct key_pair *pair;
331
332                                 if (rule.sysfs_pair_count >= KEY_SYSFS_PAIRS_MAX) {
333                                         err("skip rule, to many " KEY_SYSFS " keys in a single rule");
334                                         goto error;
335                                 }
336                                 pair = &rule.sysfs_pair[rule.sysfs_pair_count];
337                                 attr = get_key_attribute(key + sizeof(KEY_SYSFS)-1);
338                                 if (attr == NULL) {
339                                         err("error parsing " KEY_SYSFS " attribute");
340                                         goto error;
341                                 }
342                                 strlcpy(pair->name, attr, sizeof(pair->name));
343                                 strlcpy(pair->value, value, sizeof(pair->value));
344                                 pair->operation = operation;
345                                 rule.sysfs_pair_count++;
346                                 valid = 1;
347                                 continue;
348                         }
349
350                         if (strncasecmp(key, KEY_ENV, sizeof(KEY_ENV)-1) == 0) {
351                                 struct key_pair *pair;
352
353                                 if (rule.env_pair_count >= KEY_ENV_PAIRS_MAX) {
354                                         err("skip rule, to many " KEY_ENV " keys in a single rule");
355                                         goto error;
356                                 }
357                                 pair = &rule.env_pair[rule.env_pair_count];
358                                 attr = get_key_attribute(key + sizeof(KEY_ENV)-1);
359                                 if (attr == NULL) {
360                                         err("error parsing " KEY_ENV " attribute");
361                                         continue;
362                                 }
363                                 strlcpy(pair->name, attr, sizeof(pair->name));
364                                 strlcpy(pair->value, value, sizeof(pair->value));
365                                 pair->operation = operation;
366                                 rule.env_pair_count++;
367                                 valid = 1;
368                                 continue;
369                         }
370
371                         if (strcasecmp(key, KEY_MODALIAS) == 0) {
372                                 strlcpy(rule.modalias, value, sizeof(rule.modalias));
373                                 rule.modalias_operation = operation;
374                                 valid = 1;
375                                 continue;
376                         }
377
378                         if (strncasecmp(key, KEY_IMPORT, sizeof(KEY_IMPORT)-1) == 0) {
379                                 attr = get_key_attribute(key + sizeof(KEY_IMPORT)-1);
380                                 if (attr && strstr(attr, "program")) {
381                                         dbg(KEY_IMPORT" will be executed");
382                                         rule.import_exec = 1;
383                                 } else if (attr && strstr(attr, "file")) {
384                                         dbg(KEY_IMPORT" will be included as file");
385                                 } else {
386                                         /* figure it out if it is executable */
387                                         char file[PATH_SIZE];
388                                         char *pos;
389                                         struct stat stats;
390
391                                         strlcpy(file, value, sizeof(file));
392                                         pos = strchr(file, ' ');
393                                         if (pos)
394                                                 pos[0] = '\0';
395                                         dbg(KEY_IMPORT" auto mode for '%s'", file);
396                                         if (!lstat(file, &stats) && (stats.st_mode & S_IXUSR)) {
397                                                         dbg(KEY_IMPORT" is executable, will be executed");
398                                                         rule.import_exec = 1;
399                                         }
400                                 }
401                                 strlcpy(rule.import, value, sizeof(rule.import));
402                                 rule.import_operation = operation;
403                                 valid = 1;
404                                 continue;
405                         }
406
407                         if (strcasecmp(key, KEY_DRIVER) == 0) {
408                                 strlcpy(rule.driver, value, sizeof(rule.driver));
409                                 rule.driver_operation = operation;
410                                 valid = 1;
411                                 continue;
412                         }
413
414                         if (strcasecmp(key, KEY_RESULT) == 0) {
415                                 strlcpy(rule.result, value, sizeof(rule.result));
416                                 rule.result_operation = operation;
417                                 valid = 1;
418                                 continue;
419                         }
420
421                         if (strcasecmp(key, KEY_PROGRAM) == 0) {
422                                 strlcpy(rule.program, value, sizeof(rule.program));
423                                 rule.program_operation = operation;
424                                 program_given = 1;
425                                 valid = 1;
426                                 continue;
427                         }
428
429                         if (strncasecmp(key, KEY_NAME, sizeof(KEY_NAME)-1) == 0) {
430                                 attr = get_key_attribute(key + sizeof(KEY_NAME)-1);
431                                 if (attr != NULL) {
432                                         if (strstr(attr, OPTION_PARTITIONS) != NULL) {
433                                                 dbg("creation of partition nodes requested");
434                                                 rule.partitions = DEFAULT_PARTITIONS_COUNT;
435                                         }
436                                         if (strstr(attr, OPTION_IGNORE_REMOVE) != NULL) {
437                                                 dbg("remove event should be ignored");
438                                                 rule.ignore_remove = 1;
439                                         }
440                                 }
441                                 rule.name_operation = operation;
442                                 strlcpy(rule.name, value, sizeof(rule.name));
443                                 valid = 1;
444                                 continue;
445                         }
446
447                         if (strcasecmp(key, KEY_SYMLINK) == 0) {
448                                 strlcpy(rule.symlink, value, sizeof(rule.symlink));
449                                 rule.symlink_operation = operation;
450                                 valid = 1;
451                                 continue;
452                         }
453
454                         if (strcasecmp(key, KEY_OWNER) == 0) {
455                                 strlcpy(rule.owner, value, sizeof(rule.owner));
456                                 rule.owner_operation = operation;
457                                 valid = 1;
458                                 continue;
459                         }
460
461                         if (strcasecmp(key, KEY_GROUP) == 0) {
462                                 strlcpy(rule.group, value, sizeof(rule.group));
463                                 rule.group_operation = operation;
464                                 valid = 1;
465                                 continue;
466                         }
467
468                         if (strcasecmp(key, KEY_MODE) == 0) {
469                                 rule.mode = strtol(value, NULL, 8);
470                                 rule.mode_operation = operation;
471                                 valid = 1;
472                                 continue;
473                         }
474
475                         if (strcasecmp(key, KEY_RUN) == 0) {
476                                 strlcpy(rule.run, value, sizeof(rule.run));
477                                 rule.run_operation = operation;
478                                 valid = 1;
479                                 continue;
480                         }
481
482                         if (strcasecmp(key, KEY_OPTIONS) == 0) {
483                                 if (strstr(value, OPTION_LAST_RULE) != NULL) {
484                                         dbg("last rule to be applied");
485                                         rule.last_rule = 1;
486                                 }
487                                 if (strstr(value, OPTION_IGNORE_DEVICE) != NULL) {
488                                         dbg("device should be ignored");
489                                         rule.ignore_device = 1;
490                                 }
491                                 if (strstr(value, OPTION_IGNORE_REMOVE) != NULL) {
492                                         dbg("remove event should be ignored");
493                                         rule.ignore_remove = 1;
494                                 }
495                                 if (strstr(value, OPTION_PARTITIONS) != NULL) {
496                                         dbg("creation of partition nodes requested");
497                                         rule.partitions = DEFAULT_PARTITIONS_COUNT;
498                                 }
499                                 valid = 1;
500                                 continue;
501                         }
502
503                         err("unknown key '%s'", key);
504                         goto error;
505                 }
506
507                 /* skip line if not any valid key was found */
508                 if (!valid)
509                         goto error;
510
511                 if ((rule.result[0] != '\0') && (program_given == 0)) {
512                         info(KEY_RESULT " is only useful when " KEY_PROGRAM " is called in any rule before");
513                         goto error;
514                 }
515
516                 rule.config_line = lineno;
517                 strlcpy(rule.config_file, filename, sizeof(rule.config_file));
518                 retval = add_rule_to_list(&rule);
519                 if (retval) {
520                         dbg("add_rule_to_list returned with error %d", retval);
521                         continue;
522 error:
523                         err("parse error %s, line %d:%d, rule skipped",
524                              filename, lineno, (int) (linepos - line));
525                 }
526         }
527
528         file_unmap(buf, bufsize);
529         return retval;
530 }
531
532 static int rules_map(const char *filename)
533 {
534         char *buf;
535         size_t size;
536
537         if (file_map(filename, &buf, &size))
538                 return -1;
539         if (size == 0)
540                 return -1;
541         rules_array = (struct udev_rule *) buf;
542         rules_array_size = size;
543         rules_count = size / sizeof(struct udev_rule);
544         dbg("found %zi compiled rules", rules_count);
545
546         return 0;
547 }
548
549 int udev_rules_init(void)
550 {
551         char comp[PATH_SIZE];
552         struct stat stats;
553         int retval;
554
555         strlcpy(comp, udev_rules_filename, sizeof(comp));
556         strlcat(comp, ".compiled", sizeof(comp));
557         if (stat(comp, &stats) == 0) {
558                 dbg("parse compiled rules '%s'", comp);
559                 return rules_map(comp);
560         }
561
562         if (stat(udev_rules_filename, &stats) != 0)
563                 return -1;
564
565         if ((stats.st_mode & S_IFMT) != S_IFDIR) {
566                 dbg("parse single rules file '%s'", udev_rules_filename);
567                 retval = rules_parse(udev_rules_filename);
568         } else {
569                 struct name_entry *name_loop, *name_tmp;
570                 LIST_HEAD(name_list);
571
572                 dbg("parse rules directory '%s'", udev_rules_filename);
573                 retval = add_matching_files(&name_list, udev_rules_filename, RULEFILE_SUFFIX);
574
575                 list_for_each_entry_safe(name_loop, name_tmp, &name_list, node) {
576                         rules_parse(name_loop->name);
577                         list_del(&name_loop->node);
578                 }
579         }
580
581         return retval;
582 }
583
584 void udev_rules_close(void)
585 {
586         struct udev_rule *rule;
587         struct udev_rule *temp_rule;
588
589         if (rules_array)
590                 file_unmap(rules_array, rules_array_size);
591         else
592                 list_for_each_entry_safe(rule, temp_rule, &rules_list, node) {
593                         list_del(&rule->node);
594                         free(rule);
595                 }
596 }