root/sys/kern/kern_udev.c
/*
 * Copyright (c) 2010,2018 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Alex Hornung <ahornung@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/event.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/objcache.h>
#include <sys/ctype.h>
#include <sys/syslog.h>
#include <sys/udev.h>
#include <sys/devfs.h>
#include <libprop/proplib.h>

MALLOC_DEFINE(M_UDEV, "udev", "udev allocs");
static struct objcache *udev_event_kernel_cache;

/* XXX: use UUIDs for identification; would need help from devfs */

static cdev_t           udev_dev;
static d_open_t         udev_dev_open;
static d_close_t        udev_dev_close;
static d_read_t         udev_dev_read;
static d_kqfilter_t     udev_dev_kqfilter;
static d_ioctl_t        udev_dev_ioctl;
static d_clone_t        udev_dev_clone;

struct udev_prop_ctx {
        prop_array_t cdevs;
        int error;
};

struct udev_event_kernel {
        struct udev_event ev;
        TAILQ_ENTRY(udev_event_kernel)  link;
};

struct udev_softc {
        TAILQ_ENTRY(udev_softc) entry;
        int opened;
        int initiated;
        int unit;
        cdev_t dev;

        struct udev_event_kernel marker;        /* udev_evq marker */
};

struct cmd_function {
        const char *cmd;
        int  (*fn)(struct udev_softc *, struct plistref *,
                   u_long, prop_dictionary_t);
};

static int _udev_dict_set_cstr(prop_dictionary_t, const char *, char *);
static int _udev_dict_set_int(prop_dictionary_t, const char *, int64_t);
static int _udev_dict_set_uint(prop_dictionary_t, const char *, uint64_t);
static int _udev_dict_delete_key(prop_dictionary_t, const char *);
static prop_dictionary_t udev_init_dict_event(cdev_t, const char *);
static int udev_init_dict(cdev_t);
static int udev_destroy_dict(cdev_t);
static void udev_event_insert(int, prop_dictionary_t);
static void udev_clean_events_locked(void);
static char *udev_event_externalize(struct udev_event_kernel *);
static void udev_getdevs_scan_callback(char *, cdev_t, bool, void *);
static int udev_getdevs_ioctl(struct udev_softc *, struct plistref *,
                                        u_long, prop_dictionary_t);
static void udev_dev_filter_detach(struct knote *);
static int udev_dev_filter_read(struct knote *, long);

static struct dev_ops udev_dev_ops = {
        { "udev", 0, 0 },
        .d_open = udev_dev_open,
        .d_close = udev_dev_close,
        .d_read = udev_dev_read,
        .d_kqfilter = udev_dev_kqfilter,
        .d_ioctl = udev_dev_ioctl
};

static struct cmd_function cmd_fn[] = {
                { .cmd = "getdevs", .fn = udev_getdevs_ioctl},
                {NULL, NULL}
};

DEVFS_DEFINE_CLONE_BITMAP(udev);

static TAILQ_HEAD(, udev_softc) udevq;
static TAILQ_HEAD(, udev_event_kernel) udev_evq;
static struct kqinfo udev_kq;
static struct lock udev_lk;
static int udev_evqlen;
static int udev_initiated_count;
static int udev_open_count;
static int udev_seqwait;
static int udev_seq;
static struct lock udev_dict_lk = LOCK_INITIALIZER("dict", 0, 0);

/*
 * Acquire the device's si_dict and lock the device's si_dict field.
 * If the device does not have an attached dictionary, NULL is returned.
 *
 * This function must be matched by a udev_put_dict() call regardless of
 * the return value.  The device field is STILL LOCKED even when NULL is
 * returned.
 *
 * Currently a single global lock is implemented.
 */
static prop_dictionary_t
udev_get_dict(cdev_t dev)
{
        prop_dictionary_t udict;

        lockmgr(&udev_dict_lk, LK_EXCLUSIVE|LK_CANRECURSE);
        udict = dev->si_dict;
        if (udict)
                prop_object_retain(udict);
        return udict;
}

/*
 * Release the dictionary previously returned by udev_get_dict() and unlock
 * the device's si_dict field.  udict may be NULL.
 */
static void
udev_put_dict(cdev_t dev, prop_dictionary_t udict)
{
        if (udict)
                prop_object_release(udict);
        lockmgr(&udev_dict_lk, LK_RELEASE);
}

static int
_udev_dict_set_cstr(prop_dictionary_t dict, const char *key, char *str)
{
        prop_string_t   ps;

        KKASSERT(dict != NULL);

        ps = prop_string_create_cstring(str);
        if (ps == NULL) {
                return ENOMEM;
        }

        if (prop_dictionary_set(dict, key, ps) == false) {
                prop_object_release(ps);
                return ENOMEM;
        }

        prop_object_release(ps);
        return 0;
}

static int
_udev_dict_set_int(prop_dictionary_t dict, const char *key, int64_t val)
{
        prop_number_t   pn;

        KKASSERT(dict != NULL);

        pn = prop_number_create_integer(val);
        if (pn == NULL)
                return ENOMEM;

        if (prop_dictionary_set(dict, key, pn) == false) {
                prop_object_release(pn);
                return ENOMEM;
        }

        prop_object_release(pn);
        return 0;
}

static int
_udev_dict_set_uint(prop_dictionary_t dict, const char *key, uint64_t val)
{
        prop_number_t   pn;

        KKASSERT(dict != NULL);

        pn = prop_number_create_unsigned_integer(val);
        if (pn == NULL)
                return ENOMEM;

        if (prop_dictionary_set(dict, key, pn) == false) {
                prop_object_release(pn);
                return ENOMEM;
        }

        prop_object_release(pn);
        return 0;
}

static int
_udev_dict_delete_key(prop_dictionary_t dict, const char *key)
{
        KKASSERT(dict != NULL);

        prop_dictionary_remove(dict, key);

        return 0;
}

/*
 * Initialize an event dictionary, which contains three parameters to
 * identify the device referred to (name, devnum, kptr) and the affected key.
 */
static prop_dictionary_t
udev_init_dict_event(cdev_t dev, const char *key)
{
        prop_dictionary_t       dict;
        uint64_t        kptr;
        int error;

        kptr = (uint64_t)(uintptr_t)dev;
        KKASSERT(dev != NULL);

        dict = prop_dictionary_create();
        if (dict == NULL) {
                log(LOG_DEBUG, "udev_init_dict_event: prop_dictionary_create() failed\n");
                return NULL;
        }

        if ((error = _udev_dict_set_cstr(dict, "name", dev->si_name)))
                goto error_out;
        if ((error = _udev_dict_set_uint(dict, "devnum", dev->si_inode)))
                goto error_out;
        if ((error = _udev_dict_set_uint(dict, "devtype", (dev_dflags(dev) & D_TYPEMASK))))
                goto error_out;
        if ((error = _udev_dict_set_uint(dict, "kptr", kptr)))
                goto error_out;
        if ((error = _udev_dict_set_cstr(dict, "key", __DECONST(char *, key))))
                goto error_out;

        return dict;

error_out:
        prop_object_release(dict);
        return NULL;
}

int
udev_dict_set_cstr(cdev_t dev, const char *key, char *str)
{
        prop_dictionary_t dict;
        prop_dictionary_t udict;
        int error;

        KKASSERT(dev != NULL);

        while ((udict = udev_get_dict(dev)) == NULL) {
                error = udev_init_dict(dev);
                udev_put_dict(dev, udict);
                if (error)
                        return -1;
        }

        /* Queue a key update event */
        dict = udev_init_dict_event(dev, key);
        if (dict == NULL) {
                error = ENOMEM;
                goto errout;
        }

        if ((error = _udev_dict_set_cstr(dict, "value", str))) {
                prop_object_release(dict);
                goto errout;
        }
        udev_event_insert(UDEV_EV_KEY_UPDATE, dict);
        prop_object_release(dict);
        error = _udev_dict_set_cstr(udict, key, str);

errout:
        udev_put_dict(dev, udict);

        return error;
}

int
udev_dict_set_int(cdev_t dev, const char *key, int64_t val)
{
        prop_dictionary_t dict;
        prop_dictionary_t udict;
        int error;

        KKASSERT(dev != NULL);

        while ((udict = udev_get_dict(dev)) == NULL) {
                error = udev_init_dict(dev);
                udev_put_dict(dev, udict);
                if (error)
                        return -1;
        }

        /* Queue a key update event */
        dict = udev_init_dict_event(dev, key);
        if (dict == NULL) {
                error = ENOMEM;
                goto errout;
        }
        if ((error = _udev_dict_set_int(dict, "value", val))) {
                prop_object_release(dict);
                goto errout;
        }
        udev_event_insert(UDEV_EV_KEY_UPDATE, dict);
        prop_object_release(dict);
        error = _udev_dict_set_int(udict, key, val);
errout:
        udev_put_dict(dev, udict);

        return error;
}

int
udev_dict_set_uint(cdev_t dev, const char *key, uint64_t val)
{
        prop_dictionary_t dict;
        prop_dictionary_t udict;
        int error;

        KKASSERT(dev != NULL);

        while ((udict = udev_get_dict(dev)) == NULL) {
                error = udev_init_dict(dev);
                udev_put_dict(dev, udict);
                if (error)
                        return -1;
        }

        /* Queue a key update event */
        dict = udev_init_dict_event(dev, key);
        if (dict == NULL) {
                error = ENOMEM;
                goto errout;
        }
        if ((error = _udev_dict_set_uint(dict, "value", val))) {
                prop_object_release(dict);
                goto errout;
        }
        udev_event_insert(UDEV_EV_KEY_UPDATE, dict);
        prop_object_release(dict);
        error = _udev_dict_set_uint(udict, key, val);
errout:
        udev_put_dict(dev, udict);

        return error;
}

int
udev_dict_delete_key(cdev_t dev, const char *key)
{
        prop_dictionary_t dict;
        prop_dictionary_t udict;
        int error;

        KKASSERT(dev != NULL);
        udict = udev_get_dict(dev);
        if (udict == NULL) {
                error = ENOMEM;
                goto errout;
        }

        /* Queue a key removal event */
        dict = udev_init_dict_event(dev, key);
        if (dict == NULL) {
                error = ENOMEM;
                goto errout;
        }
        udev_event_insert(UDEV_EV_KEY_REMOVE, dict);
        prop_object_release(dict);
        error = _udev_dict_delete_key(udict, key);
errout:
        udev_put_dict(dev, udict);

        return error;
}

/*
 * device dictionary access already locked
 */
static int
udev_init_dict(cdev_t dev)
{
        prop_dictionary_t dict;
        uint64_t        kptr;
        int error;

        kptr = (uint64_t)(uintptr_t)dev;

        KKASSERT(dev != NULL);

        if (dev->si_dict != NULL) {
                log(LOG_DEBUG,
                    "udev_init_dict: new dict for %s, but has "
                    "dict already (%p)!\n",
                    dev->si_name, dev->si_dict);
                return 0;
        }

        dict = prop_dictionary_create();
        if (dict == NULL) {
                log(LOG_DEBUG,
                    "udev_init_dict: prop_dictionary_create() failed\n");
                return ENOMEM;
        }

        if ((error = _udev_dict_set_cstr(dict, "name", dev->si_name)))
                goto error_out;
        if ((error = _udev_dict_set_uint(dict, "devnum", dev->si_inode)))
                goto error_out;
        if ((error = _udev_dict_set_uint(dict, "kptr", kptr)))
                goto error_out;
        if ((error = _udev_dict_set_uint(dict, "devtype",
                                         (dev_dflags(dev) & D_TYPEMASK)))) {
                goto error_out;
        }

        /* XXX: The next 3 are marginallly useful, if at all */
        if ((error = _udev_dict_set_uint(dict, "uid", dev->si_uid)))
                goto error_out;
        if ((error = _udev_dict_set_uint(dict, "gid", dev->si_gid)))
                goto error_out;
        if ((error = _udev_dict_set_int(dict, "mode", dev->si_perms)))
                goto error_out;

        if ((error = _udev_dict_set_int(dict, "major", dev->si_umajor)))
                goto error_out;
        if ((error = _udev_dict_set_int(dict, "minor", dev->si_uminor)))
                goto error_out;
        if (dev->si_ops->head.name != NULL) {
                if ((error = _udev_dict_set_cstr(dict, "driver",
                    __DECONST(char *, dev->si_ops->head.name))))
                        goto error_out;
        }

        dev->si_dict = dict;
        return 0;

error_out:
        dev->si_dict = NULL;
        prop_object_release(dict);
        return error;
}

/*
 * device dictionary access already locked
 */
static int
udev_destroy_dict(cdev_t dev)
{
        prop_dictionary_t udict;

        KKASSERT(dev != NULL);
        udict = udev_get_dict(dev);

        /* field is now locked, use directly to handle races */
        if (dev->si_dict) {
                prop_object_release(dev->si_dict);
                dev->si_dict = NULL;
        }
        udev_put_dict(dev, udict);

        return 0;
}

static void
udev_event_insert(int ev_type, prop_dictionary_t dict)
{
        struct udev_event_kernel *ev;
        prop_dictionary_t dict_copy;

        /* Only start queing events after client has initiated properly */
        if (udev_initiated_count) {
                dict_copy = prop_dictionary_copy(dict);
                if (dict_copy == NULL)
                        return;
                ev = objcache_get(udev_event_kernel_cache, M_WAITOK);
                ev->ev.ev_dict = dict_copy;
                ev->ev.ev_type = ev_type;

                lockmgr(&udev_lk, LK_EXCLUSIVE);
                TAILQ_INSERT_TAIL(&udev_evq, ev, link);
                ++udev_evqlen;
                ++udev_seq;
                if (udev_seqwait)
                        wakeup(&udev_seqwait);
                lockmgr(&udev_lk, LK_RELEASE);
                wakeup(&udev_evq);
                KNOTE(&udev_kq.ki_note, 0);
        } else if (udev_open_count) {
                lockmgr(&udev_lk, LK_EXCLUSIVE);
                ++udev_seq;
                if (udev_seqwait)
                        wakeup(&udev_seqwait);
                lockmgr(&udev_lk, LK_RELEASE);
                KNOTE(&udev_kq.ki_note, 0);
        }
}

static void
udev_clean_events_locked(void)
{
        struct udev_event_kernel *ev;

        while ((ev = TAILQ_FIRST(&udev_evq)) &&
               ev->ev.ev_dict != NULL) {
                TAILQ_REMOVE(&udev_evq, ev, link);
                objcache_put(udev_event_kernel_cache, ev);
                --udev_evqlen;
        }
}

static char *
udev_event_externalize(struct udev_event_kernel *ev)
{
        prop_dictionary_t       dict;
        char *xml;
        int error;

        dict = prop_dictionary_create();
        if (dict == NULL) {
                log(LOG_DEBUG,
                    "udev_event_externalize: prop_dictionary_create() failed\n");
                return NULL;
        }

        if ((error = _udev_dict_set_int(dict, "evtype", ev->ev.ev_type))) {
                prop_object_release(dict);
                return NULL;
        }

        if (prop_dictionary_set(dict, "evdict", ev->ev.ev_dict) == false) {
                prop_object_release(dict);
                return NULL;
        }

        prop_object_release(ev->ev.ev_dict);

        xml = prop_dictionary_externalize(dict);

        prop_object_release(dict);

        return xml;
}

int
udev_event_attach(cdev_t dev, char *name, int alias)
{
        prop_dictionary_t dict;
        prop_dictionary_t udict;
        int error;

        KKASSERT(dev != NULL);

        error = ENOMEM;

        udict = udev_get_dict(dev);
        if (alias) {
                if (udict == NULL)
                        goto error_out;
                dict = prop_dictionary_copy(udict);
                if (dict == NULL)
                        goto error_out;

                if ((error = _udev_dict_set_cstr(dict, "name", name))) {
                        prop_object_release(dict);
                        goto error_out;
                }

                _udev_dict_set_int(dict, "alias", 1);

                udev_event_insert(UDEV_EVENT_ATTACH, dict);
                prop_object_release(dict);
        } else {
                while (udict == NULL) {
                        error = udev_init_dict(dev);
                        if (error)
                                goto error_out;
                        udev_put_dict(dev, udict);
                        udict = udev_get_dict(dev);
                }
                _udev_dict_set_int(udict, "alias", 0);
                udev_event_insert(UDEV_EVENT_ATTACH, udict);
        }
error_out:
        udev_put_dict(dev, udict);

        return error;
}

int
udev_event_detach(cdev_t dev, char *name, int alias)
{
        prop_dictionary_t dict;
        prop_dictionary_t udict;

        KKASSERT(dev != NULL);

        udict = udev_get_dict(dev);
        if (alias) {
                dict = prop_dictionary_copy(udict);
                if (dict == NULL)
                        goto error_out;

                if (_udev_dict_set_cstr(dict, "name", name)) {
                        prop_object_release(dict);
                        goto error_out;
                }

                _udev_dict_set_int(dict, "alias", 1);

                udev_event_insert(UDEV_EVENT_DETACH, dict);
                prop_object_release(dict);
        } else {
                if (udict)
                        udev_event_insert(UDEV_EVENT_DETACH, udict);
        }

error_out:
        udev_destroy_dict(dev);
        udev_put_dict(dev, udict);

        return 0;
}

/*
 * Allow multiple opens.  Each opener gets a different device.
 * Messages are replicated to all devices using a marker system.
 */
static int
udev_dev_clone(struct dev_clone_args *ap)
{
        struct udev_softc *softc;
        int unit;

        unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(udev), 1000);
        if (unit < 0) {
                ap->a_dev = NULL;
                return 1;
        }

        softc = kmalloc(sizeof(*softc), M_UDEV, M_WAITOK | M_ZERO);
        softc->unit = unit;
        lockmgr(&udev_lk, LK_EXCLUSIVE);
        TAILQ_INSERT_TAIL(&udevq, softc, entry);
        lockmgr(&udev_lk, LK_RELEASE);

        softc->dev = make_only_dev(&udev_dev_ops, unit, ap->a_cred->cr_ruid,
                                   0, 0600, "udevs/%d", unit);
        softc->dev->si_drv1 = softc;
        ap->a_dev = softc->dev;
        return 0;
}

/*
 * dev stuff
 */
static int
udev_dev_open(struct dev_open_args *ap)
{
        struct udev_softc *softc = ap->a_head.a_dev->si_drv1;

        lockmgr(&udev_lk, LK_EXCLUSIVE);
        if (softc == NULL || softc->opened) {
                lockmgr(&udev_lk, LK_RELEASE);
                return EBUSY;
        }
        softc->opened = 1;
        ++udev_open_count;
        lockmgr(&udev_lk, LK_RELEASE);

        return 0;
}

static int
udev_dev_close(struct dev_close_args *ap)
{
        struct udev_softc *softc = ap->a_head.a_dev->si_drv1;

        KKASSERT(softc->dev == ap->a_head.a_dev);
        KKASSERT(softc->opened == 1);

        destroy_dev(ap->a_head.a_dev);
        lockmgr(&udev_lk, LK_EXCLUSIVE);
        TAILQ_REMOVE(&udevq, softc, entry);

        if (softc->initiated) {
                TAILQ_REMOVE(&udev_evq, &softc->marker, link);
                softc->initiated = 0;
                --udev_initiated_count;
                udev_clean_events_locked();
        }
        softc->opened = 0;
        softc->dev = NULL;
        ap->a_head.a_dev->si_drv1 = NULL;
        --udev_open_count;
        lockmgr(&udev_lk, LK_RELEASE);

        /*
         * WARNING! devfs_clone_bitmap_put() interacts with the devfs
         *          thread, avoid deadlocks by ensuring we are unlocked
         *          before calling.
         */
        devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(udev), softc->unit);
        wakeup(&udev_evq);

        kfree(softc, M_UDEV);

        return 0;
}

static struct filterops udev_dev_read_filtops =
        { FILTEROP_ISFD | FILTEROP_MPSAFE, NULL,
          udev_dev_filter_detach, udev_dev_filter_read };

static int
udev_dev_kqfilter(struct dev_kqfilter_args *ap)
{
        struct udev_softc *softc = ap->a_head.a_dev->si_drv1;
        struct knote *kn = ap->a_kn;
        struct klist *klist;

        ap->a_result = 0;
        lockmgr(&udev_lk, LK_EXCLUSIVE);

        switch (kn->kn_filter) {
        case EVFILT_READ:
                kn->kn_fop = &udev_dev_read_filtops;
                kn->kn_hook = (caddr_t)softc;
                break;
        default:
                ap->a_result = EOPNOTSUPP;
                lockmgr(&udev_lk, LK_RELEASE);
                return (0);
        }

        klist = &udev_kq.ki_note;
        knote_insert(klist, kn);

        lockmgr(&udev_lk, LK_RELEASE);

        return (0);
}

static void
udev_dev_filter_detach(struct knote *kn)
{
        struct klist *klist;

        lockmgr(&udev_lk, LK_EXCLUSIVE);
        klist = &udev_kq.ki_note;
        knote_remove(klist, kn);
        lockmgr(&udev_lk, LK_RELEASE);
}

static int
udev_dev_filter_read(struct knote *kn, long hint)
{
        struct udev_softc *softc = (void *)kn->kn_hook;
        struct udev_event_kernel *ev;
        int ready = 0;

        lockmgr(&udev_lk, LK_EXCLUSIVE);
        if (softc->initiated) {
                ev = TAILQ_NEXT(&softc->marker, link);
                while (ev && ev->ev.ev_dict == NULL)
                        ev = TAILQ_NEXT(ev, link);
                if (ev)
                        ready = 1;
        }
        lockmgr(&udev_lk, LK_RELEASE);

        return (ready);
}

static int
udev_dev_read(struct dev_read_args *ap)
{
        struct udev_softc *softc = ap->a_head.a_dev->si_drv1;
        struct udev_event_kernel *ev;
        struct uio *uio = ap->a_uio;
        char    *xml;
        size_t  len;
        int     error;

        lockmgr(&udev_lk, LK_EXCLUSIVE);

        /*
         * Automatically enable message collection if it has not already
         * been enabled.
         */
        if (softc->initiated == 0) {
                softc->initiated = 1;
                ++udev_initiated_count;
                TAILQ_INSERT_HEAD(&udev_evq, &softc->marker, link);
        }

        /*
         * Loop, sleep interruptably until we get an event or signal.
         */
        error = 0;
        for (;;) {
                if (softc->initiated) {
                        ev = TAILQ_NEXT(&softc->marker, link);
                        while (ev && ev->ev.ev_dict == NULL)
                                ev = TAILQ_NEXT(ev, link);
                        if (ev) {
                                if ((xml = udev_event_externalize(ev)) == NULL) {
                                        error = ENOMEM;
                                        break;
                                }
                                len = strlen(xml) + 1; /* include terminator */
                                if (uio->uio_resid < len)
                                        error = ENOMEM;
                                else
                                        error = uiomove((caddr_t)xml, len, uio);
                                kfree(xml, M_TEMP);

                                /*
                                 * Move the marker
                                 */
                                TAILQ_REMOVE(&udev_evq, &softc->marker, link);
                                TAILQ_INSERT_AFTER(&udev_evq,
                                                   ev, &softc->marker, link);
                                udev_clean_events_locked();
                                break;
                        }
                }
                if (ap->a_ioflag & IO_NDELAY) {
                        error = EWOULDBLOCK;
                        break;
                }
                if ((error = lksleep(&udev_evq, &udev_lk, PCATCH, "udevq", 0)))
                        break;
        }

        lockmgr(&udev_lk, LK_RELEASE);
        return error;
}

static int
udev_dev_ioctl(struct dev_ioctl_args *ap)
{
        struct udev_softc *softc = ap->a_head.a_dev->si_drv1;
        prop_dictionary_t dict;
        prop_object_t   po;
        prop_string_t   ps;
        struct plistref *pref;
        int i, error;
        int seq;

        error = 0;

        switch(ap->a_cmd) {
        case UDEVPROP:
                /* Use proplib(3) for userspace/kernel communication */
                pref = (struct plistref *)ap->a_data;
                error = prop_dictionary_copyin_ioctl(pref, ap->a_cmd, &dict);
                if (error)
                        return error;

                po = prop_dictionary_get(dict, "command");
                if (po == NULL || prop_object_type(po) != PROP_TYPE_STRING) {
                        log(LOG_DEBUG, "udev: prop_dictionary_get() failed\n");
                        prop_object_release(dict);
                        return EINVAL;
                }

                ps = po;
                /* Handle cmd */
                for(i = 0; cmd_fn[i].cmd != NULL; i++) {
                        if (prop_string_equals_cstring(ps, cmd_fn[i].cmd))
                                break;
                }

                if (cmd_fn[i].cmd != NULL) {
                        error = cmd_fn[i].fn(softc, pref, ap->a_cmd, dict);
                } else {
                        error = EINVAL;
                }

                //prop_object_release(po);
                prop_object_release(dict);
                break;
        case UDEVWAIT:
                /*
                 * Wait for events based on sequence number.  Updates
                 * sequence number for loop.
                 */
                lockmgr(&udev_lk, LK_EXCLUSIVE);
                seq = *(int *)ap->a_data;
                ++udev_seqwait;
                while (seq == udev_seq) {
                        error = lksleep(&udev_seqwait, &udev_lk,
                                        PCATCH, "udevw", 0);
                        if (error)
                                break;
                }
                --udev_seqwait;
                *(int *)ap->a_data = udev_seq;
                lockmgr(&udev_lk, LK_RELEASE);
                break;
        default:
                error = ENOTTY; /* Inappropriate ioctl for device */
                break;
        }

        return(error);
}

static void
udev_getdevs_scan_callback(char *name, cdev_t cdev, bool is_alias, void *arg)
{
        struct udev_prop_ctx *ctx = arg;

        KKASSERT(arg != NULL);

        if (cdev->si_dict == NULL)
                return;

        if (prop_array_add(ctx->cdevs, cdev->si_dict) == false) {
                ctx->error = EINVAL;
                return;
        }
}

static int
udev_getdevs_ioctl(struct udev_softc *softc, struct plistref *pref,
                   u_long cmd, prop_dictionary_t dict)
{
        prop_dictionary_t odict;
        struct udev_prop_ctx ctx;
        int error;

        /*
         * Ensure event notification is enabled before doing the devfs
         * scan so nothing gets missed.
         */
        lockmgr(&udev_lk, LK_EXCLUSIVE);
        if (softc->initiated == 0) {
                softc->initiated = 1;
                ++udev_initiated_count;
                TAILQ_INSERT_HEAD(&udev_evq, &softc->marker, link);
        }
        lockmgr(&udev_lk, LK_RELEASE);

        /*
         * Devfs scan to build full dictionary.
         */
        ctx.error = 0;
        ctx.cdevs = prop_array_create();
        if (ctx.cdevs == NULL) {
                log(LOG_DEBUG,
                    "udev_getdevs_ioctl: prop_array_create() failed\n");
                return EINVAL;
        }

        devfs_scan_callback(udev_getdevs_scan_callback, &ctx);

        if (ctx.error != 0) {
                prop_object_release(ctx.cdevs);
                return (ctx.error);
        }

        odict = prop_dictionary_create();
        if (odict == NULL) {
                return ENOMEM;
        }

        if ((prop_dictionary_set(odict, "array", ctx.cdevs)) == 0) {
                log(LOG_DEBUG,
                    "udev_getdevs_ioctl: prop_dictionary_set failed\n");
                prop_object_release(odict);
                return ENOMEM;
        }

        error = prop_dictionary_copyout_ioctl(pref, cmd, odict);

        prop_object_release(odict);
        return error;
}


/*
 * SYSINIT stuff
 */
static void
udev_init(void)
{
        lockinit(&udev_lk, "udevlk", 0, LK_CANRECURSE);
        TAILQ_INIT(&udevq);
        TAILQ_INIT(&udev_evq);
        udev_event_kernel_cache = objcache_create_simple(M_UDEV, sizeof(struct udev_event_kernel));
}

static void
udev_uninit(void)
{
        objcache_destroy(udev_event_kernel_cache);
}

static void
udev_dev_init(void)
{
        udev_dev = make_autoclone_dev(&udev_dev_ops, &DEVFS_CLONE_BITMAP(udev),
                                      udev_dev_clone,
                                      UID_ROOT, GID_WHEEL, 0600, "udev");
}

static void
udev_dev_uninit(void)
{
        destroy_dev(udev_dev);
}

SYSINIT(subr_udev_register, SI_SUB_CREATE_INIT, SI_ORDER_ANY,
        udev_init, NULL);
SYSUNINIT(subr_udev_register, SI_SUB_CREATE_INIT, SI_ORDER_ANY,
        udev_uninit, NULL);
SYSINIT(subr_udev_dev_register, SI_SUB_DRIVERS, SI_ORDER_ANY,
        udev_dev_init, NULL);
SYSUNINIT(subr_udev_dev_register, SI_SUB_DRIVERS, SI_ORDER_ANY,
        udev_dev_uninit, NULL);