chiark / gitweb /
b3b23521d6cf154f1554921546171ddb480809ce
[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 <time.h>
33 #include <sys/wait.h>
34 #include <sys/stat.h>
35
36 #include "list.h"
37 #include "udev.h"
38 #include "udev_version.h"
39 #include "namedev.h"
40 #include "libsysfs/libsysfs.h"
41 #include "klibc_fixups.h"
42
43 LIST_HEAD(config_device_list);
44 LIST_HEAD(perm_device_list);
45
46 /* compare string with pattern (supports * ? [0-9] [!A-Z]) */
47 static int strcmp_pattern(const char *p, const char *s)
48 {
49         if (*s == '\0') {
50                 while (*p == '*')
51                         p++;
52                 return (*p != '\0');
53         }
54         switch (*p) {
55         case '[':
56                 {
57                         int not = 0;
58                         p++;
59                         if (*p == '!') {
60                                 not = 1;
61                                 p++;
62                         }
63                         while (*p && (*p != ']')) {
64                                 int match = 0;
65                                 if (p[1] == '-') {
66                                         if ((*s >= *p) && (*s <= p[2]))
67                                                 match = 1;
68                                         p += 3;
69                                 } else {
70                                         match = (*p == *s);
71                                         p++;
72                                 }
73                                 if (match ^ not) {
74                                         while (*p && (*p != ']'))
75                                                 p++;
76                                         return strcmp_pattern(p+1, s+1);
77                                 }
78                         }
79                 }
80                 break;
81         case '*':
82                 if (strcmp_pattern(p, s+1))
83                         return strcmp_pattern(p+1, s);
84                 return 0;
85         case '\0':
86                 if (*s == '\0') {
87                         return 0;
88                 }
89                 break;
90         default:
91                 if ((*p == *s) || (*p == '?'))
92                         return strcmp_pattern(p+1, s+1);
93                 break;
94         }
95         return 1;
96 }
97
98 #define copy_var(a, b, var)             \
99         if (b->var)                     \
100                 a->var = b->var;
101
102 #define copy_string(a, b, var)          \
103         if (strlen(b->var))             \
104                 strcpy(a->var, b->var);
105
106 int add_perm_dev(struct perm_device *new_dev)
107 {
108         struct perm_device *dev;
109         struct perm_device *tmp_dev;
110
111         /* update the values if we already have the device */
112         list_for_each_entry(dev, &perm_device_list, node) {
113                 if (strcmp_pattern(new_dev->name, dev->name))
114                         continue;
115                 copy_var(dev, new_dev, mode);
116                 copy_string(dev, new_dev, owner);
117                 copy_string(dev, new_dev, group);
118                 return 0;
119         }
120
121         /* not found, add new structure to the perm list */
122         tmp_dev = malloc(sizeof(*tmp_dev));
123         if (!tmp_dev)
124                 return -ENOMEM;
125         memcpy(tmp_dev, new_dev, sizeof(*tmp_dev));
126         list_add_tail(&tmp_dev->node, &perm_device_list);
127         //dump_perm_dev(tmp_dev);
128         return 0;
129 }
130
131 static struct perm_device *find_perm(char *name)
132 {
133         struct perm_device *perm;
134
135         list_for_each_entry(perm, &perm_device_list, node) {
136                 if (strcmp_pattern(perm->name, name))
137                         continue;
138                 return perm;
139         }
140         return NULL;
141 }
142
143 static mode_t get_default_mode(struct sysfs_class_device *class_dev)
144 {
145         mode_t mode = 0600;     /* default to owner rw only */
146
147         if (strlen(default_mode_str) != 0) {
148                 mode = strtol(default_mode_str, NULL, 8);
149         }
150         return mode;
151 }
152
153 static void apply_format(struct udevice *udev, unsigned char *string)
154 {
155         char temp[NAME_SIZE];
156         char temp1[NAME_SIZE];
157         char *tail;
158         char *pos;
159         char *pos2;
160         char *pos3;
161         int num;
162
163         pos = string;
164         while (1) {
165                 num = 0;
166                 pos = strchr(pos, '%');
167
168                 if (pos) {
169                         pos[0] = '\0';
170                         tail = pos+1;
171                         if (isdigit(tail[0])) {
172                                 num = (int) strtoul(&pos[1], &tail, 10);
173                                 if (tail == NULL) {
174                                         dbg("format parsing error '%s'", pos+1);
175                                         break;
176                                 }
177                         }
178                         strfieldcpy(temp, tail+1);
179
180                         switch (tail[0]) {
181                         case 'b':
182                                 if (strlen(udev->bus_id) == 0)
183                                         break;
184                                 strcat(pos, udev->bus_id);
185                                 dbg("substitute bus_id '%s'", udev->bus_id);
186                                 break;
187                         case 'k':
188                                 if (strlen(udev->kernel_name) == 0)
189                                         break;
190                                 strcat(pos, udev->kernel_name);
191                                 dbg("substitute kernel name '%s'", udev->kernel_name);
192                                 break;
193                         case 'n':
194                                 if (strlen(udev->kernel_number) == 0)
195                                         break;
196                                 strcat(pos, udev->kernel_number);
197                                 dbg("substitute kernel number '%s'", udev->kernel_number);
198                                 break;
199                         case 'm':
200                                 sprintf(pos, "%u", udev->minor);
201                                 dbg("substitute minor number '%u'", udev->minor);
202                                 break;
203                         case 'M':
204                                 sprintf(pos, "%u", udev->major);
205                                 dbg("substitute major number '%u'", udev->major);
206                                 break;
207                         case 'c':
208                                 if (strlen(udev->program_result) == 0)
209                                         break;
210                                 if (num) {
211                                         /* get part of return string */
212                                         strncpy(temp1, udev->program_result, sizeof(temp1));
213                                         pos2 = temp1;
214                                         while (num) {
215                                                 num--;
216                                                 pos3 = strsep(&pos2, " ");
217                                                 if (pos3 == NULL) {
218                                                         dbg("requested part of result string not found");
219                                                         break;
220                                                 }
221                                         }
222                                         if (pos3) {
223                                                 strcat(pos, pos3);
224                                                 dbg("substitute part of result string '%s'", pos3);
225                                         }
226                                 } else {
227                                         strcat(pos, udev->program_result);
228                                         dbg("substitute result string '%s'", udev->program_result);
229                                 }
230                                 break;
231                         case '%':
232                                 strcat(pos, "%");
233                                 pos++;
234                                 break;
235                         default:
236                                 dbg("unknown substitution type '%%%c'", pos[1]);
237                                 break;
238                         }
239                         strcat(string, temp);
240                 } else
241                         break;
242         }
243 }
244
245 static struct bus_file {
246         char *bus;
247         char *file;
248 } bus_files[] = {
249         { .bus = "scsi",        .file = "vendor" },
250         { .bus = "usb",         .file = "idVendor" },
251         { .bus = "usb-serial",  .file = "detach_state" },
252         { .bus = "ide",         .file = "detach_state" },
253         { .bus = "pci",         .file = "vendor" },
254         {}
255 };
256
257 #define SECONDS_TO_WAIT_FOR_FILE        10
258 static void wait_for_device_to_initialize(struct sysfs_device *sysfs_device)
259 {
260         /* sleep until we see the file for this specific bus type show up this
261          * is needed because we can easily out-run the kernel in looking for
262          * these files before the paticular subsystem has created them in the
263          * sysfs tree properly.
264          *
265          * And people thought that the /sbin/hotplug event system was going to
266          * be slow, poo on you for arguing that before even testing it...
267          */
268         struct bus_file *b = &bus_files[0];
269         struct sysfs_attribute *tmpattr;
270         int loop;
271
272         while (1) {
273                 if (b->bus == NULL)
274                         break;
275                 if (strcmp(sysfs_device->bus, b->bus) == 0) {
276                         tmpattr = NULL;
277                         loop = SECONDS_TO_WAIT_FOR_FILE;
278                         while (loop--) {
279                                 dbg("looking for file '%s' on bus '%s'", b->file, b->bus);
280                                 tmpattr = sysfs_get_device_attr(sysfs_device, b->file);
281                                 if (tmpattr) {
282                                         /* found it! */
283                                         goto exit;
284                                 }
285                                 /* sleep to give the kernel a chance to create the file */
286                                 sleep(1);
287                         }
288                         dbg("Timed out waiting for '%s' file, continuing on anyway...", b->file);
289                         goto exit;
290                 }
291                 b++;
292         }
293         dbg("Did not find bus type '%s' on list of bus_id_files, contact greg@kroah.com", sysfs_device->bus);
294 exit:
295         return; /* here to prevent compiler warning... */
296 }
297
298 static int execute_program(char *path, char *value, int len)
299 {
300         int retval;
301         int res;
302         int status;
303         int fds[2];
304         pid_t pid;
305         int value_set = 0;
306         char buffer[256];
307         char *pos;
308         char *args[PROGRAM_MAXARG];
309         int i;
310
311         dbg("executing '%s'", path);
312         retval = pipe(fds);
313         if (retval != 0) {
314                 dbg("pipe failed");
315                 return -1;
316         }
317         pid = fork();
318         if (pid == -1) {
319                 dbg("fork failed");
320                 return -1;
321         }
322
323         if (pid == 0) {
324                 /* child */
325                 close(STDOUT_FILENO);
326                 dup(fds[1]);    /* dup write side of pipe to STDOUT */
327                 if (strchr(path, ' ')) {
328                         /* exec with arguments */
329                         pos = path;
330                         for (i=0; i < PROGRAM_MAXARG-1; i++) {
331                                 args[i] = strsep(&pos, " ");
332                                 if (args[i] == NULL)
333                                         break;
334                         }
335                         if (args[i]) {
336                                 dbg("too many args - %d", i);
337                                 args[i] = NULL;
338                         }
339                         retval = execve(args[0], args, main_envp);
340                 } else {
341                         retval = execve(path, main_argv, main_envp);
342                 }
343                 if (retval != 0) {
344                         dbg("child execve failed");
345                         exit(1);
346                 }
347                 return -1; /* avoid compiler warning */
348         } else {
349                 /* parent reads from fds[0] */
350                 close(fds[1]);
351                 retval = 0;
352                 while (1) {
353                         res = read(fds[0], buffer, sizeof(buffer) - 1);
354                         if (res <= 0)
355                                 break;
356                         buffer[res] = '\0';
357                         if (res > len) {
358                                 dbg("result len %d too short", len);
359                                 retval = -1;
360                         }
361                         if (value_set) {
362                                 dbg("result value already set");
363                                 retval = -1;
364                         } else {
365                                 value_set = 1;
366                                 strncpy(value, buffer, len);
367                                 pos = value + strlen(value)-1;
368                                 if (pos[0] == '\n')
369                                 pos[0] = '\0';
370                                 dbg("result is '%s'", value);
371                         }
372                 }
373                 close(fds[0]);
374                 res = wait(&status);
375                 if (res < 0) {
376                         dbg("wait failed result %d", res);
377                         retval = -1;
378                 }
379
380                 if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) {
381                         dbg("exec program status 0x%x", status);
382                         retval = -1;
383                 }
384         }
385         return retval;
386 }
387
388 static int compare_sysfs_attribute(struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device, struct sysfs_pair *pair)
389 {
390         struct sysfs_attribute *tmpattr = NULL;
391         char *c;
392
393         if ((pair == NULL) || (pair->file[0] == '\0') || (pair->value == '\0'))
394                 return -ENODEV;
395
396         dbg("look for device attribute '%s'", pair->file);
397         /* try to find the attribute in the class device directory */
398         tmpattr = sysfs_get_classdev_attr(class_dev, pair->file);
399         if (tmpattr)
400                 goto label_found;
401
402         /* look in the class device directory if present */
403         if (sysfs_device) {
404                 tmpattr = sysfs_get_device_attr(sysfs_device, pair->file);
405                 if (tmpattr)
406                         goto label_found;
407         }
408         return -ENODEV;
409
410 label_found:
411         c = tmpattr->value + strlen(tmpattr->value)-1;
412         if (*c == '\n')
413                 *c = 0x00;
414         dbg("compare attribute '%s' value '%s' with '%s'",
415                   pair->file, tmpattr->value, pair->value);
416         if (strcmp_pattern(pair->value, tmpattr->value) != 0)
417                 return -ENODEV;
418
419         dbg("found matching attribute '%s' with value '%s'",
420             pair->file, pair->value);
421         return 0;
422 }
423
424 static int match_sysfs_pairs(struct config_device *dev, struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device)
425 {
426         struct sysfs_pair *pair;
427         int i;
428
429         for (i = 0; i < MAX_SYSFS_PAIRS; ++i) {
430                 pair = &dev->sysfs_pair[i];
431                 if ((pair->file[0] == '\0') || (pair->value[0] == '\0'))
432                         break;
433                 if (compare_sysfs_attribute(class_dev, sysfs_device, pair) != 0) {
434                         dbg("sysfs attribute doesn't match");
435                         return -ENODEV;
436                 }
437         }
438
439         return 0;
440 }
441
442 static int match_id(struct config_device *dev, struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device)
443 {
444         char path[SYSFS_PATH_MAX];
445         int found;
446         char *temp = NULL;
447
448         /* we have to have a sysfs device for ID to work */
449         if (!sysfs_device)
450                 return -ENODEV;
451
452         found = 0;
453         strfieldcpy(path, sysfs_device->path);
454         temp = strrchr(path, '/');
455         dbg("search '%s' in '%s', path='%s'", dev->id, temp, path);
456         if (strstr(temp, dev->id) != NULL) {
457                 found = 1;
458         } else {
459                 *temp = 0x00;
460                 temp = strrchr(path, '/');
461                 dbg("search '%s' in '%s', path='%s'", dev->id, temp, path);
462                 if (strstr(temp, dev->id) != NULL)
463                         found = 1;
464         }
465         if (!found) {
466                 dbg("id doesn't match");
467                 return -ENODEV;
468         }
469
470         return 0;
471 }
472
473 static int match_place(struct config_device *dev, struct sysfs_class_device *class_dev, struct sysfs_device *sysfs_device)
474 {
475         char path[SYSFS_PATH_MAX];
476         int found;
477         char *temp = NULL;
478
479         /* we have to have a sysfs device for PLACE to work */
480         if (!sysfs_device)
481                 return -ENODEV;
482
483         found = 0;
484         strfieldcpy(path, sysfs_device->path);
485         temp = strrchr(path, '/');
486         dbg("search '%s' in '%s', path='%s'", dev->place, temp, path);
487         if (strstr(temp, dev->place) != NULL) {
488                 found = 1;
489         } else {
490                 *temp = 0x00;
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         }
496         if (!found) {
497                 dbg("place doesn't match");
498                 return -ENODEV;
499         }
500
501         return 0;
502 }
503
504 static struct sysfs_device *get_sysfs_device(struct sysfs_class_device *class_dev)
505 {
506         struct sysfs_device *sysfs_device;
507         struct sysfs_class_device *class_dev_parent;
508         struct timespec tspec;
509         int loop;
510
511         /* Figure out where the device symlink is at.  For char devices this will
512          * always be in the class_dev->path.  But for block devices, it's different.
513          * The main block device will have the device symlink in it's path, but
514          * all partitions have the symlink in its parent directory.
515          * But we need to watch out for block devices that do not have parents, yet
516          * look like a partition (fd0, loop0, etc.)  They all do not have a device
517          * symlink yet.  We do sit and spin on waiting for them right now, we should
518          * possibly have a whitelist for these devices here...
519          */
520         class_dev_parent = sysfs_get_classdev_parent(class_dev);
521         if (class_dev_parent) 
522                 dbg("Really a partition");
523
524         tspec.tv_sec = 0;
525         tspec.tv_nsec = 10000000;  /* sleep 10 millisec */
526         loop = 10;
527         while (loop--) {
528                 nanosleep(&tspec, NULL);
529                 if (class_dev_parent)
530                         sysfs_device = sysfs_get_classdev_device(class_dev_parent);
531                 else
532                         sysfs_device = sysfs_get_classdev_device(class_dev);
533
534                 if (sysfs_device != NULL)
535                         goto device_found;
536         }
537         dbg("Timed out waiting for device symlink, continuing on anyway...");
538         
539 device_found:
540         /* We have another issue with just the wait above - the sysfs part of
541          * the kernel may not be quick enough to have created the link to the
542          * device under the "bus" subsystem. Due to this, the sysfs_device->bus
543          * will not contain the actual bus name :(
544          *
545          * Libsysfs now provides a new API sysfs_get_device_bus(), so use it
546          * if needed
547          */
548         if (sysfs_device) {
549                 
550                 if (sysfs_device->bus[0] != '\0')
551                         goto bus_found;
552                 
553                 loop = 10;
554                 tspec.tv_nsec = 10000000;
555                 while (loop--) {
556                         nanosleep(&tspec, NULL);
557                         sysfs_get_device_bus(sysfs_device);
558                         
559                         if (sysfs_device->bus[0] != '\0')
560                                 goto bus_found;
561                 }
562                 dbg("Timed out waiting to find the device bus, continuing on anyway\n");
563                 goto exit;
564 bus_found:
565                 dbg("Device %s is registered with bus %s\n",
566                                 sysfs_device->name, sysfs_device->bus);
567         }
568 exit:
569         return sysfs_device;
570 }
571
572 int namedev_name_device(struct sysfs_class_device *class_dev, struct udevice *udev)
573 {
574         struct sysfs_device *sysfs_device = NULL;
575         struct config_device *dev;
576         struct perm_device *perm;
577         char *pos;
578
579         udev->mode = 0;
580
581         /* find the sysfs_device associated with this class device */
582         sysfs_device = get_sysfs_device(class_dev);
583         if (sysfs_device) {
584                 dbg("sysfs_device->path='%s'", sysfs_device->path);
585                 dbg("sysfs_device->bus_id='%s'", sysfs_device->bus_id);
586                 dbg("sysfs_device->bus='%s'", sysfs_device->bus);
587                 strfieldcpy(udev->bus_id, sysfs_device->bus_id);
588                 wait_for_device_to_initialize(sysfs_device);
589         } else {
590                 dbg("class_dev->name = '%s'", class_dev->name);
591         }
592
593         strfieldcpy(udev->kernel_name, class_dev->name);
594
595         /* get kernel number */
596         pos = class_dev->name + strlen(class_dev->name);
597         while (isdigit(*(pos-1)))
598                 pos--;
599         strfieldcpy(udev->kernel_number, pos);
600         dbg("kernel_number='%s'", udev->kernel_number);
601
602         /* look for a matching rule to apply */
603         list_for_each_entry(dev, &config_device_list, node) {
604                 dbg("process rule");
605
606                 /* check for matching bus value */
607                 if (dev->bus[0] != '\0') {
608                         if (sysfs_device == NULL) {
609                                 dbg("device has no bus");
610                                 continue;
611                         }
612                         dbg("check for " FIELD_BUS " dev->bus='%s' sysfs_device->bus='%s'", dev->bus, sysfs_device->bus);
613                         if (strcmp_pattern(dev->bus, sysfs_device->bus) != 0) {
614                                 dbg(FIELD_BUS " is not matching");
615                                 continue;
616                         } else {
617                                 dbg(FIELD_BUS " matches");
618                         }
619                 }
620
621                 /* check for matching kernel name*/
622                 if (dev->kernel[0] != '\0') {
623                         dbg("check for " FIELD_KERNEL " dev->kernel='%s' class_dev->name='%s'", dev->kernel, class_dev->name);
624                         if (strcmp_pattern(dev->kernel, class_dev->name) != 0) {
625                                 dbg(FIELD_KERNEL " is not matching");
626                                 continue;
627                         } else {
628                                 dbg(FIELD_KERNEL " matches");
629                         }
630                 }
631
632                 /* check for matching bus id */
633                 if (dev->id[0] != '\0') {
634                         dbg("check " FIELD_ID);
635                         if (match_id(dev, class_dev, sysfs_device) != 0) {
636                                 dbg(FIELD_ID " is not matching");
637                                 continue;
638                         } else {
639                                 dbg(FIELD_ID " matches");
640                         }
641                 }
642
643                 /* check for matching place of device */
644                 if (dev->place[0] != '\0') {
645                         dbg("check " FIELD_PLACE);
646                         if (match_place(dev, class_dev, sysfs_device) != 0) {
647                                 dbg(FIELD_PLACE " is not matching");
648                                 continue;
649                         } else {
650                                 dbg(FIELD_PLACE " matches");
651                         }
652                 }
653
654                 /* check for matching sysfs pairs */
655                 if (dev->sysfs_pair[0].file[0] != '\0') {
656                         dbg("check " FIELD_SYSFS " pairs");
657                         if (match_sysfs_pairs(dev, class_dev, sysfs_device) != 0) {
658                                 dbg(FIELD_SYSFS " is not matching");
659                                 continue;
660                         } else {
661                                 dbg(FIELD_SYSFS " matches");
662                         }
663                 }
664
665                 /* execute external program */
666                 if (dev->program[0] != '\0') {
667                         dbg("check " FIELD_PROGRAM);
668                         apply_format(udev, dev->program);
669                         if (execute_program(dev->program, udev->program_result, NAME_SIZE) != 0) {
670                                 dbg(FIELD_PROGRAM " returned nozero");
671                                 continue;
672                         } else {
673                                 dbg(FIELD_PROGRAM " returned successful");
674                         }
675                 }
676
677                 /* check for matching result of external program */
678                 if (dev->result[0] != '\0') {
679                         dbg("check for " FIELD_RESULT
680                             " dev->result='%s', udev->program_result='%s'",
681                             dev->result, udev->program_result);
682                         if (strcmp_pattern(dev->result, udev->program_result) != 0) {
683                                 dbg(FIELD_RESULT " is not matching");
684                                 continue;
685                         } else {
686                                 dbg(FIELD_RESULT " matches");
687                         }
688                 }
689
690                 /* check if we are instructed to ignore this device */
691                 if (dev->name[0] == '\0') {
692                         dbg("instructed to ignore this device");
693                         return -1;
694                 }
695
696                 /* Yup, this rule belongs to us! */
697                 dbg("found matching rule, '%s' becomes '%s'", dev->kernel, dev->name);
698                 strfieldcpy(udev->name, dev->name);
699                 strfieldcpy(udev->symlink, dev->symlink);
700                 goto found;
701         }
702
703         /* no rule was found so we use the kernel name */
704         strfieldcpy(udev->name, class_dev->name);
705         goto done;
706
707 found:
708         /* substitute placeholder */
709         apply_format(udev, udev->name);
710         apply_format(udev, udev->symlink);
711
712 done:
713         perm = find_perm(udev->name);
714         if (perm) {
715                 udev->mode = perm->mode;
716                 strfieldcpy(udev->owner, perm->owner);
717                 strfieldcpy(udev->group, perm->group);
718         } else {
719                 /* no matching perms found :( */
720                 udev->mode = get_default_mode(class_dev);
721                 udev->owner[0] = 0x00;
722                 udev->group[0] = 0x00;
723         }
724         dbg("name, '%s' is going to have owner='%s', group='%s', mode = %#o",
725             udev->name, udev->owner, udev->group, udev->mode);
726
727         return 0;
728 }
729
730 int namedev_init(void)
731 {
732         int retval;
733
734         retval = namedev_init_rules();
735         if (retval)
736                 return retval;
737
738         retval = namedev_init_permissions();
739         if (retval)
740                 return retval;
741
742         dump_config_dev_list();
743         dump_perm_dev_list();
744         return retval;
745 }