chiark / gitweb /
move code to its own files
[elogind.git] / udev_rules_parse.c
1 /*
2  * udev_rules_parse.c
3  *
4  * Copyright (C) 2003,2004 Greg Kroah-Hartman <greg@kroah.com>
5  * Copyright (C) 2003-2005 Kay Sievers <kay.sievers@vrfy.org>
6  *
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 <ctype.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29 #include <errno.h>
30
31 #include "udev_libc_wrapper.h"
32 #include "udev.h"
33 #include "udev_utils.h"
34 #include "logging.h"
35 #include "udev_rules.h"
36
37
38 void udev_rules_iter_init(struct udev_rules *rules)
39 {
40         dbg("bufsize=%zi", rules->bufsize);
41         rules->current = 0;
42 }
43
44 struct udev_rule *udev_rules_iter_next(struct udev_rules *rules)
45 {
46         static struct udev_rule *rule;
47
48         if (!rules)
49                 return NULL;
50
51         dbg("current=%zi", rules->current);
52         if (rules->current >= rules->bufsize) {
53                 dbg("no more rules");
54                 return NULL;
55         }
56
57         /* get next rule */
58         rule = (struct udev_rule *) (rules->buf + rules->current);
59         rules->current += sizeof(struct udev_rule) + rule->bufsize;
60
61         return rule;
62 }
63
64 struct udev_rule *udev_rules_iter_label(struct udev_rules *rules, const char *label)
65 {
66         static struct udev_rule *rule;
67
68 next:
69         dbg("current=%zi", rules->current);
70         if (rules->current >= rules->bufsize) {
71                 dbg("no more rules");
72                 return NULL;
73         }
74         rule = (struct udev_rule *) (rules->buf + rules->current);
75
76         if (strcmp(&rule->buf[rule->label.val_off], label) != 0) {
77                 dbg("moving forward, looking for label '%s'", label);
78                 rules->current += sizeof(struct udev_rule) + rule->bufsize;
79                 goto next;
80         }
81
82         dbg("found label '%s'", label);
83         return rule;
84 }
85
86 static int get_key(char **line, char **key, enum key_operation *operation, char **value)
87 {
88         char *linepos;
89         char *temp;
90
91         linepos = *line;
92         if (!linepos)
93                 return -1;
94
95         /* skip whitespace */
96         while (isspace(linepos[0]) || linepos[0] == ',')
97                 linepos++;
98
99         /* get the key */
100         *key = linepos;
101         while (1) {
102                 linepos++;
103                 if (linepos[0] == '\0')
104                         return -1;
105                 if (isspace(linepos[0]))
106                         break;
107                 if (linepos[0] == '=')
108                         break;
109                 if (linepos[0] == '+')
110                         break;
111                 if (linepos[0] == '!')
112                         break;
113                 if (linepos[0] == ':')
114                         break;
115         }
116
117         /* remember end of key */
118         temp = linepos;
119
120         /* skip whitespace after key */
121         while (isspace(linepos[0]))
122                 linepos++;
123
124         /* get operation type */
125         if (linepos[0] == '=' && linepos[1] == '=') {
126                 *operation = KEY_OP_MATCH;
127                 linepos += 2;
128                 dbg("operator=match");
129         } else if (linepos[0] == '!' && linepos[1] == '=') {
130                 *operation = KEY_OP_NOMATCH;
131                 linepos += 2;
132                 dbg("operator=nomatch");
133         } else if (linepos[0] == '+' && linepos[1] == '=') {
134                 *operation = KEY_OP_ADD;
135                 linepos += 2;
136                 dbg("operator=add");
137         } else if (linepos[0] == '=') {
138                 *operation = KEY_OP_ASSIGN;
139                 linepos++;
140                 dbg("operator=assign");
141         } else if (linepos[0] == ':' && linepos[1] == '=') {
142                 *operation = KEY_OP_ASSIGN_FINAL;
143                 linepos += 2;
144                 dbg("operator=assign_final");
145         } else
146                 return -1;
147
148         /* terminate key */
149         temp[0] = '\0';
150         dbg("key='%s'", *key);
151
152         /* skip whitespace after operator */
153         while (isspace(linepos[0]))
154                 linepos++;
155
156         /* get the value*/
157         if (linepos[0] == '"')
158                 linepos++;
159         else
160                 return -1;
161         *value = linepos;
162
163         temp = strchr(linepos, '"');
164         if (!temp)
165                 return -1;
166         temp[0] = '\0';
167         temp++;
168         dbg("value='%s'", *value);
169
170         /* move line to next key */
171         *line = temp;
172
173         return 0;
174 }
175
176 /* extract possible KEY{attr} */
177 static char *get_key_attribute(char *str)
178 {
179         char *pos;
180         char *attr;
181
182         attr = strchr(str, '{');
183         if (attr != NULL) {
184                 attr++;
185                 pos = strchr(attr, '}');
186                 if (pos == NULL) {
187                         err("missing closing brace for format");
188                         return NULL;
189                 }
190                 pos[0] = '\0';
191                 dbg("attribute='%s'", attr);
192                 return attr;
193         }
194
195         return NULL;
196 }
197
198 static int add_rule_key(struct udev_rule *rule, struct key *key,
199                         enum key_operation operation, const char *value)
200 {
201         size_t val_len = strnlen(value, PATH_SIZE);
202
203         key->operation = operation;
204
205         key->val_off = rule->bufsize;
206         strlcpy(rule->buf + rule->bufsize, value, val_len+1);
207         rule->bufsize += val_len+1;
208
209         return 0;
210 }
211
212 static int add_rule_key_pair(struct udev_rule *rule, struct key_pairs *pairs,
213                              enum key_operation operation, const char *key, const char *value)
214 {
215         size_t key_len = strnlen(key, PATH_SIZE);
216
217         if (pairs->count >= PAIRS_MAX) {
218                 err("skip, too many keys in a single rule");
219                 return -1;
220         }
221
222         add_rule_key(rule, &pairs->keys[pairs->count].key, operation, value);
223
224         /* add the key-name of the pair */
225         pairs->keys[pairs->count].key_name_off = rule->bufsize;
226         strlcpy(rule->buf + rule->bufsize, key, key_len+1);
227         rule->bufsize += key_len+1;
228
229         pairs->count++;
230
231         return 0;
232 }
233
234 static int add_to_rules(struct udev_rules *rules, char *line)
235 {
236         struct udev_rule *rule;
237         size_t rule_size;
238         int valid;
239         char *linepos;
240         char *attr;
241         size_t padding;
242         int retval;
243
244         /* get all the keys */
245         rule = calloc(1, sizeof (struct udev_rule) + LINE_SIZE);
246         if (!rule) {
247                 err("malloc failed");
248                 return -1;
249         }
250         linepos = line;
251         valid = 0;
252
253         while (1) {
254                 char *key;
255                 char *value;
256                 enum key_operation operation = KEY_OP_UNSET;
257
258                 retval = get_key(&linepos, &key, &operation, &value);
259                 if (retval)
260                         break;
261
262                 if (strcasecmp(key, "LABEL") == 0) {
263                         add_rule_key(rule, &rule->label, operation, value);
264                         valid = 1;
265                         continue;
266                 }
267
268                 if (strcasecmp(key, "GOTO") == 0) {
269                         add_rule_key(rule, &rule->goto_label, operation, value);
270                         valid = 1;
271                         continue;
272                 }
273
274                 if (strcasecmp(key, "KERNEL") == 0) {
275                         add_rule_key(rule, &rule->kernel_name, operation, value);
276                         valid = 1;
277                         continue;
278                 }
279
280                 if (strcasecmp(key, "SUBSYSTEM") == 0) {
281                         add_rule_key(rule, &rule->subsystem, operation, value);
282                         valid = 1;
283                         continue;
284                 }
285
286                 if (strcasecmp(key, "ACTION") == 0) {
287                         add_rule_key(rule, &rule->action, operation, value);
288                         valid = 1;
289                         continue;
290                 }
291
292                 if (strcasecmp(key, "DEVPATH") == 0) {
293                         add_rule_key(rule, &rule->devpath, operation, value);
294                         valid = 1;
295                         continue;
296                 }
297
298                 if (strcasecmp(key, "BUS") == 0) {
299                         add_rule_key(rule, &rule->bus, operation, value);
300                         valid = 1;
301                         continue;
302                 }
303
304                 if (strcasecmp(key, "ID") == 0) {
305                         add_rule_key(rule, &rule->id, operation, value);
306                         valid = 1;
307                         continue;
308                 }
309
310                 if (strncasecmp(key, "SYSFS", sizeof("SYSFS")-1) == 0) {
311                         attr = get_key_attribute(key + sizeof("SYSFS")-1);
312                         if (attr == NULL) {
313                                 err("error parsing SYSFS attribute in '%s'", line);
314                                 continue;
315                         }
316                         add_rule_key_pair(rule, &rule->sysfs, operation, attr, value);
317                         valid = 1;
318                         continue;
319                 }
320
321                 if (strcasecmp(key, "WAIT_FOR_SYSFS") == 0) {
322                         add_rule_key(rule, &rule->wait_for_sysfs, operation, value);
323                         valid = 1;
324                         continue;
325                 }
326
327                 if (strncasecmp(key, "ENV", sizeof("ENV")-1) == 0) {
328                         attr = get_key_attribute(key + sizeof("ENV")-1);
329                         if (attr == NULL) {
330                                 err("error parsing ENV attribute");
331                                 continue;
332                         }
333                         add_rule_key_pair(rule, &rule->env, operation, attr, value);
334                         valid = 1;
335                         continue;
336                 }
337
338                 if (strcasecmp(key, "MODALIAS") == 0) {
339                         add_rule_key(rule, &rule->modalias, operation, value);
340                         valid = 1;
341                         continue;
342                 }
343
344                 if (strncasecmp(key, "IMPORT", sizeof("IMPORT")-1) == 0) {
345                         attr = get_key_attribute(key + sizeof("IMPORT")-1);
346                         if (attr && strstr(attr, "program")) {
347                                 dbg("IMPORT will be executed");
348                                 rule->import_type  = IMPORT_PROGRAM;
349                         } else if (attr && strstr(attr, "file")) {
350                                 dbg("IMPORT will be included as file");
351                                 rule->import_type  = IMPORT_FILE;
352                         } else if (attr && strstr(attr, "parent")) {
353                                 dbg("IMPORT will include the parent values");
354                                 rule->import_type = IMPORT_PARENT;
355                         } else {
356                                 /* figure it out if it is executable */
357                                 char file[PATH_SIZE];
358                                 char *pos;
359                                 struct stat stats;
360
361                                 strlcpy(file, value, sizeof(file));
362                                 pos = strchr(file, ' ');
363                                 if (pos)
364                                         pos[0] = '\0';
365                                 dbg("IMPORT auto mode for '%s'", file);
366                                 if (!lstat(file, &stats) && (stats.st_mode & S_IXUSR)) {
367                                         dbg("IMPORT is executable, will be executed (autotype)");
368                                         rule->import_type  = IMPORT_PROGRAM;
369                                 } else {
370                                         dbg("IMPORT is not executable, will be included as file (autotype)");
371                                         rule->import_type  = IMPORT_FILE;
372                                 }
373                         }
374                         add_rule_key(rule, &rule->import, operation, value);
375                         valid = 1;
376                         continue;
377                 }
378
379                 if (strcasecmp(key, "DRIVER") == 0) {
380                         add_rule_key(rule, &rule->driver, operation, value);
381                         valid = 1;
382                         continue;
383                 }
384
385                 if (strcasecmp(key, "RESULT") == 0) {
386                         add_rule_key(rule, &rule->result, operation, value);
387                         valid = 1;
388                         continue;
389                 }
390
391                 if (strcasecmp(key, "PROGRAM") == 0) {
392                         add_rule_key(rule, &rule->program, operation, value);
393                         valid = 1;
394                         continue;
395                 }
396
397                 if (strncasecmp(key, "NAME", sizeof("NAME")-1) == 0) {
398                         attr = get_key_attribute(key + sizeof("NAME")-1);
399                         if (attr != NULL) {
400                                 if (strstr(attr, "all_partitions") != NULL) {
401                                         dbg("creation of partition nodes requested");
402                                         rule->partitions = DEFAULT_PARTITIONS_COUNT;
403                                 }
404                                 if (strstr(attr, "ignore_remove") != NULL) {
405                                         dbg("remove event should be ignored");
406                                         rule->ignore_remove = 1;
407                                 }
408                         }
409                         if (value[0] == '\0') {
410                                 dbg("name empty device should be ignored");
411                                 rule->name.operation = operation;
412                                 rule->ignore_device = 1;
413                         } else
414                                 add_rule_key(rule, &rule->name, operation, value);
415                         continue;
416                 }
417
418                 if (strcasecmp(key, "SYMLINK") == 0) {
419                         add_rule_key(rule, &rule->symlink, operation, value);
420                         valid = 1;
421                         continue;
422                 }
423
424                 if (strcasecmp(key, "OWNER") == 0) {
425                         valid = 1;
426                         if (rules->resolve_names && (!strchr(value, '$') && !strchr(value, '%'))) {
427                                 char *endptr;
428                                 strtoul(value, &endptr, 10);
429                                 if (endptr[0] != '\0') {
430                                         char owner[32];
431                                         uid_t uid = lookup_user(value);
432                                         dbg("replacing username='%s' by id=%i", value, uid);
433                                         sprintf(owner, "%li", uid);
434                                         add_rule_key(rule, &rule->owner, operation, owner);
435                                         continue;
436                                 }
437                         }
438
439                         add_rule_key(rule, &rule->owner, operation, value);
440                         continue;
441                 }
442
443                 if (strcasecmp(key, "GROUP") == 0) {
444                         valid = 1;
445                         if (rules->resolve_names && (!strchr(value, '$') && !strchr(value, '%'))) {
446                                 char *endptr;
447                                 strtoul(value, &endptr, 10);
448                                 if (endptr[0] != '\0') {
449                                         char group[32];
450                                         gid_t gid = lookup_group(value);
451                                         dbg("replacing groupname='%s' by id=%i", value, gid);
452                                         sprintf(group, "%li", gid);
453                                         add_rule_key(rule, &rule->group, operation, group);
454                                         continue;
455                                 }
456                         }
457
458                         add_rule_key(rule, &rule->group, operation, value);
459                         continue;
460                 }
461
462                 if (strcasecmp(key, "MODE") == 0) {
463                         rule->mode = strtol(value, NULL, 8);
464                         rule->mode_operation = operation;
465                         valid = 1;
466                         continue;
467                 }
468
469                 if (strcasecmp(key, "RUN") == 0) {
470                         add_rule_key(rule, &rule->run, operation, value);
471                         valid = 1;
472                         continue;
473                 }
474
475                 if (strcasecmp(key, "OPTIONS") == 0) {
476                         if (strstr(value, "last_rule") != NULL) {
477                                 dbg("last rule to be applied");
478                                 rule->last_rule = 1;
479                         }
480                         if (strstr(value, "ignore_device") != NULL) {
481                                 dbg("device should be ignored");
482                                 rule->ignore_device = 1;
483                         }
484                         if (strstr(value, "ignore_remove") != NULL) {
485                                 dbg("remove event should be ignored");
486                                 rule->ignore_remove = 1;
487                         }
488                         if (strstr(value, "all_partitions") != NULL) {
489                                 dbg("creation of partition nodes requested");
490                                 rule->partitions = DEFAULT_PARTITIONS_COUNT;
491                         }
492                         valid = 1;
493                         continue;
494                 }
495
496                 err("unknown key '%s', in '%s'", key, line);
497         }
498
499         /* skip line if not any valid key was found */
500         if (!valid) {
501                 err("invalid rule '%s'", line);
502                 goto exit;
503         }
504
505         /* grow buffer and add rule */
506         rule_size = sizeof(struct udev_rule) + rule->bufsize;
507         padding = (sizeof(size_t) - rule_size % sizeof(size_t)) % sizeof(size_t);
508         dbg("add %zi padding bytes", padding);
509         rule_size += padding;
510         rule->bufsize += padding;
511
512         rules->buf = realloc(rules->buf, rules->bufsize + rule_size);
513         if (!rules->buf) {
514                 err("realloc failed");
515                 goto exit;
516         }
517         dbg("adding rule to offset %zi", rules->bufsize);
518         memcpy(rules->buf + rules->bufsize, rule, rule_size);
519         rules->bufsize += rule_size;
520 exit:
521         free(rule);
522         return 0;
523 }
524
525 static int parse_file(struct udev_rules *rules, const char *filename)
526 {
527         char line[LINE_SIZE];
528         char *bufline;
529         int lineno;
530         char *buf;
531         size_t bufsize;
532         size_t cur;
533         size_t count;
534         int retval = 0;
535
536         if (file_map(filename, &buf, &bufsize) != 0) {
537                 err("can't open '%s' as rules file", filename);
538                 return -1;
539         }
540         dbg("reading '%s' as rules file", filename);
541
542         /* loop through the whole file */
543         cur = 0;
544         lineno = 0;
545         while (cur < bufsize) {
546                 unsigned int i, j;
547
548                 count = buf_get_line(buf, bufsize, cur);
549                 bufline = &buf[cur];
550                 cur += count+1;
551                 lineno++;
552
553                 if (count >= sizeof(line)) {
554                         info("line too long, rule skipped %s, line %d", filename, lineno);
555                         continue;
556                 }
557
558                 /* eat the whitespace */
559                 while ((count > 0) && isspace(bufline[0])) {
560                         bufline++;
561                         count--;
562                 }
563                 if (count == 0)
564                         continue;
565
566                 /* see if this is a comment */
567                 if (bufline[0] == COMMENT_CHARACTER)
568                         continue;
569
570                 /* skip backslash and newline from multi line rules */
571                 for (i = j = 0; i < count; i++) {
572                         if (bufline[i] == '\\' && bufline[i+1] == '\n')
573                                 continue;
574
575                         line[j++] = bufline[i];
576                 }
577                 line[j] = '\0';
578
579                 dbg("read '%s'", line);
580                 add_to_rules(rules, line);
581         }
582
583         file_unmap(buf, bufsize);
584         return retval;
585 }
586
587 static int rules_map(struct udev_rules *rules, const char *filename)
588 {
589         if (file_map(filename, &rules->buf, &rules->bufsize)) {
590                 rules->buf = NULL;
591                 return -1;
592         }
593         if (rules->bufsize == 0) {
594                 file_unmap(rules->buf, rules->bufsize);
595                 rules->buf = NULL;
596                 return -1;
597         }
598         rules->mapped = 1;
599
600         return 0;
601 }
602
603 int udev_rules_init(struct udev_rules *rules, int resolve_names)
604 {
605         char comp[PATH_SIZE];
606         struct stat stats;
607         int retval;
608
609         memset(rules, 0x00, sizeof(struct udev_rules));
610         rules->resolve_names = resolve_names;
611
612         /* check for precompiled rules */
613         strlcpy(comp, udev_rules_filename, sizeof(comp));
614         strlcat(comp, ".compiled", sizeof(comp));
615         if (stat(comp, &stats) == 0) {
616                 dbg("map compiled rules '%s'", comp);
617                 if (rules_map(rules, comp) == 0)
618                         return 0;
619         }
620
621         if (stat(udev_rules_filename, &stats) != 0)
622                 return -1;
623
624         if ((stats.st_mode & S_IFMT) != S_IFDIR) {
625                 dbg("parse single rules file '%s'", udev_rules_filename);
626                 retval = parse_file(rules, udev_rules_filename);
627         } else {
628                 struct name_entry *name_loop, *name_tmp;
629                 LIST_HEAD(name_list);
630
631                 dbg("parse rules directory '%s'", udev_rules_filename);
632                 retval = add_matching_files(&name_list, udev_rules_filename, RULEFILE_SUFFIX);
633
634                 list_for_each_entry_safe(name_loop, name_tmp, &name_list, node) {
635                         parse_file(rules, name_loop->name);
636                         list_del(&name_loop->node);
637                 }
638         }
639
640         return retval;
641 }
642
643 void udev_rules_close(struct udev_rules *rules)
644 {
645         if (rules->mapped)
646                 file_unmap(rules->buf, rules->bufsize);
647         else
648                 free(rules->buf);
649
650         rules->buf = NULL;
651 }