chiark / gitweb /
[PATCH] support =, ==, !=, += for the key match and assignment
[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 LIST_HEAD(udev_rule_list);
41
42 static int add_config_dev(struct udev_rule *new_rule)
43 {
44         struct udev_rule *tmp_rule;
45
46         tmp_rule = malloc(sizeof(*tmp_rule));
47         if (tmp_rule == NULL)
48                 return -ENOMEM;
49         memcpy(tmp_rule, new_rule, sizeof(*tmp_rule));
50         list_add_tail(&tmp_rule->node, &udev_rule_list);
51         udev_rule_dump(tmp_rule);
52
53         return 0;
54 }
55
56 void udev_rule_dump(struct udev_rule *rule)
57 {
58         dbg("name='%s', symlink='%s', bus='%s', id='%s', "
59             "sysfs_file[0]='%s', sysfs_value[0]='%s', "
60             "kernel='%s', program='%s', result='%s'"
61             "owner='%s', group='%s', mode=%#o",
62             rule->name, rule->symlink, rule->bus, rule->id,
63             rule->sysfs_pair[0].file, rule->sysfs_pair[0].value,
64             rule->kernel, rule->program, rule->result,
65             rule->owner, rule->group, rule->mode);
66 }
67
68 void udev_rule_list_dump(void)
69 {
70         struct udev_rule *rule;
71
72         list_for_each_entry(rule, &udev_rule_list, node)
73                 udev_rule_dump(rule);
74 }
75
76 static int get_key(char **line, char **key, enum key_operation *operation, char **value)
77 {
78         char *linepos;
79         char *temp;
80
81         linepos = *line;
82         if (!linepos)
83                 return -1;
84
85         /* skip whitespace */
86         while (isspace(linepos[0]) || linepos[0] == ',')
87                 linepos++;
88
89         /* get the key */
90         *key = linepos;
91         while (1) {
92                 linepos++;
93                 if (linepos[0] == '\0')
94                         return -1;
95                 if (isspace(linepos[0]))
96                         break;
97                 if (linepos[0] == '=')
98                         break;
99                 if (linepos[0] == '+')
100                         break;
101                 if (linepos[0] == '!')
102                         break;
103         }
104
105         /* remember end of key */
106         temp = linepos;
107
108         /* skip whitespace after key */
109         while (isspace(linepos[0]))
110                 linepos++;
111
112         /* get operation type */
113         if (linepos[0] == '=' && linepos[1] == '=') {
114                 *operation = KEY_OP_MATCH;
115                 linepos += 2;
116                 dbg("operator=match");
117         } else if (linepos[0] == '!' && linepos[1] == '=') {
118                 *operation = KEY_OP_NOMATCH;
119                 linepos += 2;
120                 dbg("operator=nomatch");
121         } else if (linepos[0] == '+' && linepos[1] == '=') {
122                 *operation = KEY_OP_ADD;
123                 linepos += 2;
124                 dbg("operator=add");
125         } else if (linepos[0] == '=') {
126                 *operation = KEY_OP_ASSIGN;
127                 linepos++;
128                 dbg("operator=assign");
129         } else
130                 return -1;
131
132         /* terminate key */
133         temp[0] = '\0';
134         dbg("key='%s'", *key);
135
136         /* skip whitespace after operator */
137         while (isspace(linepos[0]))
138                 linepos++;
139
140         /* get the value*/
141         if (linepos[0] == '"')
142                 linepos++;
143         else
144                 return -1;
145         *value = linepos;
146
147         temp = strchr(linepos, '"');
148         if (!temp)
149                 return -1;
150         temp[0] = '\0';
151         temp++;
152         dbg("value='%s'", *value);
153
154         /* move line to next key */
155         *line = temp;
156
157         return 0;
158 }
159
160 /* extract possible KEY{attr} */
161 static char *get_key_attribute(char *str)
162 {
163         char *pos;
164         char *attr;
165
166         attr = strchr(str, '{');
167         if (attr != NULL) {
168                 attr++;
169                 pos = strchr(attr, '}');
170                 if (pos == NULL) {
171                         dbg("missing closing brace for format");
172                         return NULL;
173                 }
174                 pos[0] = '\0';
175                 dbg("attribute='%s'", attr);
176                 return attr;
177         }
178
179         return NULL;
180 }
181
182 static int rules_parse(struct udevice *udev, const char *filename)
183 {
184         char line[LINE_SIZE];
185         char *bufline;
186         int lineno;
187         char *linepos;
188         char *attr;
189         char *buf;
190         size_t bufsize;
191         size_t cur;
192         size_t count;
193         int program_given = 0;
194         int valid;
195         int retval = 0;
196         struct udev_rule rule;
197
198         if (file_map(filename, &buf, &bufsize) != 0) {
199                 dbg("can't open '%s' as rules file", filename);
200                 return -1;
201         }
202         dbg("reading '%s' as rules file", filename);
203
204         /* loop through the whole file */
205         cur = 0;
206         lineno = 0;
207         while (cur < bufsize) {
208                 unsigned int i, j;
209
210                 count = buf_get_line(buf, bufsize, cur);
211                 bufline = &buf[cur];
212                 cur += count+1;
213                 lineno++;
214
215                 if (count >= sizeof(line)) {
216                         info("line too long, rule skipped %s, line %d", filename, lineno);
217                         continue;
218                 }
219
220                 /* eat the whitespace */
221                 while ((count > 0) && isspace(bufline[0])) {
222                         bufline++;
223                         count--;
224                 }
225                 if (count == 0)
226                         continue;
227
228                 /* see if this is a comment */
229                 if (bufline[0] == COMMENT_CHARACTER)
230                         continue;
231
232                 /* skip backslash and newline from multi line rules */
233                 for (i = j = 0; i < count; i++) {
234                         if (bufline[i] == '\\' && bufline[i+1] == '\n')
235                                 continue;
236
237                         line[j++] = bufline[i];
238                 }
239                 line[j] = '\0';
240                 dbg("read '%s'", line);
241
242                 /* get all known keys */
243                 memset(&rule, 0x00, sizeof(struct udev_rule));
244                 linepos = line;
245                 valid = 0;
246
247                 while (1) {
248                         char *key;
249                         char *value;
250                         enum key_operation operation = KEY_OP_UNKNOWN;
251
252                         retval = get_key(&linepos, &key, &operation, &value);
253                         if (retval)
254                                 break;
255
256                         if (strcasecmp(key, KEY_KERNEL) == 0) {
257                                 strlcpy(rule.kernel, value, sizeof(rule.kernel));
258                                 rule.kernel_operation = operation;
259                                 valid = 1;
260                                 continue;
261                         }
262
263                         if (strcasecmp(key, KEY_SUBSYSTEM) == 0) {
264                                 strlcpy(rule.subsystem, value, sizeof(rule.subsystem));
265                                 rule.subsystem_operation = operation;
266                                 valid = 1;
267                                 continue;
268                         }
269
270                         if (strcasecmp(key, KEY_BUS) == 0) {
271                                 strlcpy(rule.bus, value, sizeof(rule.bus));
272                                 rule.bus_operation = operation;
273                                 valid = 1;
274                                 continue;
275                         }
276
277                         if (strcasecmp(key, KEY_ID) == 0) {
278                                 strlcpy(rule.id, value, sizeof(rule.id));
279                                 rule.id_operation = operation;
280                                 valid = 1;
281                                 continue;
282                         }
283
284                         if (strncasecmp(key, KEY_SYSFS, sizeof(KEY_SYSFS)-1) == 0) {
285                                 struct sysfs_pair *pair = &rule.sysfs_pair[0];
286                                 int sysfs_pair_num = 0;
287
288                                 /* find first unused pair */
289                                 while (pair->file[0] != '\0') {
290                                         ++sysfs_pair_num;
291                                         if (sysfs_pair_num >= MAX_SYSFS_PAIRS) {
292                                                 pair = NULL;
293                                                 break;
294                                         }
295                                         ++pair;
296                                 }
297                                 if (pair) {
298                                         attr = get_key_attribute(key + sizeof(KEY_SYSFS)-1);
299                                         if (attr == NULL) {
300                                                 dbg("error parsing " KEY_SYSFS " attribute");
301                                                 continue;
302                                         }
303                                         strlcpy(pair->file, attr, sizeof(pair->file));
304                                         strlcpy(pair->value, value, sizeof(pair->value));
305                                         pair->operation = operation;
306                                         valid = 1;
307                                 }
308                                 continue;
309                         }
310
311                         if (strcasecmp(key, KEY_DRIVER) == 0) {
312                                 strlcpy(rule.driver, value, sizeof(rule.driver));
313                                 rule.driver_operation = operation;
314                                 valid = 1;
315                                 continue;
316                         }
317
318                         if (strcasecmp(key, KEY_RESULT) == 0) {
319                                 strlcpy(rule.result, value, sizeof(rule.result));
320                                 rule.result_operation = operation;
321                                 valid = 1;
322                                 continue;
323                         }
324
325                         if (strcasecmp(key, KEY_PROGRAM) == 0) {
326                                 strlcpy(rule.program, value, sizeof(rule.program));
327                                 rule.program_operation = operation;
328                                 program_given = 1;
329                                 valid = 1;
330                                 continue;
331                         }
332
333                         if (strncasecmp(key, KEY_NAME, sizeof(KEY_NAME)-1) == 0) {
334                                 attr = get_key_attribute(key + sizeof(KEY_NAME)-1);
335                                 /* FIXME: remove old style options and make OPTIONS= mandatory */
336                                 if (attr != NULL) {
337                                         if (strstr(attr, OPTION_PARTITIONS) != NULL) {
338                                                 dbg("creation of partition nodes requested");
339                                                 rule.partitions = DEFAULT_PARTITIONS_COUNT;
340                                         }
341                                         if (strstr(attr, OPTION_IGNORE_REMOVE) != NULL) {
342                                                 dbg("remove event should be ignored");
343                                                 rule.ignore_remove = 1;
344                                         }
345                                 }
346                                 if (value[0] != '\0')
347                                         strlcpy(rule.name, value, sizeof(rule.name));
348                                 else
349                                         rule.ignore_device = 1;
350                                 valid = 1;
351                                 continue;
352                         }
353
354                         if (strcasecmp(key, KEY_SYMLINK) == 0) {
355                                 strlcpy(rule.symlink, value, sizeof(rule.symlink));
356                                 valid = 1;
357                                 continue;
358                         }
359
360                         if (strcasecmp(key, KEY_OWNER) == 0) {
361                                 strlcpy(rule.owner, value, sizeof(rule.owner));
362                                 valid = 1;
363                                 continue;
364                         }
365
366                         if (strcasecmp(key, KEY_GROUP) == 0) {
367                                 strlcpy(rule.group, value, sizeof(rule.group));
368                                 valid = 1;
369                                 continue;
370                         }
371
372                         if (strcasecmp(key, KEY_MODE) == 0) {
373                                 rule.mode = strtol(value, NULL, 8);
374                                 valid = 1;
375                                 continue;
376                         }
377
378                         if (strcasecmp(key, KEY_OPTIONS) == 0) {
379                                 if (strstr(value, OPTION_LAST_RULE) != NULL) {
380                                         dbg("last rule to be applied");
381                                         rule.last_rule = 1;
382                                 }
383                                 if (strstr(value, OPTION_IGNORE_DEVICE) != NULL) {
384                                         dbg("device should be ignored");
385                                         rule.ignore_device = 1;
386                                 }
387                                 if (strstr(value, OPTION_IGNORE_REMOVE) != NULL) {
388                                         dbg("remove event should be ignored");
389                                         rule.ignore_remove = 1;
390                                 }
391                                 if (strstr(value, OPTION_PARTITIONS) != NULL) {
392                                         dbg("creation of partition nodes requested");
393                                         rule.partitions = DEFAULT_PARTITIONS_COUNT;
394                                 }
395                                 valid = 1;
396                                 continue;
397                         }
398
399                         dbg("unknown key '%s'", key);
400                         goto error;
401                 }
402
403                 /* skip line if not any valid key was found */
404                 if (!valid)
405                         goto error;
406
407                 /* simple plausibility checks for given keys */
408                 if ((rule.sysfs_pair[0].file[0] == '\0') ^
409                     (rule.sysfs_pair[0].value[0] == '\0')) {
410                         info("inconsistency in " KEY_SYSFS " key");
411                         goto error;
412                 }
413
414                 if ((rule.result[0] != '\0') && (program_given == 0)) {
415                         info(KEY_RESULT " is only useful when "
416                              KEY_PROGRAM " is called in any rule before");
417                         goto error;
418                 }
419
420                 rule.config_line = lineno;
421                 strlcpy(rule.config_file, filename, sizeof(rule.config_file));
422                 retval = add_config_dev(&rule);
423                 if (retval) {
424                         dbg("add_config_dev returned with error %d", retval);
425                         continue;
426 error:
427                         info("parse error %s, line %d:%d, rule skipped",
428                              filename, lineno, (int) (linepos - line));
429                 }
430         }
431
432         file_unmap(buf, bufsize);
433         return retval;
434 }
435
436 int udev_rules_init(void)
437 {
438         struct stat stats;
439         int retval;
440
441         if (stat(udev_rules_filename, &stats) != 0)
442                 return -1;
443
444         if ((stats.st_mode & S_IFMT) != S_IFDIR)
445                 retval = rules_parse(NULL, udev_rules_filename);
446         else
447                 retval = call_foreach_file(rules_parse, NULL, udev_rules_filename, RULEFILE_SUFFIX);
448
449         return retval;
450 }
451
452 void udev_rules_close(void)
453 {
454         struct udev_rule *rule;
455         struct udev_rule *temp_rule;
456
457         list_for_each_entry_safe(rule, temp_rule, &udev_rule_list, node) {
458                 list_del(&rule->node);
459                 free(rule);
460         }
461 }
462