chiark / gitweb /
1faa253c1e9b8685da16c5413d585b202383c8b0
[elogind.git] / namedev.c
1 /*
2  * namedev.c
3  *
4  * Userspace devfs
5  *
6  * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
7  *
8  *
9  *      This program is free software; you can redistribute it and/or modify it
10  *      under the terms of the GNU General Public License as published by the
11  *      Free Software Foundation version 2 of the License.
12  * 
13  *      This program is distributed in the hope that it will be useful, but
14  *      WITHOUT ANY WARRANTY; without even the implied warranty of
15  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  *      General Public License for more details.
17  * 
18  *      You should have received a copy of the GNU General Public License along
19  *      with this program; if not, write to the Free Software Foundation, Inc.,
20  *      675 Mass Ave, Cambridge, MA 02139, USA.
21  *
22  */
23
24 #include <stddef.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <stdio.h>
28 #include <fcntl.h>
29 #include <ctype.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <sys/wait.h>
33
34 #include "list.h"
35 #include "udev.h"
36 #include "udev_version.h"
37 #include "namedev.h"
38 #include "libsysfs/libsysfs.h"
39 #include "klibc_fixups.h"
40
41 LIST_HEAD(config_device_list);
42 LIST_HEAD(perm_device_list);
43
44 /* compare string with pattern (supports * ? [0-9] [!A-Z]) */
45 static int strcmp_pattern(const char *p, const char *s)
46 {
47         if (*s == '\0') {
48                 while (*p == '*')
49                         p++;
50                 return (*p != '\0');
51         }
52         switch (*p) {
53         case '[':
54                 {
55                         int not = 0;
56                         p++;
57                         if (*p == '!') {
58                                 not = 1;
59                                 p++;
60                         }
61                         while (*p && (*p != ']')) {
62                                 int match = 0;
63                                 if (p[1] == '-') {
64                                         if ((*s >= *p) && (*s <= p[2]))
65                                                 match = 1;
66                                         p += 3;
67                                 } else {
68                                         match = (*p == *s);
69                                         p++;
70                                 }
71                                 if (match ^ not) {
72                                         while (*p && (*p != ']'))
73                                                 p++;
74                                         return strcmp_pattern(p+1, s+1);
75                                 }
76                         }
77                 }
78                 break;
79         case '*':
80                 if (strcmp_pattern(p, s+1))
81                         return strcmp_pattern(p+1, s);
82                 return 0;
83         case '\0':
84                 if (*s == '\0') {
85                         return 0;
86                 }
87                 break;
88         default:
89                 if ((*p == *s) || (*p == '?'))
90                         return strcmp_pattern(p+1, s+1);
91                 break;
92         }
93         return 1;
94 }
95
96 #define copy_var(a, b, var)             \
97         if (b->var)                     \
98                 a->var = b->var;
99
100 #define copy_string(a, b, var)          \
101         if (strlen(b->var))             \
102                 strcpy(a->var, b->var);
103
104 int add_config_dev(struct config_device *new_dev)
105 {
106         struct list_head *tmp;
107         struct config_device *tmp_dev;
108
109         /* update the values if we already have the device */
110         list_for_each(tmp, &config_device_list) {
111                 struct config_device *dev = list_entry(tmp, struct config_device, node);
112                 if (strcmp_pattern(new_dev->name, dev->name))
113                         continue;
114                 if (strncmp(dev->bus, new_dev->bus, sizeof(dev->name)))
115                         continue;
116                 copy_var(dev, new_dev, type);
117                 copy_string(dev, new_dev, bus);
118                 copy_string(dev, new_dev, sysfs_file);
119                 copy_string(dev, new_dev, sysfs_value);
120                 copy_string(dev, new_dev, id);
121                 copy_string(dev, new_dev, place);
122                 copy_string(dev, new_dev, kernel_name);
123                 copy_string(dev, new_dev, exec_program);
124                 return 0;
125         }
126
127         /* not found, add new structure to the device list */
128         tmp_dev = malloc(sizeof(*tmp_dev));
129         if (!tmp_dev)
130                 return -ENOMEM;
131         memcpy(tmp_dev, new_dev, sizeof(*tmp_dev));
132         list_add_tail(&tmp_dev->node, &config_device_list);
133         //dump_config_dev(tmp_dev);
134         return 0;
135 }
136
137 int add_perm_dev(struct perm_device *new_dev)
138 {
139         struct list_head *tmp;
140         struct perm_device *tmp_dev;
141
142         /* update the values if we already have the device */
143         list_for_each(tmp, &perm_device_list) {
144                 struct perm_device *dev = list_entry(tmp, struct perm_device, node);
145                 if (strcmp_pattern(new_dev->name, dev->name))
146                         continue;
147                 copy_var(dev, new_dev, mode);
148                 copy_string(dev, new_dev, owner);
149                 copy_string(dev, new_dev, group);
150                 return 0;
151         }
152
153         /* not found, add new structure to the perm list */
154         tmp_dev = malloc(sizeof(*tmp_dev));
155         if (!tmp_dev)
156                 return -ENOMEM;
157         memcpy(tmp_dev, new_dev, sizeof(*tmp_dev));
158         list_add_tail(&tmp_dev->node, &perm_device_list);
159         //dump_perm_dev(tmp_dev);
160         return 0;
161 }
162
163 static struct perm_device *find_perm(char *name)
164 {
165         struct list_head *tmp;
166         struct perm_device *perm = NULL;
167
168         list_for_each(tmp, &perm_device_list) {
169                 perm = list_entry(tmp, struct perm_device, node);
170                 if (strcmp_pattern(perm->name, name))
171                         continue;
172                 return perm;
173         }
174         return NULL;
175 }
176
177 static mode_t get_default_mode(struct sysfs_class_device *class_dev)
178 {
179         mode_t mode = 0600;     /* default to owner rw only */
180
181         if (strlen(default_mode_str) != 0) {
182                 mode = strtol(default_mode_str, NULL, 8);
183         }
184         return mode;
185 }
186
187 static void build_kernel_number(struct sysfs_class_device *class_dev, struct udevice *udev)
188 {
189         char *dig;
190
191         /* FIXME, figure out how to handle stuff like sdaj which will not work right now. */
192         dig = class_dev->name + strlen(class_dev->name);
193         while (isdigit(*(dig-1)))
194                 dig--;
195         strfieldcpy(udev->kernel_number, dig);
196         dbg("kernel_number='%s'", udev->kernel_number);
197 }
198
199 static void apply_format(struct udevice *udev, unsigned char *string)
200 {
201         char name[NAME_SIZE];
202         char *pos;
203
204         while (1) {
205                 pos = strchr(string, '%');
206
207                 if (pos) {
208                         strfieldcpy(name, pos+2);
209                         *pos = 0x00;
210                         switch (pos[1]) {
211                         case 'b':
212                                 if (strlen(udev->bus_id) == 0)
213                                         break;
214                                 strcat(pos, udev->bus_id);
215                                 dbg("substitute bus_id '%s'", udev->bus_id);
216                                 break;
217                         case 'n':
218                                 if (strlen(udev->kernel_number) == 0)
219                                         break;
220                                 strcat(pos, udev->kernel_number);
221                                 dbg("substitute kernel number '%s'", udev->kernel_number);
222                                 break;
223                         case 'D':
224                                 if (strlen(udev->kernel_number) == 0) {
225                                         strcat(pos, "disk");
226                                         break;
227                                 }
228                                 strcat(pos, "part");
229                                 strcat(pos, udev->kernel_number);
230                                 dbg("substitute kernel number '%s'", udev->kernel_number);
231                                 break;
232                         case 'm':
233                                 sprintf(pos, "%u", udev->minor);
234                                 dbg("substitute minor number '%u'", udev->minor);
235                                 break;
236                         case 'M':
237                                 sprintf(pos, "%u", udev->major);
238                                 dbg("substitute major number '%u'", udev->major);
239                                 break;
240                         case 'c':
241                                 if (strlen(udev->callout_value) == 0)
242                                         break;
243                                 strcat(pos, udev->callout_value);
244                                 dbg("substitute callout output '%s'", udev->callout_value);
245                                 break;
246                         default:
247                                 dbg("unknown substitution type '%%%c'", pos[1]);
248                                 break;
249                         }
250                         strcat(string, name);
251                 } else
252                         break;
253         }
254 }
255
256
257 static int exec_callout(struct config_device *dev, char *value, int len)
258 {
259         int retval;
260         int res;
261         int status;
262         int fds[2];
263         pid_t pid;
264         int value_set = 0;
265         char buffer[256];
266         char *arg;
267         char *args[CALLOUT_MAXARG];
268         int i;
269
270         dbg("callout to '%s'", dev->exec_program);
271         retval = pipe(fds);
272         if (retval != 0) {
273                 dbg("pipe failed");
274                 return -1;
275         }
276         pid = fork();
277         if (pid == -1) {
278                 dbg("fork failed");
279                 return -1;
280         }
281
282         if (pid == 0) {
283                 /* child */
284                 close(STDOUT_FILENO);
285                 dup(fds[1]);    /* dup write side of pipe to STDOUT */
286                 if (strchr(dev->exec_program, ' ')) {
287                         /* callout with arguments */
288                         arg = dev->exec_program;
289                         for (i=0; i < CALLOUT_MAXARG-1; i++) {
290                                 args[i] = strsep(&arg, " ");
291                                 if (args[i] == NULL)
292                                         break;
293                         }
294                         if (args[i]) {
295                                 dbg("too many args - %d", i);
296                                 args[i] = NULL;
297                         }
298                         retval = execve(args[0], args, main_envp);
299                 } else {
300                         retval = execve(dev->exec_program, main_argv, main_envp);
301                 }
302                 if (retval != 0) {
303                         dbg("child execve failed");
304                         exit(1);
305                 }
306                 return -1; /* avoid compiler warning */
307         } else {
308                 /* parent reads from fds[0] */
309                 close(fds[1]);
310                 retval = 0;
311                 while (1) {
312                         res = read(fds[0], buffer, sizeof(buffer) - 1);
313                         if (res <= 0)
314                                 break;
315                         buffer[res] = '\0';
316                         if (res > len) {
317                                 dbg("callout len %d too short", len);
318                                 retval = -1;
319                         }
320                         if (value_set) {
321                                 dbg("callout value already set");
322                                 retval = -1;
323                         } else {
324                                 value_set = 1;
325                                 strncpy(value, buffer, len);
326                         }
327                 }
328                 dbg("callout returned '%s'", value);
329                 close(fds[0]);
330                 res = wait(&status);
331                 if (res < 0) {
332                         dbg("wait failed result %d", res);
333                         retval = -1;
334                 }
335
336 #ifndef __KLIBC__
337                 if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) {
338                         dbg("callout program status 0x%x", status);
339                         retval = -1;
340                 }
341 #endif
342         }
343         return retval;
344 }
345
346 static int do_callout(struct sysfs_class_device *class_dev, struct udevice *udev, struct sysfs_device *sysfs_device)
347 {
348         struct config_device *dev;
349         struct list_head *tmp;
350
351         list_for_each(tmp, &config_device_list) {
352                 dev = list_entry(tmp, struct config_device, node);
353                 if (dev->type != CALLOUT)
354                         continue;
355
356                 if (sysfs_device) {
357                         dbg("dev->bus='%s' sysfs_device->bus='%s'", dev->bus, sysfs_device->bus);
358                         if (strcasecmp(dev->bus, sysfs_device->bus) != 0)
359                                 continue;
360                 }
361
362                 /* substitute anything that needs to be in the program name */
363                 apply_format(udev, dev->exec_program);
364                 if (exec_callout(dev, udev->callout_value, NAME_SIZE))
365                         continue;
366                 if (strcmp_pattern(dev->id, udev->callout_value) != 0)
367                         continue;
368                 strfieldcpy(udev->name, dev->name);
369                 dbg("callout returned matching value '%s', '%s' becomes '%s'",
370                     dev->id, class_dev->name, udev->name);
371                 return 0;
372         }
373         return -ENODEV;
374 }
375
376 static int do_label(struct sysfs_class_device *class_dev, struct udevice *udev, struct sysfs_device *sysfs_device)
377 {
378         struct sysfs_attribute *tmpattr = NULL;
379         struct config_device *dev;
380         struct list_head *tmp;
381         char *c;
382
383         list_for_each(tmp, &config_device_list) {
384                 dev = list_entry(tmp, struct config_device, node);
385                 if (dev->type != LABEL)
386                         continue;
387
388                 if (sysfs_device) {
389                         dbg("dev->bus='%s' sysfs_device->bus='%s'", dev->bus, sysfs_device->bus);
390                         if (strcasecmp(dev->bus, sysfs_device->bus) != 0)
391                                 continue;
392                 }
393
394                 dbg("look for device attribute '%s'", dev->sysfs_file);
395                 /* try to find the attribute in the class device directory */
396                 tmpattr = sysfs_get_classdev_attr(class_dev, dev->sysfs_file);
397                 if (tmpattr)
398                         goto label_found;
399
400                 /* look in the class device directory if present */
401                 if (sysfs_device) {
402                         tmpattr = sysfs_get_device_attr(sysfs_device, dev->sysfs_file);
403                         if (tmpattr)
404                                 goto label_found;
405                 }
406
407                 continue;
408
409 label_found:
410                 c = tmpattr->value + strlen(tmpattr->value)-1;
411                 if (*c == '\n')
412                         *c = 0x00;
413                 dbg("compare attribute '%s' value '%s' with '%s'",
414                           dev->sysfs_file, tmpattr->value, dev->sysfs_value);
415                 if (strcmp_pattern(dev->sysfs_value, tmpattr->value) != 0)
416                         continue;
417
418                 strfieldcpy(udev->name, dev->name);
419                 dbg("found matching attribute '%s', '%s' becomes '%s' ",
420                     dev->sysfs_file, class_dev->name, udev->name);
421
422                 return 0;
423         }
424         return -ENODEV;
425 }
426
427 static int do_number(struct sysfs_class_device *class_dev, struct udevice *udev, struct sysfs_device *sysfs_device)
428 {
429         struct config_device *dev;
430         struct list_head *tmp;
431         char path[SYSFS_PATH_MAX];
432         int found;
433         char *temp = NULL;
434
435         /* we have to have a sysfs device for NUMBER to work */
436         if (!sysfs_device)
437                 return -ENODEV;
438
439         list_for_each(tmp, &config_device_list) {
440                 dev = list_entry(tmp, struct config_device, node);
441                 if (dev->type != NUMBER)
442                         continue;
443
444                 dbg("dev->bus='%s' sysfs_device->bus='%s'", dev->bus, sysfs_device->bus);
445                 if (strcasecmp(dev->bus, sysfs_device->bus) != 0)
446                         continue;
447
448                 found = 0;
449                 strfieldcpy(path, sysfs_device->path);
450                 temp = strrchr(path, '/');
451                 dbg("search '%s' in '%s', path='%s'", dev->id, temp, path);
452                 if (strstr(temp, dev->id) != NULL) {
453                         found = 1;
454                 } else {
455                         *temp = 0x00;
456                         temp = strrchr(path, '/');
457                         dbg("search '%s' in '%s', path='%s'", dev->id, temp, path);
458                         if (strstr(temp, dev->id) != NULL)
459                                 found = 1;
460                 }
461                 if (!found)
462                         continue;
463                 strfieldcpy(udev->name, dev->name);
464                 dbg("found matching id '%s', '%s' becomes '%s'",
465                     dev->id, class_dev->name, udev->name);
466                 return 0;
467         }
468         return -ENODEV;
469 }
470
471 static int do_topology(struct sysfs_class_device *class_dev, struct udevice *udev, struct sysfs_device *sysfs_device)
472 {
473         struct config_device *dev;
474         struct list_head *tmp;
475         char path[SYSFS_PATH_MAX];
476         int found;
477         char *temp = NULL;
478
479         /* we have to have a sysfs device for TOPOLOGY to work */
480         if (!sysfs_device)
481                 return -ENODEV;
482
483         list_for_each(tmp, &config_device_list) {
484                 dev = list_entry(tmp, struct config_device, node);
485                 if (dev->type != TOPOLOGY)
486                         continue;
487
488                 dbg("dev->bus='%s' sysfs_device->bus='%s'", dev->bus, sysfs_device->bus);
489                 if (strcasecmp(dev->bus, sysfs_device->bus) != 0)
490                         continue;
491
492                 found = 0;
493                 strfieldcpy(path, sysfs_device->path);
494                 temp = strrchr(path, '/');
495                 dbg("search '%s' in '%s', path='%s'", dev->place, temp, path);
496                 if (strstr(temp, dev->place) != NULL) {
497                         found = 1;
498                 } else {
499                         *temp = 0x00;
500                         temp = strrchr(path, '/');
501                         dbg("search '%s' in '%s', path='%s'", dev->place, temp, path);
502                         if (strstr(temp, dev->place) != NULL)
503                                 found = 1;
504                 }
505                 if (!found)
506                         continue;
507
508                 strfieldcpy(udev->name, dev->name);
509                 dbg("found matching place '%s', '%s' becomes '%s'",
510                     dev->place, class_dev->name, udev->name);
511                 return 0;
512         }
513         return -ENODEV;
514 }
515
516 static int do_replace(struct sysfs_class_device *class_dev, struct udevice *udev, struct sysfs_device *sysfs_device)
517 {
518         struct config_device *dev;
519         struct list_head *tmp;
520
521         list_for_each(tmp, &config_device_list) {
522                 dev = list_entry(tmp, struct config_device, node);
523                 if (dev->type != REPLACE)
524                         continue;
525
526                 dbg("compare name '%s' with '%s'", dev->kernel_name, class_dev->name);
527                 if (strcmp_pattern(dev->kernel_name, class_dev->name) != 0)
528                         continue;
529
530                 strfieldcpy(udev->name, dev->name);
531                 dbg("found name, '%s' becomes '%s'", dev->kernel_name, udev->name);
532                 
533                 return 0;
534         }
535         return -ENODEV;
536 }
537
538 static void do_kernelname(struct sysfs_class_device *class_dev, struct udevice *udev)
539 {
540         /* heh, this is pretty simple... */
541         strfieldcpy(udev->name, class_dev->name);
542 }
543
544 int namedev_name_device(struct sysfs_class_device *class_dev, struct udevice *udev)
545 {
546         struct sysfs_device *sysfs_device = NULL;
547         struct sysfs_class_device *class_dev_parent = NULL;
548         int retval = 0;
549         char *temp = NULL;
550         struct perm_device *perm;
551
552         udev->mode = 0;
553
554         /* find the sysfs_device for this class device */
555         /* Wouldn't it really be nice if libsysfs could do this for us? */
556         if (class_dev->sysdevice) {
557                 sysfs_device = class_dev->sysdevice;
558         } else {
559                 /* bah, let's go backwards up a level to see if the device is there,
560                  * as block partitions don't point to the physical device.  Need to fix that
561                  * up in the kernel...
562                  */
563                 if (strstr(class_dev->path, "block")) {
564                         dbg("looking at block device");
565                         if (isdigit(class_dev->path[strlen(class_dev->path)-1])) {
566                                 char path[SYSFS_PATH_MAX];
567
568                                 dbg("really is a partition");
569                                 strfieldcpy(path, class_dev->path);
570                                 temp = strrchr(path, '/');
571                                 *temp = 0x00;
572                                 dbg("looking for a class device at '%s'", path);
573                                 class_dev_parent = sysfs_open_class_device(path);
574                                 if (class_dev_parent == NULL) {
575                                         dbg("sysfs_open_class_device at '%s' failed", path);
576                                 } else {
577                                         dbg("class_dev_parent->name='%s'", class_dev_parent->name);
578                                         if (class_dev_parent->sysdevice)
579                                                 sysfs_device = class_dev_parent->sysdevice;
580                                 }
581                         }
582                 }
583         }
584
585         if (sysfs_device) {
586                 dbg("sysfs_device->path='%s'", sysfs_device->path);
587                 dbg("sysfs_device->bus_id='%s'", sysfs_device->bus_id);
588                 dbg("sysfs_device->bus='%s'", sysfs_device->bus);
589                 strfieldcpy(udev->bus_id, sysfs_device->bus_id);
590         } else {
591                 dbg("class_dev->name = '%s'", class_dev->name);
592         }
593
594         build_kernel_number(class_dev, udev);
595
596         /* rules are looked at in priority order */
597         retval = do_callout(class_dev, udev, sysfs_device);
598         if (retval == 0)
599                 goto found;
600
601         retval = do_label(class_dev, udev, sysfs_device);
602         if (retval == 0)
603                 goto found;
604
605         retval = do_number(class_dev, udev, sysfs_device);
606         if (retval == 0)
607                 goto found;
608
609         retval = do_topology(class_dev, udev, sysfs_device);
610         if (retval == 0)
611                 goto found;
612
613         retval = do_replace(class_dev, udev, sysfs_device);
614         if (retval == 0)
615                 goto found;
616
617         do_kernelname(class_dev, udev);
618         goto done;
619
620 found:
621         /* substitute placeholder in NAME  */
622         apply_format(udev, udev->name);
623
624 done:
625         perm = find_perm(udev->name);
626         if (perm) {
627                 udev->mode = perm->mode;
628                 strfieldcpy(udev->owner, perm->owner);
629                 strfieldcpy(udev->group, perm->group);
630         } else {
631                 /* no matching perms found :( */
632                 udev->mode = get_default_mode(class_dev);
633                 udev->owner[0] = 0x00;
634                 udev->group[0] = 0x00;
635         }
636         dbg("name, '%s' is going to have owner='%s', group='%s', mode = %#o",
637             udev->name, udev->owner, udev->group, udev->mode);
638
639         if (class_dev_parent)
640                 sysfs_close_class_device(class_dev_parent);
641
642         return 0;
643 }
644
645 int namedev_init(void)
646 {
647         int retval;
648
649         retval = namedev_init_rules();
650         if (retval)
651                 return retval;
652
653         retval = namedev_init_permissions();
654         if (retval)
655                 return retval;
656
657         dump_config_dev_list();
658         dump_perm_dev_list();
659         return retval;
660 }