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