chiark / gitweb /
[PATCH] fix permission handling logic
[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
382         list_for_each(tmp, &config_device_list) {
383                 dev = list_entry(tmp, struct config_device, node);
384                 if (dev->type != LABEL)
385                         continue;
386
387                 if (sysfs_device) {
388                         dbg("dev->bus='%s' sysfs_device->bus='%s'", dev->bus, sysfs_device->bus);
389                         if (strcasecmp(dev->bus, sysfs_device->bus) != 0)
390                                 continue;
391                 }
392
393                 dbg("look for device attribute '%s'", dev->sysfs_file);
394                 /* try to find the attribute in the class device directory */
395                 tmpattr = sysfs_get_classdev_attr(class_dev, dev->sysfs_file);
396                 if (tmpattr)
397                         goto label_found;
398
399                 /* look in the class device directory if present */
400                 if (sysfs_device) {
401                         tmpattr = sysfs_get_device_attr(sysfs_device, dev->sysfs_file);
402                         if (tmpattr)
403                                 goto label_found;
404                 }
405
406                 continue;
407
408 label_found:
409                 tmpattr->value[strlen(tmpattr->value)-1] = 0x00;
410                 dbg("compare attribute '%s' value '%s' with '%s'",
411                           dev->sysfs_file, tmpattr->value, dev->sysfs_value);
412                 if (strcmp(dev->sysfs_value, tmpattr->value) != 0)
413                         continue;
414
415                 strfieldcpy(udev->name, dev->name);
416                 dbg("found matching attribute '%s', '%s' becomes '%s' ",
417                     dev->sysfs_file, class_dev->name, udev->name);
418
419                 return 0;
420         }
421         return -ENODEV;
422 }
423
424 static int do_number(struct sysfs_class_device *class_dev, struct udevice *udev, struct sysfs_device *sysfs_device)
425 {
426         struct config_device *dev;
427         struct list_head *tmp;
428         char path[SYSFS_PATH_MAX];
429         int found;
430         char *temp = NULL;
431
432         /* we have to have a sysfs device for NUMBER to work */
433         if (!sysfs_device)
434                 return -ENODEV;
435
436         list_for_each(tmp, &config_device_list) {
437                 dev = list_entry(tmp, struct config_device, node);
438                 if (dev->type != NUMBER)
439                         continue;
440
441                 dbg("dev->bus='%s' sysfs_device->bus='%s'", dev->bus, sysfs_device->bus);
442                 if (strcasecmp(dev->bus, sysfs_device->bus) != 0)
443                         continue;
444
445                 found = 0;
446                 strfieldcpy(path, sysfs_device->path);
447                 temp = strrchr(path, '/');
448                 dbg("search '%s' in '%s', path='%s'", dev->id, temp, path);
449                 if (strstr(temp, dev->id) != NULL) {
450                         found = 1;
451                 } else {
452                         *temp = 0x00;
453                         temp = strrchr(path, '/');
454                         dbg("search '%s' in '%s', path='%s'", dev->id, temp, path);
455                         if (strstr(temp, dev->id) != NULL)
456                                 found = 1;
457                 }
458                 if (!found)
459                         continue;
460                 strfieldcpy(udev->name, dev->name);
461                 dbg("found matching id '%s', '%s' becomes '%s'",
462                     dev->id, class_dev->name, udev->name);
463                 return 0;
464         }
465         return -ENODEV;
466 }
467
468 static int do_topology(struct sysfs_class_device *class_dev, struct udevice *udev, struct sysfs_device *sysfs_device)
469 {
470         struct config_device *dev;
471         struct list_head *tmp;
472         char path[SYSFS_PATH_MAX];
473         int found;
474         char *temp = NULL;
475
476         /* we have to have a sysfs device for TOPOLOGY to work */
477         if (!sysfs_device)
478                 return -ENODEV;
479
480         list_for_each(tmp, &config_device_list) {
481                 dev = list_entry(tmp, struct config_device, node);
482                 if (dev->type != TOPOLOGY)
483                         continue;
484
485                 dbg("dev->bus='%s' sysfs_device->bus='%s'", dev->bus, sysfs_device->bus);
486                 if (strcasecmp(dev->bus, sysfs_device->bus) != 0)
487                         continue;
488
489                 found = 0;
490                 strfieldcpy(path, sysfs_device->path);
491                 temp = strrchr(path, '/');
492                 dbg("search '%s' in '%s', path='%s'", dev->place, temp, path);
493                 if (strstr(temp, dev->place) != NULL) {
494                         found = 1;
495                 } else {
496                         *temp = 0x00;
497                         temp = strrchr(path, '/');
498                         dbg("search '%s' in '%s', path='%s'", dev->place, temp, path);
499                         if (strstr(temp, dev->place) != NULL)
500                                 found = 1;
501                 }
502                 if (!found)
503                         continue;
504
505                 strfieldcpy(udev->name, dev->name);
506                 dbg("found matching place '%s', '%s' becomes '%s'",
507                     dev->place, class_dev->name, udev->name);
508                 return 0;
509         }
510         return -ENODEV;
511 }
512
513 static int do_replace(struct sysfs_class_device *class_dev, struct udevice *udev, struct sysfs_device *sysfs_device)
514 {
515         struct config_device *dev;
516         struct list_head *tmp;
517
518         list_for_each(tmp, &config_device_list) {
519                 dev = list_entry(tmp, struct config_device, node);
520                 if (dev->type != REPLACE)
521                         continue;
522
523                 dbg("compare name '%s' with '%s'", dev->kernel_name, class_dev->name);
524                 if (strcmp_pattern(dev->kernel_name, class_dev->name) != 0)
525                         continue;
526
527                 strfieldcpy(udev->name, dev->name);
528                 dbg("found name, '%s' becomes '%s'", dev->kernel_name, udev->name);
529                 
530                 return 0;
531         }
532         return -ENODEV;
533 }
534
535 static void do_kernelname(struct sysfs_class_device *class_dev, struct udevice *udev)
536 {
537         /* heh, this is pretty simple... */
538         strfieldcpy(udev->name, class_dev->name);
539 }
540
541 int namedev_name_device(struct sysfs_class_device *class_dev, struct udevice *udev)
542 {
543         struct sysfs_device *sysfs_device = NULL;
544         struct sysfs_class_device *class_dev_parent = NULL;
545         int retval = 0;
546         char *temp = NULL;
547         struct perm_device *perm;
548
549         udev->mode = 0;
550
551         /* find the sysfs_device for this class device */
552         /* Wouldn't it really be nice if libsysfs could do this for us? */
553         if (class_dev->sysdevice) {
554                 sysfs_device = class_dev->sysdevice;
555         } else {
556                 /* bah, let's go backwards up a level to see if the device is there,
557                  * as block partitions don't point to the physical device.  Need to fix that
558                  * up in the kernel...
559                  */
560                 if (strstr(class_dev->path, "block")) {
561                         dbg("looking at block device");
562                         if (isdigit(class_dev->path[strlen(class_dev->path)-1])) {
563                                 char path[SYSFS_PATH_MAX];
564
565                                 dbg("really is a partition");
566                                 strfieldcpy(path, class_dev->path);
567                                 temp = strrchr(path, '/');
568                                 *temp = 0x00;
569                                 dbg("looking for a class device at '%s'", path);
570                                 class_dev_parent = sysfs_open_class_device(path);
571                                 if (class_dev_parent == NULL) {
572                                         dbg("sysfs_open_class_device at '%s' failed", path);
573                                 } else {
574                                         dbg("class_dev_parent->name='%s'", class_dev_parent->name);
575                                         if (class_dev_parent->sysdevice)
576                                                 sysfs_device = class_dev_parent->sysdevice;
577                                 }
578                         }
579                 }
580         }
581                 
582         if (sysfs_device) {
583                 dbg("sysfs_device->path='%s'", sysfs_device->path);
584                 dbg("sysfs_device->bus_id='%s'", sysfs_device->bus_id);
585                 dbg("sysfs_device->bus='%s'", sysfs_device->bus);
586                 strfieldcpy(udev->bus_id, sysfs_device->bus_id);
587         } else {
588                 dbg("class_dev->name = '%s'", class_dev->name);
589         }
590
591         build_kernel_number(class_dev, udev);
592
593         /* rules are looked at in priority order */
594         retval = do_callout(class_dev, udev, sysfs_device);
595         if (retval == 0)
596                 goto found;
597
598         retval = do_label(class_dev, udev, sysfs_device);
599         if (retval == 0)
600                 goto found;
601
602         retval = do_number(class_dev, udev, sysfs_device);
603         if (retval == 0)
604                 goto found;
605
606         retval = do_topology(class_dev, udev, sysfs_device);
607         if (retval == 0)
608                 goto found;
609
610         retval = do_replace(class_dev, udev, sysfs_device);
611         if (retval == 0)
612                 goto found;
613
614         do_kernelname(class_dev, udev);
615         goto done;
616
617 found:
618         /* substitute placeholder in NAME  */
619         apply_format(udev, udev->name);
620
621 done:
622         perm = find_perm(udev->name);
623         if (perm) {
624                 udev->mode = perm->mode;
625                 strfieldcpy(udev->owner, perm->owner);
626                 strfieldcpy(udev->group, perm->group);
627         } else {
628                 /* no matching perms found :( */
629                 udev->mode = get_default_mode(class_dev);
630                 udev->owner[0] = 0x00;
631                 udev->group[0] = 0x00;
632         }
633         dbg("name, '%s' is going to have owner='%s', group='%s', mode = %#o",
634             udev->name, udev->owner, udev->group, udev->mode);
635
636         if (class_dev_parent)
637                 sysfs_close_class_device(class_dev_parent);
638
639         return 0;
640 }
641
642 int namedev_init(void)
643 {
644         int retval;
645         
646         retval = namedev_init_rules();
647         if (retval)
648                 return retval;
649
650         retval = namedev_init_permissions();
651         if (retval)
652                 return retval;
653
654         dump_config_dev_list();
655         dump_perm_dev_list();
656         return retval;
657 }