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