devices.c revision 029d44e6aef334d2f0f03cbc9a5609cddc1b23e3
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <errno.h>
18#include <stddef.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <sys/stat.h>
22#include <sys/types.h>
23
24#include <fcntl.h>
25#include <dirent.h>
26#include <unistd.h>
27#include <string.h>
28
29#include <sys/socket.h>
30#include <sys/un.h>
31#include <linux/netlink.h>
32
33#ifdef HAVE_SELINUX
34#include <selinux/selinux.h>
35#include <selinux/label.h>
36#endif
37
38#include <private/android_filesystem_config.h>
39#include <sys/time.h>
40#include <asm/page.h>
41#include <sys/wait.h>
42
43#include <cutils/list.h>
44#include <cutils/uevent.h>
45
46#include "devices.h"
47#include "util.h"
48#include "log.h"
49
50#define SYSFS_PREFIX    "/sys"
51#define FIRMWARE_DIR1   "/etc/firmware"
52#define FIRMWARE_DIR2   "/vendor/firmware"
53#define FIRMWARE_DIR3   "/firmware/image"
54
55#ifdef HAVE_SELINUX
56static struct selabel_handle *sehandle;
57#endif
58
59static int device_fd = -1;
60
61struct uevent {
62    const char *action;
63    const char *path;
64    const char *subsystem;
65    const char *firmware;
66    const char *partition_name;
67    const char *device_name;
68    int partition_num;
69    int major;
70    int minor;
71};
72
73struct perms_ {
74    char *name;
75    char *attr;
76    mode_t perm;
77    unsigned int uid;
78    unsigned int gid;
79    unsigned short prefix;
80};
81
82struct perm_node {
83    struct perms_ dp;
84    struct listnode plist;
85};
86
87struct platform_node {
88    char *name;
89    int name_len;
90    struct listnode list;
91};
92
93static list_declare(sys_perms);
94static list_declare(dev_perms);
95static list_declare(platform_names);
96
97int add_dev_perms(const char *name, const char *attr,
98                  mode_t perm, unsigned int uid, unsigned int gid,
99                  unsigned short prefix) {
100    struct perm_node *node = calloc(1, sizeof(*node));
101    if (!node)
102        return -ENOMEM;
103
104    node->dp.name = strdup(name);
105    if (!node->dp.name)
106        return -ENOMEM;
107
108    if (attr) {
109        node->dp.attr = strdup(attr);
110        if (!node->dp.attr)
111            return -ENOMEM;
112    }
113
114    node->dp.perm = perm;
115    node->dp.uid = uid;
116    node->dp.gid = gid;
117    node->dp.prefix = prefix;
118
119    if (attr)
120        list_add_tail(&sys_perms, &node->plist);
121    else
122        list_add_tail(&dev_perms, &node->plist);
123
124    return 0;
125}
126
127void fixup_sys_perms(const char *upath)
128{
129    char buf[512];
130    struct listnode *node;
131    struct perms_ *dp;
132
133        /* upaths omit the "/sys" that paths in this list
134         * contain, so we add 4 when comparing...
135         */
136    list_for_each(node, &sys_perms) {
137        dp = &(node_to_item(node, struct perm_node, plist))->dp;
138        if (dp->prefix) {
139            if (strncmp(upath, dp->name + 4, strlen(dp->name + 4)))
140                continue;
141        } else {
142            if (strcmp(upath, dp->name + 4))
143                continue;
144        }
145
146        if ((strlen(upath) + strlen(dp->attr) + 6) > sizeof(buf))
147            return;
148
149        sprintf(buf,"/sys%s/%s", upath, dp->attr);
150        INFO("fixup %s %d %d 0%o\n", buf, dp->uid, dp->gid, dp->perm);
151        chown(buf, dp->uid, dp->gid);
152        chmod(buf, dp->perm);
153    }
154}
155
156static mode_t get_device_perm(const char *path, unsigned *uid, unsigned *gid)
157{
158    mode_t perm;
159    struct listnode *node;
160    struct perm_node *perm_node;
161    struct perms_ *dp;
162
163    /* search the perms list in reverse so that ueventd.$hardware can
164     * override ueventd.rc
165     */
166    list_for_each_reverse(node, &dev_perms) {
167        perm_node = node_to_item(node, struct perm_node, plist);
168        dp = &perm_node->dp;
169
170        if (dp->prefix) {
171            if (strncmp(path, dp->name, strlen(dp->name)))
172                continue;
173        } else {
174            if (strcmp(path, dp->name))
175                continue;
176        }
177        *uid = dp->uid;
178        *gid = dp->gid;
179        return dp->perm;
180    }
181    /* Default if nothing found. */
182    *uid = 0;
183    *gid = 0;
184    return 0600;
185}
186
187static void make_device(const char *path,
188                        const char *upath,
189                        int block, int major, int minor)
190{
191    unsigned uid;
192    unsigned gid;
193    mode_t mode;
194    dev_t dev;
195#ifdef HAVE_SELINUX
196    char *secontext = NULL;
197#endif
198
199    mode = get_device_perm(path, &uid, &gid) | (block ? S_IFBLK : S_IFCHR);
200#ifdef HAVE_SELINUX
201    if (sehandle) {
202        selabel_lookup(sehandle, &secontext, path, mode);
203        setfscreatecon(secontext);
204    }
205#endif
206    dev = makedev(major, minor);
207    /* Temporarily change egid to avoid race condition setting the gid of the
208     * device node. Unforunately changing the euid would prevent creation of
209     * some device nodes, so the uid has to be set with chown() and is still
210     * racy. Fixing the gid race at least fixed the issue with system_server
211     * opening dynamic input devices under the AID_INPUT gid. */
212    setegid(gid);
213    mknod(path, mode, dev);
214    chown(path, uid, -1);
215    setegid(AID_ROOT);
216#ifdef HAVE_SELINUX
217    if (secontext) {
218        freecon(secontext);
219        setfscreatecon(NULL);
220    }
221#endif
222}
223
224
225static int make_dir(const char *path, mode_t mode)
226{
227    int rc;
228
229#ifdef HAVE_SELINUX
230    char *secontext = NULL;
231
232    if (sehandle) {
233        selabel_lookup(sehandle, &secontext, path, mode);
234        setfscreatecon(secontext);
235    }
236#endif
237
238    rc = mkdir(path, mode);
239
240#ifdef HAVE_SELINUX
241    if (secontext) {
242        freecon(secontext);
243        setfscreatecon(NULL);
244    }
245#endif
246    return rc;
247}
248
249
250static void add_platform_device(const char *name)
251{
252    int name_len = strlen(name);
253    struct listnode *node;
254    struct platform_node *bus;
255
256    list_for_each_reverse(node, &platform_names) {
257        bus = node_to_item(node, struct platform_node, list);
258        if ((bus->name_len < name_len) &&
259                (name[bus->name_len] == '/') &&
260                !strncmp(name, bus->name, bus->name_len))
261            /* subdevice of an existing platform, ignore it */
262            return;
263    }
264
265    INFO("adding platform device %s\n", name);
266
267    bus = calloc(1, sizeof(struct platform_node));
268    bus->name = strdup(name);
269    bus->name_len = name_len;
270    list_add_tail(&platform_names, &bus->list);
271}
272
273/*
274 * given a name that may start with a platform device, find the length of the
275 * platform device prefix.  If it doesn't start with a platform device, return
276 * 0.
277 */
278static const char *find_platform_device(const char *name)
279{
280    int name_len = strlen(name);
281    struct listnode *node;
282    struct platform_node *bus;
283
284    list_for_each_reverse(node, &platform_names) {
285        bus = node_to_item(node, struct platform_node, list);
286        if ((bus->name_len < name_len) &&
287                (name[bus->name_len] == '/') &&
288                !strncmp(name, bus->name, bus->name_len))
289            return bus->name;
290    }
291
292    return NULL;
293}
294
295static void remove_platform_device(const char *name)
296{
297    struct listnode *node;
298    struct platform_node *bus;
299
300    list_for_each_reverse(node, &platform_names) {
301        bus = node_to_item(node, struct platform_node, list);
302        if (!strcmp(name, bus->name)) {
303            INFO("removing platform device %s\n", name);
304            free(bus->name);
305            list_remove(node);
306            free(bus);
307            return;
308        }
309    }
310}
311
312#if LOG_UEVENTS
313
314static inline suseconds_t get_usecs(void)
315{
316    struct timeval tv;
317    gettimeofday(&tv, 0);
318    return tv.tv_sec * (suseconds_t) 1000000 + tv.tv_usec;
319}
320
321#define log_event_print(x...) INFO(x)
322
323#else
324
325#define log_event_print(fmt, args...)   do { } while (0)
326#define get_usecs()                     0
327
328#endif
329
330static void parse_event(const char *msg, struct uevent *uevent)
331{
332    uevent->action = "";
333    uevent->path = "";
334    uevent->subsystem = "";
335    uevent->firmware = "";
336    uevent->major = -1;
337    uevent->minor = -1;
338    uevent->partition_name = NULL;
339    uevent->partition_num = -1;
340    uevent->device_name = NULL;
341
342        /* currently ignoring SEQNUM */
343    while(*msg) {
344        if(!strncmp(msg, "ACTION=", 7)) {
345            msg += 7;
346            uevent->action = msg;
347        } else if(!strncmp(msg, "DEVPATH=", 8)) {
348            msg += 8;
349            uevent->path = msg;
350        } else if(!strncmp(msg, "SUBSYSTEM=", 10)) {
351            msg += 10;
352            uevent->subsystem = msg;
353        } else if(!strncmp(msg, "FIRMWARE=", 9)) {
354            msg += 9;
355            uevent->firmware = msg;
356        } else if(!strncmp(msg, "MAJOR=", 6)) {
357            msg += 6;
358            uevent->major = atoi(msg);
359        } else if(!strncmp(msg, "MINOR=", 6)) {
360            msg += 6;
361            uevent->minor = atoi(msg);
362        } else if(!strncmp(msg, "PARTN=", 6)) {
363            msg += 6;
364            uevent->partition_num = atoi(msg);
365        } else if(!strncmp(msg, "PARTNAME=", 9)) {
366            msg += 9;
367            uevent->partition_name = msg;
368        } else if(!strncmp(msg, "DEVNAME=", 8)) {
369            msg += 8;
370            uevent->device_name = msg;
371        }
372
373        /* advance to after the next \0 */
374        while(*msg++)
375            ;
376    }
377
378    log_event_print("event { '%s', '%s', '%s', '%s', %d, %d }\n",
379                    uevent->action, uevent->path, uevent->subsystem,
380                    uevent->firmware, uevent->major, uevent->minor);
381}
382
383static char **get_character_device_symlinks(struct uevent *uevent)
384{
385    const char *parent;
386    char *slash;
387    char **links;
388    int link_num = 0;
389    int width;
390
391    if (strncmp(uevent->path, "/devices/platform/", 18))
392        return NULL;
393
394    links = malloc(sizeof(char *) * 2);
395    if (!links)
396        return NULL;
397    memset(links, 0, sizeof(char *) * 2);
398
399    /* skip "/devices/platform/<driver>" */
400    parent = strchr(uevent->path + 18, '/');
401    if (!*parent)
402        goto err;
403
404    if (!strncmp(parent, "/usb", 4)) {
405        /* skip root hub name and device. use device interface */
406        while (*++parent && *parent != '/');
407        if (*parent)
408            while (*++parent && *parent != '/');
409        if (!*parent)
410            goto err;
411        slash = strchr(++parent, '/');
412        if (!slash)
413            goto err;
414        width = slash - parent;
415        if (width <= 0)
416            goto err;
417
418        if (asprintf(&links[link_num], "/dev/usb/%s%.*s", uevent->subsystem, width, parent) > 0)
419            link_num++;
420        else
421            links[link_num] = NULL;
422        mkdir("/dev/usb", 0755);
423    }
424    else {
425        goto err;
426    }
427
428    return links;
429err:
430    free(links);
431    return NULL;
432}
433
434static char **parse_platform_block_device(struct uevent *uevent)
435{
436    const char *device;
437    const char *path;
438    char *slash;
439    int width;
440    char buf[256];
441    char link_path[256];
442    int fd;
443    int link_num = 0;
444    int ret;
445    char *p;
446    unsigned int size;
447    struct stat info;
448
449    char **links = malloc(sizeof(char *) * 4);
450    if (!links)
451        return NULL;
452    memset(links, 0, sizeof(char *) * 4);
453
454    /* Drop "/devices/platform/" */
455    path = uevent->path;
456    device = path + 18;
457    device = find_platform_device(device);
458    if (!device)
459        goto err;
460
461    INFO("found platform device %s\n", device);
462
463    snprintf(link_path, sizeof(link_path), "/dev/block/platform/%s", device);
464
465    if (uevent->partition_name) {
466        p = strdup(uevent->partition_name);
467        sanitize(p);
468        if (asprintf(&links[link_num], "%s/by-name/%s", link_path, p) > 0)
469            link_num++;
470        else
471            links[link_num] = NULL;
472        free(p);
473    }
474
475    if (uevent->partition_num >= 0) {
476        if (asprintf(&links[link_num], "%s/by-num/p%d", link_path, uevent->partition_num) > 0)
477            link_num++;
478        else
479            links[link_num] = NULL;
480    }
481
482    slash = strrchr(path, '/');
483    if (asprintf(&links[link_num], "%s/%s", link_path, slash + 1) > 0)
484        link_num++;
485    else
486        links[link_num] = NULL;
487
488    return links;
489
490err:
491    free(links);
492    return NULL;
493}
494
495static void handle_device(const char *action, const char *devpath,
496        const char *path, int block, int major, int minor, char **links)
497{
498    int i;
499
500    if(!strcmp(action, "add")) {
501        make_device(devpath, path, block, major, minor);
502        if (links) {
503            for (i = 0; links[i]; i++)
504                make_link(devpath, links[i]);
505        }
506    }
507
508    if(!strcmp(action, "remove")) {
509        if (links) {
510            for (i = 0; links[i]; i++)
511                remove_link(devpath, links[i]);
512        }
513        unlink(devpath);
514    }
515
516    if (links) {
517        for (i = 0; links[i]; i++)
518            free(links[i]);
519        free(links);
520    }
521}
522
523static void handle_platform_device_event(struct uevent *uevent)
524{
525    const char *name = uevent->path + 18; /* length of /devices/platform/ */
526
527    if (!strcmp(uevent->action, "add"))
528        add_platform_device(name);
529    else if (!strcmp(uevent->action, "remove"))
530        remove_platform_device(name);
531}
532
533static const char *parse_device_name(struct uevent *uevent, unsigned int len)
534{
535    const char *name;
536
537    /* if it's not a /dev device, nothing else to do */
538    if((uevent->major < 0) || (uevent->minor < 0))
539        return NULL;
540
541    /* do we have a name? */
542    name = strrchr(uevent->path, '/');
543    if(!name)
544        return NULL;
545    name++;
546
547    /* too-long names would overrun our buffer */
548    if(strlen(name) > len)
549        return NULL;
550
551    return name;
552}
553
554static void handle_block_device_event(struct uevent *uevent)
555{
556    const char *base = "/dev/block/";
557    const char *name;
558    char devpath[96];
559    char **links = NULL;
560
561    name = parse_device_name(uevent, 64);
562    if (!name)
563        return;
564
565    snprintf(devpath, sizeof(devpath), "%s%s", base, name);
566    make_dir(base, 0755);
567
568    if (!strncmp(uevent->path, "/devices/platform/", 18))
569        links = parse_platform_block_device(uevent);
570
571    handle_device(uevent->action, devpath, uevent->path, 1,
572            uevent->major, uevent->minor, links);
573}
574
575static void handle_generic_device_event(struct uevent *uevent)
576{
577    char *base;
578    const char *name;
579    char devpath[96] = {0};
580    char **links = NULL;
581
582    name = parse_device_name(uevent, 64);
583    if (!name)
584        return;
585
586    if (!strncmp(uevent->subsystem, "usb", 3)) {
587         if (!strcmp(uevent->subsystem, "usb")) {
588            if (uevent->device_name) {
589                /*
590                 * create device node provided by kernel if present
591                 * see drivers/base/core.c
592                 */
593                char *p = devpath;
594                snprintf(devpath, sizeof(devpath), "/dev/%s", uevent->device_name);
595                /* skip leading /dev/ */
596                p += 5;
597                /* build directories */
598                while (*p) {
599                    if (*p == '/') {
600                        *p = 0;
601                        make_dir(devpath, 0755);
602                        *p = '/';
603                    }
604                    p++;
605                }
606             }
607             else {
608                 /* This imitates the file system that would be created
609                  * if we were using devfs instead.
610                  * Minors are broken up into groups of 128, starting at "001"
611                  */
612                 int bus_id = uevent->minor / 128 + 1;
613                 int device_id = uevent->minor % 128 + 1;
614                 /* build directories */
615                 make_dir("/dev/bus", 0755);
616                 make_dir("/dev/bus/usb", 0755);
617                 snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d", bus_id);
618                 make_dir(devpath, 0755);
619                 snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d/%03d", bus_id, device_id);
620             }
621         } else {
622             /* ignore other USB events */
623             return;
624         }
625     } else if (!strncmp(uevent->subsystem, "graphics", 8)) {
626         base = "/dev/graphics/";
627         make_dir(base, 0755);
628     } else if (!strncmp(uevent->subsystem, "oncrpc", 6)) {
629         base = "/dev/oncrpc/";
630         make_dir(base, 0755);
631     } else if (!strncmp(uevent->subsystem, "adsp", 4)) {
632         base = "/dev/adsp/";
633         make_dir(base, 0755);
634     } else if (!strncmp(uevent->subsystem, "msm_camera", 10)) {
635         base = "/dev/msm_camera/";
636         make_dir(base, 0755);
637     } else if(!strncmp(uevent->subsystem, "input", 5)) {
638         base = "/dev/input/";
639         make_dir(base, 0755);
640     } else if(!strncmp(uevent->subsystem, "mtd", 3)) {
641         base = "/dev/mtd/";
642         make_dir(base, 0755);
643     } else if(!strncmp(uevent->subsystem, "sound", 5)) {
644         base = "/dev/snd/";
645         make_dir(base, 0755);
646     } else if(!strncmp(uevent->subsystem, "misc", 4) &&
647                 !strncmp(name, "log_", 4)) {
648         base = "/dev/log/";
649         make_dir(base, 0755);
650         name += 4;
651     } else
652         base = "/dev/";
653     links = get_character_device_symlinks(uevent);
654
655     if (!devpath[0])
656         snprintf(devpath, sizeof(devpath), "%s%s", base, name);
657
658     handle_device(uevent->action, devpath, uevent->path, 0,
659             uevent->major, uevent->minor, links);
660}
661
662static void handle_device_event(struct uevent *uevent)
663{
664    if (!strcmp(uevent->action,"add"))
665        fixup_sys_perms(uevent->path);
666
667    if (!strncmp(uevent->subsystem, "block", 5)) {
668        handle_block_device_event(uevent);
669    } else if (!strncmp(uevent->subsystem, "platform", 8)) {
670        handle_platform_device_event(uevent);
671    } else {
672        handle_generic_device_event(uevent);
673    }
674}
675
676static int load_firmware(int fw_fd, int loading_fd, int data_fd)
677{
678    struct stat st;
679    long len_to_copy;
680    int ret = 0;
681
682    if(fstat(fw_fd, &st) < 0)
683        return -1;
684    len_to_copy = st.st_size;
685
686    write(loading_fd, "1", 1);  /* start transfer */
687
688    while (len_to_copy > 0) {
689        char buf[PAGE_SIZE];
690        ssize_t nr;
691
692        nr = read(fw_fd, buf, sizeof(buf));
693        if(!nr)
694            break;
695        if(nr < 0) {
696            ret = -1;
697            break;
698        }
699
700        len_to_copy -= nr;
701        while (nr > 0) {
702            ssize_t nw = 0;
703
704            nw = write(data_fd, buf + nw, nr);
705            if(nw <= 0) {
706                ret = -1;
707                goto out;
708            }
709            nr -= nw;
710        }
711    }
712
713out:
714    if(!ret)
715        write(loading_fd, "0", 1);  /* successful end of transfer */
716    else
717        write(loading_fd, "-1", 2); /* abort transfer */
718
719    return ret;
720}
721
722static int is_booting(void)
723{
724    return access("/dev/.booting", F_OK) == 0;
725}
726
727static void process_firmware_event(struct uevent *uevent)
728{
729    char *root, *loading, *data, *file1 = NULL, *file2 = NULL, *file3 = NULL;
730    int l, loading_fd, data_fd, fw_fd;
731    int booting = is_booting();
732
733    INFO("firmware: loading '%s' for '%s'\n",
734         uevent->firmware, uevent->path);
735
736    l = asprintf(&root, SYSFS_PREFIX"%s/", uevent->path);
737    if (l == -1)
738        return;
739
740    l = asprintf(&loading, "%sloading", root);
741    if (l == -1)
742        goto root_free_out;
743
744    l = asprintf(&data, "%sdata", root);
745    if (l == -1)
746        goto loading_free_out;
747
748    l = asprintf(&file1, FIRMWARE_DIR1"/%s", uevent->firmware);
749    if (l == -1)
750        goto data_free_out;
751
752    l = asprintf(&file2, FIRMWARE_DIR2"/%s", uevent->firmware);
753    if (l == -1)
754        goto data_free_out;
755
756    l = asprintf(&file3, FIRMWARE_DIR3"/%s", uevent->firmware);
757    if (l == -1)
758        goto data_free_out;
759
760    loading_fd = open(loading, O_WRONLY);
761    if(loading_fd < 0)
762        goto file_free_out;
763
764    data_fd = open(data, O_WRONLY);
765    if(data_fd < 0)
766        goto loading_close_out;
767
768try_loading_again:
769    fw_fd = open(file1, O_RDONLY);
770    if(fw_fd < 0) {
771        fw_fd = open(file2, O_RDONLY);
772        if (fw_fd < 0) {
773            fw_fd = open(file3, O_RDONLY);
774            if (fw_fd < 0) {
775                if (booting) {
776                        /* If we're not fully booted, we may be missing
777                         * filesystems needed for firmware, wait and retry.
778                         */
779                    usleep(100000);
780                    booting = is_booting();
781                    goto try_loading_again;
782                }
783                INFO("firmware: could not open '%s' %d\n", uevent->firmware, errno);
784                write(loading_fd, "-1", 2);
785                goto data_close_out;
786            }
787        }
788    }
789
790    if(!load_firmware(fw_fd, loading_fd, data_fd))
791        INFO("firmware: copy success { '%s', '%s' }\n", root, uevent->firmware);
792    else
793        INFO("firmware: copy failure { '%s', '%s' }\n", root, uevent->firmware);
794
795    close(fw_fd);
796data_close_out:
797    close(data_fd);
798loading_close_out:
799    close(loading_fd);
800file_free_out:
801    free(file1);
802    free(file2);
803data_free_out:
804    free(data);
805loading_free_out:
806    free(loading);
807root_free_out:
808    free(root);
809}
810
811static void handle_firmware_event(struct uevent *uevent)
812{
813    pid_t pid;
814    int ret;
815
816    if(strcmp(uevent->subsystem, "firmware"))
817        return;
818
819    if(strcmp(uevent->action, "add"))
820        return;
821
822    /* we fork, to avoid making large memory allocations in init proper */
823    pid = fork();
824    if (!pid) {
825        process_firmware_event(uevent);
826        exit(EXIT_SUCCESS);
827    }
828}
829
830#define UEVENT_MSG_LEN  1024
831void handle_device_fd()
832{
833    char msg[UEVENT_MSG_LEN+2];
834    int n;
835    while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {
836        if(n >= UEVENT_MSG_LEN)   /* overflow -- discard */
837            continue;
838
839        msg[n] = '\0';
840        msg[n+1] = '\0';
841
842        struct uevent uevent;
843        parse_event(msg, &uevent);
844
845        handle_device_event(&uevent);
846        handle_firmware_event(&uevent);
847    }
848}
849
850/* Coldboot walks parts of the /sys tree and pokes the uevent files
851** to cause the kernel to regenerate device add events that happened
852** before init's device manager was started
853**
854** We drain any pending events from the netlink socket every time
855** we poke another uevent file to make sure we don't overrun the
856** socket's buffer.
857*/
858
859static void do_coldboot(DIR *d)
860{
861    struct dirent *de;
862    int dfd, fd;
863
864    dfd = dirfd(d);
865
866    fd = openat(dfd, "uevent", O_WRONLY);
867    if(fd >= 0) {
868        write(fd, "add\n", 4);
869        close(fd);
870        handle_device_fd();
871    }
872
873    while((de = readdir(d))) {
874        DIR *d2;
875
876        if(de->d_type != DT_DIR || de->d_name[0] == '.')
877            continue;
878
879        fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
880        if(fd < 0)
881            continue;
882
883        d2 = fdopendir(fd);
884        if(d2 == 0)
885            close(fd);
886        else {
887            do_coldboot(d2);
888            closedir(d2);
889        }
890    }
891}
892
893static void coldboot(const char *path)
894{
895    DIR *d = opendir(path);
896    if(d) {
897        do_coldboot(d);
898        closedir(d);
899    }
900}
901
902void device_init(void)
903{
904    suseconds_t t0, t1;
905    struct stat info;
906    int fd;
907#ifdef HAVE_SELINUX
908    struct selinux_opt seopts[] = {
909        { SELABEL_OPT_PATH, "/file_contexts" }
910    };
911
912    if (is_selinux_enabled() > 0)
913        sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
914#endif
915    /* is 64K enough? udev uses 16MB! */
916    device_fd = uevent_open_socket(64*1024, true);
917    if(device_fd < 0)
918        return;
919
920    fcntl(device_fd, F_SETFD, FD_CLOEXEC);
921    fcntl(device_fd, F_SETFL, O_NONBLOCK);
922
923    if (stat(coldboot_done, &info) < 0) {
924        t0 = get_usecs();
925        coldboot("/sys/class");
926        coldboot("/sys/block");
927        coldboot("/sys/devices");
928        t1 = get_usecs();
929        fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000);
930        close(fd);
931        log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));
932    } else {
933        log_event_print("skipping coldboot, already done\n");
934    }
935}
936
937int get_device_fd()
938{
939    return device_fd;
940}
941