#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/lock.h>
#include <sys/file.h>
#include <sys/msgport.h>
#include <sys/sysctl.h>
#include <sys/ucred.h>
#include <sys/devfs.h>
#include <sys/devfs_rules.h>
#include <sys/udev.h>
#include <sys/msgport2.h>
#include <sys/spinlock2.h>
#include <sys/sysref2.h>
MALLOC_DEFINE(M_DEVFS, "devfs", "Device File System (devfs) allocations");
DEVFS_DEFINE_CLONE_BITMAP(ops_id);
static void devfs_cdev_terminate(cdev_t dev);
static void devfs_cdev_lock(cdev_t dev);
static void devfs_cdev_unlock(cdev_t dev);
static struct sysref_class cdev_sysref_class = {
.name = "cdev",
.mtype = M_DEVFS,
.proto = SYSREF_PROTO_DEV,
.offset = offsetof(struct cdev, si_sysref),
.objsize = sizeof(struct cdev),
.nom_cache = 32,
.flags = 0,
.ops = {
.terminate = (sysref_terminate_func_t)devfs_cdev_terminate,
.lock = (sysref_lock_func_t)devfs_cdev_lock,
.unlock = (sysref_unlock_func_t)devfs_cdev_unlock
}
};
static struct objcache *devfs_node_cache;
static struct objcache *devfs_msg_cache;
static struct objcache *devfs_dev_cache;
static struct objcache_malloc_args devfs_node_malloc_args = {
sizeof(struct devfs_node), M_DEVFS };
struct objcache_malloc_args devfs_msg_malloc_args = {
sizeof(struct devfs_msg), M_DEVFS };
struct objcache_malloc_args devfs_dev_malloc_args = {
sizeof(struct cdev), M_DEVFS };
static struct devfs_dev_head devfs_dev_list =
TAILQ_HEAD_INITIALIZER(devfs_dev_list);
static struct devfs_mnt_head devfs_mnt_list =
TAILQ_HEAD_INITIALIZER(devfs_mnt_list);
static struct devfs_chandler_head devfs_chandler_list =
TAILQ_HEAD_INITIALIZER(devfs_chandler_list);
static struct devfs_alias_head devfs_alias_list =
TAILQ_HEAD_INITIALIZER(devfs_alias_list);
static struct devfs_dev_ops_head devfs_dev_ops_list =
TAILQ_HEAD_INITIALIZER(devfs_dev_ops_list);
struct lock devfs_lock;
struct lwkt_token devfs_token;
static struct lwkt_port devfs_dispose_port;
static struct lwkt_port devfs_msg_port;
static struct thread *td_core;
static struct spinlock ino_lock;
static ino_t d_ino;
static int devfs_debug_enable;
static int devfs_run;
static ino_t devfs_fetch_ino(void);
static int devfs_reference_ops(struct dev_ops *ops);
static void devfs_release_ops(struct dev_ops *ops);
static int devfs_create_all_dev_worker(struct devfs_node *);
static int devfs_create_dev_worker(cdev_t, uid_t, gid_t, int);
static int devfs_destroy_dev_worker(cdev_t);
static int devfs_destroy_related_worker(cdev_t);
static int devfs_destroy_dev_by_ops_worker(struct dev_ops *, int);
static int devfs_propagate_dev(cdev_t, int);
static int devfs_unlink_dev(cdev_t dev);
static void devfs_msg_exec(devfs_msg_t msg);
static int devfs_chandler_add_worker(const char *, d_clone_t *);
static int devfs_chandler_del_worker(const char *);
static void devfs_msg_autofree_reply(lwkt_port_t, lwkt_msg_t);
static void devfs_msg_core(void *);
static int devfs_find_device_by_name_worker(devfs_msg_t);
static int devfs_find_device_by_devid_worker(devfs_msg_t);
static int devfs_apply_reset_rules_caller(char *, int);
static int devfs_scan_callback_worker(devfs_scan_t *, void *);
static struct devfs_node *devfs_resolve_or_create_dir(struct devfs_node *,
char *, size_t, int);
static int devfs_make_alias_worker(struct devfs_alias *);
static int devfs_destroy_alias_worker(struct devfs_alias *);
static int devfs_alias_remove(cdev_t);
static int devfs_alias_reap(void);
static int devfs_alias_propagate(struct devfs_alias *, int);
static int devfs_alias_apply(struct devfs_node *, struct devfs_alias *);
static int devfs_alias_check_create(struct devfs_node *);
static int devfs_clr_related_flag_worker(cdev_t, uint32_t);
static int devfs_destroy_related_without_flag_worker(cdev_t, uint32_t);
static void *devfs_reaperp_callback(struct devfs_node *, void *);
static void devfs_iterate_orphans_unmount(struct mount *mp);
static void *devfs_gc_dirs_callback(struct devfs_node *, void *);
static void *devfs_gc_links_callback(struct devfs_node *, struct devfs_node *);
static void *
devfs_inode_to_vnode_worker_callback(struct devfs_node *, ino_t *);
int
devfs_debug(int level, char *fmt, ...)
{
__va_list ap;
__va_start(ap, fmt);
if (level <= devfs_debug_enable)
kvprintf(fmt, ap);
__va_end(ap);
return 0;
}
struct devfs_node *
devfs_allocp(devfs_nodetype devfsnodetype, char *name,
struct devfs_node *parent, struct mount *mp, cdev_t dev)
{
struct devfs_node *node = NULL;
size_t namlen = strlen(name);
node = objcache_get(devfs_node_cache, M_WAITOK);
bzero(node, sizeof(*node));
atomic_add_long(&DEVFS_MNTDATA(mp)->leak_count, 1);
node->d_dev = NULL;
node->nchildren = 1;
node->mp = mp;
node->d_dir.d_ino = devfs_fetch_ino();
node->cookie_jar = 2;
node->mode = DEVFS_DEFAULT_MODE;
node->uid = DEVFS_DEFAULT_UID;
node->gid = DEVFS_DEFAULT_GID;
switch (devfsnodetype) {
case Nroot:
node->flags |= DEVFS_NODE_LINKED;
case Ndir:
TAILQ_INIT(DEVFS_DENODE_HEAD(node));
node->d_dir.d_type = DT_DIR;
node->nchildren = 2;
break;
case Nlink:
node->d_dir.d_type = DT_LNK;
break;
case Nreg:
node->d_dir.d_type = DT_REG;
break;
case Ndev:
if (dev != NULL) {
node->d_dir.d_type = DT_CHR;
node->d_dev = dev;
node->mode = dev->si_perms;
node->uid = dev->si_uid;
node->gid = dev->si_gid;
devfs_alias_check_create(node);
}
break;
default:
panic("devfs_allocp: unknown node type");
}
node->v_node = NULL;
node->node_type = devfsnodetype;
node->d_dir.d_namlen = namlen;
node->d_dir.d_name = kmalloc(namlen+1, M_DEVFS, M_WAITOK);
memcpy(node->d_dir.d_name, name, namlen);
node->d_dir.d_name[namlen] = '\0';
node->parent = parent;
vfs_timestamp(&node->atime);
node->mtime = node->ctime = node->atime;
if (parent) {
if (parent->node_type == Nroot ||
parent->node_type == Ndir) {
parent->nchildren++;
node->cookie = parent->cookie_jar++;
node->flags |= DEVFS_NODE_LINKED;
TAILQ_INSERT_TAIL(DEVFS_DENODE_HEAD(parent), node, link);
++mp->mnt_namecache_gen;
} else {
kprintf("devfs: Cannot link node %p (%s) "
"into %p (%s)\n",
node, node->d_dir.d_name,
parent, parent->d_dir.d_name);
print_backtrace(-1);
}
}
if (DEVFS_MNTDATA(mp)->root_node)
devfs_rule_check_apply(node, NULL);
atomic_add_long(&DEVFS_MNTDATA(mp)->file_count, 1);
return node;
}
int
devfs_allocv(struct vnode **vpp, struct devfs_node *node)
{
struct vnode *vp;
int error = 0;
KKASSERT(node);
try_again:
while ((vp = node->v_node) != NULL) {
vhold(vp);
lockmgr(&devfs_lock, LK_RELEASE);
error = vget(vp, LK_EXCLUSIVE);
vdrop(vp);
lockmgr(&devfs_lock, LK_EXCLUSIVE);
if (error == 0) {
*vpp = vp;
goto out;
}
if (error != ENOENT) {
*vpp = NULL;
goto out;
}
}
lockmgr(&devfs_lock, LK_RELEASE);
if ((error = getnewvnode(VT_DEVFS, node->mp, vpp, 0, 0)) != 0) {
lockmgr(&devfs_lock, LK_EXCLUSIVE);
goto out;
}
lockmgr(&devfs_lock, LK_EXCLUSIVE);
vp = *vpp;
if (node->v_node != NULL) {
vp->v_type = VBAD;
vx_put(vp);
goto try_again;
}
vp->v_data = node;
node->v_node = vp;
switch (node->node_type) {
case Nroot:
vsetflags(vp, VROOT);
case Ndir:
vp->v_type = VDIR;
break;
case Nlink:
vp->v_type = VLNK;
break;
case Nreg:
vp->v_type = VREG;
break;
case Ndev:
vp->v_type = VCHR;
KKASSERT(node->d_dev);
vp->v_uminor = node->d_dev->si_uminor;
vp->v_umajor = node->d_dev->si_umajor;
v_associate_rdev(vp, node->d_dev);
vp->v_ops = &node->mp->mnt_vn_spec_ops;
if (node->d_dev->si_ops->head.flags & D_KVABIO)
vsetflags(vp, VKVABIO);
break;
default:
panic("devfs_allocv: unknown node type");
}
vx_downgrade(vp);
out:
return error;
}
int
devfs_allocvp(struct mount *mp, struct vnode **vpp, devfs_nodetype devfsnodetype,
char *name, struct devfs_node *parent, cdev_t dev)
{
struct devfs_node *node;
node = devfs_allocp(devfsnodetype, name, parent, mp, dev);
if (node != NULL)
devfs_allocv(vpp, node);
else
*vpp = NULL;
return 0;
}
void
devfs_freep(struct devfs_node *node)
{
struct vnode *vp;
int maxloops;
KKASSERT(node);
if (node->flags & DEVFS_DESTROYED) {
if ((node->flags & DEVFS_NLINKSWAIT) &&
node->nlinks == 0) {
kprintf("devfs: final node '%s' on nlinks\n",
node->d_dir.d_name);
if (node->d_dir.d_name) {
kfree(node->d_dir.d_name, M_DEVFS);
node->d_dir.d_name = NULL;
}
objcache_put(devfs_node_cache, node);
} else {
kprintf("devfs: race avoided node '%s' (%p)\n",
node->d_dir.d_name, node);
}
return;
}
node->flags |= DEVFS_DESTROYED;
atomic_subtract_long(&DEVFS_MNTDATA(node->mp)->leak_count, 1);
atomic_subtract_long(&DEVFS_MNTDATA(node->mp)->file_count, 1);
if (node->flags & DEVFS_ORPHANED)
devfs_tracer_del_orphan(node);
maxloops = 1000;
while ((vp = node->v_node) != NULL) {
int relock;
vhold(vp);
if (lockstatus(&devfs_lock, curthread) == LK_EXCLUSIVE) {
lockmgr(&devfs_lock, LK_RELEASE);
relock = 1;
} else {
relock = 0;
}
if (node->v_node == NULL) {
vdrop(vp);
} else if (vget(vp, LK_EXCLUSIVE | LK_RETRY) == 0) {
vdrop(vp);
v_release_rdev(vp);
vp->v_data = NULL;
node->v_node = NULL;
vput(vp);
} else {
vdrop(vp);
}
if (relock)
lockmgr(&devfs_lock, LK_EXCLUSIVE);
if (--maxloops == 0) {
kprintf("devfs_freep: livelock on node %p\n", node);
break;
}
}
if (node->symlink_name) {
kfree(node->symlink_name, M_DEVFS);
node->symlink_name = NULL;
}
if (node->nlinks) {
node->flags |= DEVFS_NLINKSWAIT;
} else {
if (node->d_dir.d_name) {
kfree(node->d_dir.d_name, M_DEVFS);
node->d_dir.d_name = NULL;
}
objcache_put(devfs_node_cache, node);
}
}
static void *devfs_alias_getvp(struct devfs_node *node)
{
struct devfs_node *found = node;
int depth = 0;
while ((found->node_type == Nlink) && (found->link_target)) {
if (depth >= 8) {
devfs_debug(DEVFS_DEBUG_SHOW, "Recursive link or depth >= 8");
break;
}
found = found->link_target;
++depth;
}
return found->v_node;
}
void
devfs_unlinkp(struct devfs_node *node)
{
struct devfs_node *parent;
struct devfs_node *target;
struct vnode *vp;
KKASSERT(node);
devfs_tracer_add_orphan(node);
parent = node->parent;
node->parent = NULL;
if (node->flags & DEVFS_NODE_LINKED) {
if (parent) {
TAILQ_REMOVE(DEVFS_DENODE_HEAD(parent), node, link);
parent->nchildren--;
} else if (node == DEVFS_MNTDATA(node->mp)->root_node) {
DEVFS_MNTDATA(node->mp)->root_node = NULL;
}
node->flags &= ~DEVFS_NODE_LINKED;
}
if (node->node_type == Nlink) {
if ((target = node->link_target) != NULL) {
vp = devfs_alias_getvp(node);
node->link_target = NULL;
target->nlinks--;
if (target->nlinks == 0 &&
(target->flags & DEVFS_DESTROYED)) {
devfs_freep(target);
}
} else {
vp = NULL;
}
} else {
vp = node->v_node;
}
if (vp != NULL)
cache_inval_vp(vp, CINV_DESTROY);
}
void *
devfs_iterate_topology(struct devfs_node *node,
devfs_iterate_callback_t *callback, void *arg1)
{
struct devfs_node *node1, *node2;
void *ret = NULL;
if (((node->node_type == Nroot) || (node->node_type == Ndir)) &&
node->nchildren > 2) {
TAILQ_FOREACH_MUTABLE(node1, DEVFS_DENODE_HEAD(node),
link, node2) {
ret = devfs_iterate_topology(node1, callback, arg1);
if (ret)
return ret;
}
}
ret = callback(node, arg1);
return ret;
}
static void *
devfs_alias_reaper_callback(struct devfs_node *node, void *unused)
{
if (node->node_type == Nlink) {
devfs_unlinkp(node);
devfs_freep(node);
}
return NULL;
}
static void *
devfs_reaperp_callback(struct devfs_node *node, void *unused)
{
devfs_unlinkp(node);
devfs_freep(node);
return NULL;
}
static void
devfs_iterate_orphans_unmount(struct mount *mp)
{
struct devfs_orphan *orphan;
while ((orphan = TAILQ_FIRST(DEVFS_ORPHANLIST(mp))) != NULL) {
devfs_freep(orphan->node);
}
}
static void *
devfs_gc_dirs_callback(struct devfs_node *node, void *unused)
{
if (node->node_type == Ndir) {
if ((node->nchildren == 2) &&
!(node->flags & DEVFS_USER_CREATED)) {
devfs_unlinkp(node);
devfs_freep(node);
}
}
return NULL;
}
static void *
devfs_gc_links_callback(struct devfs_node *node, struct devfs_node *target)
{
if ((node->node_type == Nlink) && (node->link_target == target)) {
devfs_unlinkp(node);
devfs_freep(node);
}
return NULL;
}
int
devfs_gc(struct devfs_node *node)
{
struct devfs_node *root_node = DEVFS_MNTDATA(node->mp)->root_node;
if (node->nlinks > 0)
devfs_iterate_topology(root_node,
(devfs_iterate_callback_t *)devfs_gc_links_callback, node);
devfs_unlinkp(node);
devfs_iterate_topology(root_node,
(devfs_iterate_callback_t *)devfs_gc_dirs_callback, NULL);
devfs_freep(node);
return 0;
}
int
devfs_create_dev(cdev_t dev, uid_t uid, gid_t gid, int perms)
{
reference_dev(dev);
devfs_msg_send_dev(DEVFS_DEVICE_CREATE, dev, uid, gid, perms);
return 0;
}
int
devfs_destroy_dev(cdev_t dev)
{
devfs_msg_send_dev(DEVFS_DEVICE_DESTROY, dev, 0, 0, 0);
return 0;
}
int
devfs_mount_add(struct devfs_mnt_data *mnt)
{
devfs_msg_t msg;
msg = devfs_msg_get();
msg->mdv_mnt = mnt;
devfs_msg_send_sync(DEVFS_MOUNT_ADD, msg);
devfs_msg_put(msg);
return 0;
}
int
devfs_mount_del(struct devfs_mnt_data *mnt)
{
devfs_msg_t msg;
msg = devfs_msg_get();
msg->mdv_mnt = mnt;
devfs_msg_send_sync(DEVFS_MOUNT_DEL, msg);
devfs_msg_put(msg);
return 0;
}
int
devfs_destroy_related(cdev_t dev)
{
devfs_msg_t msg;
msg = devfs_msg_get();
msg->mdv_load = dev;
devfs_msg_send_sync(DEVFS_DESTROY_RELATED, msg);
devfs_msg_put(msg);
return 0;
}
int
devfs_clr_related_flag(cdev_t dev, uint32_t flag)
{
devfs_msg_t msg;
msg = devfs_msg_get();
msg->mdv_flags.dev = dev;
msg->mdv_flags.flag = flag;
devfs_msg_send_sync(DEVFS_CLR_RELATED_FLAG, msg);
devfs_msg_put(msg);
return 0;
}
int
devfs_destroy_related_without_flag(cdev_t dev, uint32_t flag)
{
devfs_msg_t msg;
msg = devfs_msg_get();
msg->mdv_flags.dev = dev;
msg->mdv_flags.flag = flag;
devfs_msg_send_sync(DEVFS_DESTROY_RELATED_WO_FLAG, msg);
devfs_msg_put(msg);
return 0;
}
int
devfs_create_all_dev(struct devfs_node *root)
{
devfs_msg_send_generic(DEVFS_CREATE_ALL_DEV, root);
return 0;
}
int
devfs_destroy_dev_by_ops(struct dev_ops *ops, int minor)
{
devfs_msg_send_ops(DEVFS_DESTROY_DEV_BY_OPS, ops, minor);
return 0;
}
int
devfs_clone_handler_add(const char *name, d_clone_t *nhandler)
{
devfs_msg_t msg;
msg = devfs_msg_get();
msg->mdv_chandler.name = name;
msg->mdv_chandler.nhandler = nhandler;
devfs_msg_send_sync(DEVFS_CHANDLER_ADD, msg);
devfs_msg_put(msg);
return 0;
}
int
devfs_clone_handler_del(const char *name)
{
devfs_msg_t msg;
msg = devfs_msg_get();
msg->mdv_chandler.name = name;
msg->mdv_chandler.nhandler = NULL;
devfs_msg_send_sync(DEVFS_CHANDLER_DEL, msg);
devfs_msg_put(msg);
return 0;
}
cdev_t
devfs_find_device_by_name(const char *fmt, ...)
{
cdev_t found = NULL;
devfs_msg_t msg;
char *target;
__va_list ap;
if (fmt == NULL)
return NULL;
__va_start(ap, fmt);
kvasnprintf(&target, PATH_MAX, fmt, ap);
__va_end(ap);
msg = devfs_msg_get();
msg->mdv_name = target;
devfs_msg_send_sync(DEVFS_FIND_DEVICE_BY_NAME, msg);
found = msg->mdv_cdev;
devfs_msg_put(msg);
kvasfree(&target);
return found;
}
cdev_t
devfs_find_device_by_devid(dev_t udev)
{
cdev_t found = NULL;
devfs_msg_t msg;
msg = devfs_msg_get();
msg->mdv_udev = udev;
devfs_msg_send_sync(DEVFS_FIND_DEVICE_BY_DEVID, msg);
found = msg->mdv_cdev;
devfs_msg_put(msg);
devfs_debug(DEVFS_DEBUG_DEBUG,
"devfs_find_device_by_devid found? %s -end:3-\n",
((found) ? found->si_name:"NO"));
return found;
}
struct vnode *
devfs_inode_to_vnode(struct mount *mp, ino_t target)
{
struct vnode *vp = NULL;
devfs_msg_t msg;
if (mp == NULL)
return NULL;
msg = devfs_msg_get();
msg->mdv_ino.mp = mp;
msg->mdv_ino.ino = target;
devfs_msg_send_sync(DEVFS_INODE_TO_VNODE, msg);
vp = msg->mdv_ino.vp;
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
devfs_msg_put(msg);
return vp;
}
int
devfs_make_alias(const char *name, cdev_t dev_target)
{
struct devfs_alias *alias;
size_t len;
len = strlen(name);
alias = kmalloc(sizeof(struct devfs_alias), M_DEVFS, M_WAITOK);
alias->name = kstrdup(name, M_DEVFS);
alias->namlen = len;
alias->dev_target = dev_target;
devfs_msg_send_generic(DEVFS_MAKE_ALIAS, alias);
return 0;
}
int
devfs_destroy_alias(const char *name, cdev_t dev_target)
{
struct devfs_alias *alias;
size_t len;
len = strlen(name);
alias = kmalloc(sizeof(struct devfs_alias), M_DEVFS, M_WAITOK);
alias->name = kstrdup(name, M_DEVFS);
alias->namlen = len;
alias->dev_target = dev_target;
devfs_msg_send_generic(DEVFS_DESTROY_ALIAS, alias);
return 0;
}
int
devfs_apply_rules(char *mntto)
{
char *new_name;
new_name = kstrdup(mntto, M_DEVFS);
devfs_msg_send_name(DEVFS_APPLY_RULES, new_name);
return 0;
}
int
devfs_reset_rules(char *mntto)
{
char *new_name;
new_name = kstrdup(mntto, M_DEVFS);
devfs_msg_send_name(DEVFS_RESET_RULES, new_name);
return 0;
}
int
devfs_scan_callback(devfs_scan_t *callback, void *arg)
{
devfs_msg_t msg;
KKASSERT(callback);
msg = devfs_msg_get();
msg->mdv_load = callback;
msg->mdv_load2 = arg;
devfs_msg_send_sync(DEVFS_SCAN_CALLBACK, msg);
devfs_msg_put(msg);
return 0;
}
static void
devfs_msg_autofree_reply(lwkt_port_t port, lwkt_msg_t msg)
{
devfs_msg_put((devfs_msg_t)msg);
}
devfs_msg_t
devfs_msg_get(void)
{
return objcache_get(devfs_msg_cache, M_WAITOK);
}
int
devfs_msg_put(devfs_msg_t msg)
{
objcache_put(devfs_msg_cache, msg);
return 0;
}
void
devfs_msg_send(uint32_t cmd, devfs_msg_t devfs_msg)
{
lwkt_port_t port = &devfs_msg_port;
lwkt_initmsg(&devfs_msg->hdr, &devfs_dispose_port, 0);
devfs_msg->hdr.u.ms_result = cmd;
if (port->mpu_td == curthread) {
devfs_msg_exec(devfs_msg);
lwkt_replymsg(&devfs_msg->hdr, 0);
} else {
lwkt_sendmsg(port, (lwkt_msg_t)devfs_msg);
}
}
int
devfs_msg_send_sync(uint32_t cmd, devfs_msg_t devfs_msg)
{
struct lwkt_port rep_port;
int error;
lwkt_port_t port = &devfs_msg_port;
lwkt_initport_thread(&rep_port, curthread);
lwkt_initmsg(&devfs_msg->hdr, &rep_port, 0);
devfs_msg->hdr.u.ms_result = cmd;
error = lwkt_domsg(port, (lwkt_msg_t)devfs_msg, 0);
return error;
}
void
devfs_msg_send_generic(uint32_t cmd, void *load)
{
devfs_msg_t devfs_msg = devfs_msg_get();
devfs_msg->mdv_load = load;
devfs_msg_send(cmd, devfs_msg);
}
void
devfs_msg_send_name(uint32_t cmd, char *name)
{
devfs_msg_t devfs_msg = devfs_msg_get();
devfs_msg->mdv_name = name;
devfs_msg_send(cmd, devfs_msg);
}
void
devfs_msg_send_mount(uint32_t cmd, struct devfs_mnt_data *mnt)
{
devfs_msg_t devfs_msg = devfs_msg_get();
devfs_msg->mdv_mnt = mnt;
devfs_msg_send(cmd, devfs_msg);
}
void
devfs_msg_send_ops(uint32_t cmd, struct dev_ops *ops, int minor)
{
devfs_msg_t devfs_msg = devfs_msg_get();
devfs_msg->mdv_ops.ops = ops;
devfs_msg->mdv_ops.minor = minor;
devfs_msg_send(cmd, devfs_msg);
}
void
devfs_msg_send_chandler(uint32_t cmd, char *name, d_clone_t handler)
{
devfs_msg_t devfs_msg = devfs_msg_get();
devfs_msg->mdv_chandler.name = name;
devfs_msg->mdv_chandler.nhandler = handler;
devfs_msg_send(cmd, devfs_msg);
}
void
devfs_msg_send_dev(uint32_t cmd, cdev_t dev, uid_t uid, gid_t gid, int perms)
{
devfs_msg_t devfs_msg = devfs_msg_get();
devfs_msg->mdv_dev.dev = dev;
devfs_msg->mdv_dev.uid = uid;
devfs_msg->mdv_dev.gid = gid;
devfs_msg->mdv_dev.perms = perms;
devfs_msg_send(cmd, devfs_msg);
}
void
devfs_msg_send_link(uint32_t cmd, char *name, char *target, struct mount *mp)
{
devfs_msg_t devfs_msg = devfs_msg_get();
devfs_msg->mdv_link.name = name;
devfs_msg->mdv_link.target = target;
devfs_msg->mdv_link.mp = mp;
devfs_msg_send(cmd, devfs_msg);
}
static void
devfs_msg_core(void *arg)
{
devfs_msg_t msg;
lwkt_initport_thread(&devfs_msg_port, curthread);
lockmgr(&devfs_lock, LK_EXCLUSIVE);
devfs_run = 1;
wakeup(td_core);
lockmgr(&devfs_lock, LK_RELEASE);
lwkt_gettoken(&devfs_token);
while (devfs_run) {
msg = (devfs_msg_t)lwkt_waitport(&devfs_msg_port, 0);
devfs_debug(DEVFS_DEBUG_DEBUG,
"devfs_msg_core, new msg: %x\n",
(unsigned int)msg->hdr.u.ms_result);
devfs_msg_exec(msg);
lwkt_replymsg(&msg->hdr, 0);
}
lwkt_reltoken(&devfs_token);
wakeup(td_core);
lwkt_exit();
}
static void
devfs_msg_exec(devfs_msg_t msg)
{
struct devfs_mnt_data *mnt;
struct devfs_node *node;
cdev_t dev;
lockmgr(&devfs_lock, LK_EXCLUSIVE);
switch (msg->hdr.u.ms_result) {
case DEVFS_DEVICE_CREATE:
dev = msg->mdv_dev.dev;
devfs_create_dev_worker(dev,
msg->mdv_dev.uid,
msg->mdv_dev.gid,
msg->mdv_dev.perms);
break;
case DEVFS_DEVICE_DESTROY:
dev = msg->mdv_dev.dev;
devfs_destroy_dev_worker(dev);
break;
case DEVFS_DESTROY_RELATED:
devfs_destroy_related_worker(msg->mdv_load);
break;
case DEVFS_DESTROY_DEV_BY_OPS:
devfs_destroy_dev_by_ops_worker(msg->mdv_ops.ops,
msg->mdv_ops.minor);
break;
case DEVFS_CREATE_ALL_DEV:
node = (struct devfs_node *)msg->mdv_load;
devfs_create_all_dev_worker(node);
break;
case DEVFS_MOUNT_ADD:
mnt = msg->mdv_mnt;
TAILQ_INSERT_TAIL(&devfs_mnt_list, mnt, link);
devfs_create_all_dev_worker(mnt->root_node);
break;
case DEVFS_MOUNT_DEL:
mnt = msg->mdv_mnt;
TAILQ_REMOVE(&devfs_mnt_list, mnt, link);
devfs_iterate_topology(mnt->root_node,
devfs_alias_reaper_callback,
NULL);
devfs_iterate_topology(mnt->root_node,
devfs_reaperp_callback,
NULL);
devfs_iterate_orphans_unmount(mnt->mp);
if (mnt->leak_count) {
devfs_debug(DEVFS_DEBUG_SHOW,
"Leaked %ld devfs_node elements!\n",
mnt->leak_count);
}
break;
case DEVFS_CHANDLER_ADD:
devfs_chandler_add_worker(msg->mdv_chandler.name,
msg->mdv_chandler.nhandler);
break;
case DEVFS_CHANDLER_DEL:
devfs_chandler_del_worker(msg->mdv_chandler.name);
break;
case DEVFS_FIND_DEVICE_BY_NAME:
devfs_find_device_by_name_worker(msg);
break;
case DEVFS_FIND_DEVICE_BY_DEVID:
devfs_find_device_by_devid_worker(msg);
break;
case DEVFS_MAKE_ALIAS:
devfs_make_alias_worker((struct devfs_alias *)msg->mdv_load);
break;
case DEVFS_DESTROY_ALIAS:
devfs_destroy_alias_worker((struct devfs_alias *)msg->mdv_load);
break;
case DEVFS_APPLY_RULES:
devfs_apply_reset_rules_caller(msg->mdv_name, 1);
break;
case DEVFS_RESET_RULES:
devfs_apply_reset_rules_caller(msg->mdv_name, 0);
break;
case DEVFS_SCAN_CALLBACK:
devfs_scan_callback_worker((devfs_scan_t *)msg->mdv_load,
msg->mdv_load2);
break;
case DEVFS_CLR_RELATED_FLAG:
devfs_clr_related_flag_worker(msg->mdv_flags.dev,
msg->mdv_flags.flag);
break;
case DEVFS_DESTROY_RELATED_WO_FLAG:
devfs_destroy_related_without_flag_worker(msg->mdv_flags.dev,
msg->mdv_flags.flag);
break;
case DEVFS_INODE_TO_VNODE:
msg->mdv_ino.vp = devfs_iterate_topology(
DEVFS_MNTDATA(msg->mdv_ino.mp)->root_node,
(devfs_iterate_callback_t *)devfs_inode_to_vnode_worker_callback,
&msg->mdv_ino.ino);
break;
case DEVFS_TERMINATE_CORE:
devfs_run = 0;
break;
case DEVFS_SYNC:
break;
default:
devfs_debug(DEVFS_DEBUG_WARNING,
"devfs_msg_core: unknown message "
"received at core\n");
break;
}
lockmgr(&devfs_lock, LK_RELEASE);
}
static void
devfs_devctl_notify(cdev_t dev, const char *ev)
{
static const char prefix[] = "cdev=";
char *data;
int namelen;
namelen = strlen(dev->si_name);
data = kmalloc(namelen + sizeof(prefix), M_TEMP, M_WAITOK);
memcpy(data, prefix, sizeof(prefix) - 1);
memcpy(data + sizeof(prefix) - 1, dev->si_name, namelen + 1);
devctl_notify("DEVFS", "CDEV", ev, data);
kfree(data, M_TEMP);
}
static int
devfs_create_dev_worker(cdev_t dev, uid_t uid, gid_t gid, int perms)
{
KKASSERT(dev);
dev->si_uid = uid;
dev->si_gid = gid;
dev->si_perms = perms;
devfs_link_dev(dev);
devfs_propagate_dev(dev, 1);
udev_event_attach(dev, NULL, 0);
devfs_devctl_notify(dev, "CREATE");
return 0;
}
static int
devfs_destroy_dev_worker(cdev_t dev)
{
int error;
KKASSERT(dev);
KKASSERT((lockstatus(&devfs_lock, curthread)) == LK_EXCLUSIVE);
error = devfs_unlink_dev(dev);
devfs_propagate_dev(dev, 0);
devfs_devctl_notify(dev, "DESTROY");
udev_event_detach(dev, NULL, 0);
if (error == 0)
release_dev(dev);
release_dev(dev);
release_dev(dev);
return 0;
}
static int
devfs_destroy_related_worker(cdev_t needle)
{
cdev_t dev;
restart:
devfs_debug(DEVFS_DEBUG_DEBUG, "related worker: %s\n",
needle->si_name);
TAILQ_FOREACH(dev, &devfs_dev_list, link) {
if (dev->si_parent == needle) {
devfs_destroy_related_worker(dev);
devfs_destroy_dev_worker(dev);
goto restart;
}
}
return 0;
}
static int
devfs_clr_related_flag_worker(cdev_t needle, uint32_t flag)
{
cdev_t dev, dev1;
TAILQ_FOREACH_MUTABLE(dev, &devfs_dev_list, link, dev1) {
if (dev->si_parent == needle) {
devfs_clr_related_flag_worker(dev, flag);
dev->si_flags &= ~flag;
}
}
return 0;
}
static int
devfs_destroy_related_without_flag_worker(cdev_t needle, uint32_t flag)
{
cdev_t dev;
restart:
devfs_debug(DEVFS_DEBUG_DEBUG, "related_wo_flag: %s\n",
needle->si_name);
TAILQ_FOREACH(dev, &devfs_dev_list, link) {
if (dev->si_parent == needle) {
devfs_destroy_related_without_flag_worker(dev, flag);
if (!(dev->si_flags & flag)) {
devfs_destroy_dev_worker(dev);
devfs_debug(DEVFS_DEBUG_DEBUG,
"related_wo_flag: %s restart\n", dev->si_name);
goto restart;
}
}
}
return 0;
}
static int
devfs_create_all_dev_worker(struct devfs_node *root)
{
cdev_t dev;
KKASSERT(root);
TAILQ_FOREACH(dev, &devfs_dev_list, link) {
devfs_create_device_node(root, dev, NULL, NULL, NULL);
}
return 0;
}
static int
devfs_destroy_dev_by_ops_worker(struct dev_ops *ops, int minor)
{
cdev_t dev, dev1;
KKASSERT(ops);
TAILQ_FOREACH_MUTABLE(dev, &devfs_dev_list, link, dev1) {
if (dev->si_ops != ops)
continue;
if ((minor < 0) || (dev->si_uminor == minor)) {
devfs_destroy_dev_worker(dev);
}
}
return 0;
}
static int
devfs_chandler_add_worker(const char *name, d_clone_t *nhandler)
{
struct devfs_clone_handler *chandler = NULL;
u_char len = strlen(name);
if (len == 0)
return 1;
TAILQ_FOREACH(chandler, &devfs_chandler_list, link) {
if (chandler->namlen != len)
continue;
if (!memcmp(chandler->name, name, len)) {
return 1;
}
}
chandler = kmalloc(sizeof(*chandler), M_DEVFS, M_WAITOK | M_ZERO);
chandler->name = kstrdup(name, M_DEVFS);
chandler->namlen = len;
chandler->nhandler = nhandler;
TAILQ_INSERT_TAIL(&devfs_chandler_list, chandler, link);
return 0;
}
static int
devfs_chandler_del_worker(const char *name)
{
struct devfs_clone_handler *chandler, *chandler2;
u_char len = strlen(name);
if (len == 0)
return 1;
TAILQ_FOREACH_MUTABLE(chandler, &devfs_chandler_list, link, chandler2) {
if (chandler->namlen != len)
continue;
if (memcmp(chandler->name, name, len))
continue;
TAILQ_REMOVE(&devfs_chandler_list, chandler, link);
kfree(chandler->name, M_DEVFS);
kfree(chandler, M_DEVFS);
break;
}
return 0;
}
static int
devfs_find_device_by_name_worker(devfs_msg_t devfs_msg)
{
struct devfs_alias *alias;
cdev_t dev;
cdev_t found = NULL;
TAILQ_FOREACH(dev, &devfs_dev_list, link) {
if (strcmp(devfs_msg->mdv_name, dev->si_name) == 0) {
found = dev;
break;
}
}
if (found == NULL) {
TAILQ_FOREACH(alias, &devfs_alias_list, link) {
if (strcmp(devfs_msg->mdv_name, alias->name) == 0) {
found = alias->dev_target;
break;
}
}
}
devfs_msg->mdv_cdev = found;
return 0;
}
static int
devfs_find_device_by_devid_worker(devfs_msg_t devfs_msg)
{
cdev_t dev, dev1;
cdev_t found = NULL;
TAILQ_FOREACH_MUTABLE(dev, &devfs_dev_list, link, dev1) {
if (((dev_t)dev->si_inode) == devfs_msg->mdv_udev) {
found = dev;
break;
}
}
devfs_msg->mdv_cdev = found;
return 0;
}
static int
devfs_make_alias_worker(struct devfs_alias *alias)
{
struct devfs_alias *alias2;
size_t len = strlen(alias->name);
int found = 0;
TAILQ_FOREACH(alias2, &devfs_alias_list, link) {
if (len != alias2->namlen)
continue;
if (!memcmp(alias->name, alias2->name, len)) {
found = 1;
break;
}
}
if (!found) {
TAILQ_INSERT_TAIL(&devfs_alias_list, alias, link);
devfs_alias_propagate(alias, 0);
udev_event_attach(alias->dev_target, alias->name, 1);
} else {
devfs_debug(DEVFS_DEBUG_WARNING,
"Warning: duplicate devfs_make_alias for %s\n",
alias->name);
kfree(alias->name, M_DEVFS);
kfree(alias, M_DEVFS);
}
return 0;
}
static int
devfs_destroy_alias_worker(struct devfs_alias *alias)
{
struct devfs_alias *alias2;
int found = 0;
TAILQ_FOREACH(alias2, &devfs_alias_list, link) {
if (alias->dev_target != alias2->dev_target)
continue;
if (devfs_WildCmp(alias->name, alias2->name) == 0) {
found = 1;
break;
}
}
if (!found) {
devfs_debug(DEVFS_DEBUG_WARNING,
"Warning: devfs_destroy_alias for inexistant alias: %s\n",
alias->name);
kfree(alias->name, M_DEVFS);
kfree(alias, M_DEVFS);
} else {
TAILQ_REMOVE(&devfs_alias_list, alias2, link);
devfs_alias_propagate(alias2, 1);
udev_event_detach(alias2->dev_target, alias2->name, 1);
kfree(alias->name, M_DEVFS);
kfree(alias, M_DEVFS);
kfree(alias2->name, M_DEVFS);
kfree(alias2, M_DEVFS);
}
return 0;
}
static int
devfs_alias_reap(void)
{
struct devfs_alias *alias, *alias2;
TAILQ_FOREACH_MUTABLE(alias, &devfs_alias_list, link, alias2) {
TAILQ_REMOVE(&devfs_alias_list, alias, link);
kfree(alias->name, M_DEVFS);
kfree(alias, M_DEVFS);
}
return 0;
}
static int
devfs_alias_remove(cdev_t dev)
{
struct devfs_alias *alias, *alias2;
TAILQ_FOREACH_MUTABLE(alias, &devfs_alias_list, link, alias2) {
if (alias->dev_target == dev) {
TAILQ_REMOVE(&devfs_alias_list, alias, link);
udev_event_detach(alias->dev_target, alias->name, 1);
kfree(alias->name, M_DEVFS);
kfree(alias, M_DEVFS);
}
}
return 0;
}
static int
devfs_alias_propagate(struct devfs_alias *alias, int remove)
{
struct devfs_mnt_data *mnt;
TAILQ_FOREACH(mnt, &devfs_mnt_list, link) {
if (remove) {
devfs_destroy_node(mnt->root_node, alias->name);
} else {
devfs_alias_apply(mnt->root_node, alias);
}
}
return 0;
}
static int
devfs_alias_apply(struct devfs_node *node, struct devfs_alias *alias)
{
struct devfs_node *node1, *node2;
KKASSERT(alias != NULL);
if ((node->node_type == Nroot) || (node->node_type == Ndir)) {
if (node->nchildren > 2) {
TAILQ_FOREACH_MUTABLE(node1, DEVFS_DENODE_HEAD(node), link, node2) {
devfs_alias_apply(node1, alias);
}
}
} else {
if (node->d_dev == alias->dev_target)
devfs_alias_create(alias->name, node, 0);
}
return 0;
}
static int
devfs_alias_check_create(struct devfs_node *node)
{
struct devfs_alias *alias;
TAILQ_FOREACH(alias, &devfs_alias_list, link) {
if (node->d_dev == alias->dev_target)
devfs_alias_create(alias->name, node, 0);
}
return 0;
}
int
devfs_alias_create(char *name_orig, struct devfs_node *target, int rule_based)
{
struct mount *mp = target->mp;
struct devfs_node *parent = DEVFS_MNTDATA(mp)->root_node;
struct devfs_node *linknode;
char *create_path = NULL;
char *name;
char *name_buf;
int result = 0;
KKASSERT((lockstatus(&devfs_lock, curthread)) == LK_EXCLUSIVE);
name_buf = kmalloc(PATH_MAX, M_TEMP, M_WAITOK);
devfs_resolve_name_path(name_orig, name_buf, &create_path, &name);
if (create_path)
parent = devfs_resolve_or_create_path(parent, create_path, 1);
if (devfs_find_device_node_by_name(parent, name)) {
devfs_debug(DEVFS_DEBUG_WARNING,
"Node already exists: %s "
"(devfs_make_alias_worker)!\n",
name);
result = 1;
goto done;
}
linknode = devfs_allocp(Nlink, name, parent, mp, NULL);
if (linknode == NULL) {
result = 1;
goto done;
}
linknode->link_target = target;
target->nlinks++;
if (rule_based)
linknode->flags |= DEVFS_RULE_CREATED;
done:
kfree(name_buf, M_TEMP);
return (result);
}
static int
devfs_apply_reset_rules_caller(char *mountto, int apply)
{
struct devfs_mnt_data *mnt;
if (mountto[0] == '*') {
TAILQ_FOREACH(mnt, &devfs_mnt_list, link) {
devfs_iterate_topology(mnt->root_node,
(apply)?(devfs_rule_check_apply):(devfs_rule_reset_node),
NULL);
}
} else {
TAILQ_FOREACH(mnt, &devfs_mnt_list, link) {
if (!strcmp(mnt->mp->mnt_stat.f_mntonname, mountto)) {
devfs_iterate_topology(mnt->root_node,
(apply)?(devfs_rule_check_apply):(devfs_rule_reset_node),
NULL);
break;
}
}
}
kfree(mountto, M_DEVFS);
return 0;
}
static int
devfs_scan_callback_worker(devfs_scan_t *callback, void *arg)
{
cdev_t dev, dev1;
struct devfs_alias *alias, *alias1;
TAILQ_FOREACH_MUTABLE(dev, &devfs_dev_list, link, dev1) {
callback(dev->si_name, dev, false, arg);
}
TAILQ_FOREACH_MUTABLE(alias, &devfs_alias_list, link, alias1) {
callback(alias->name, alias->dev_target, true, arg);
}
return 0;
}
static struct devfs_node *
devfs_resolve_or_create_dir(struct devfs_node *parent, char *dir_name,
size_t name_len, int create)
{
struct devfs_node *node, *found = NULL;
TAILQ_FOREACH(node, DEVFS_DENODE_HEAD(parent), link) {
if (name_len != node->d_dir.d_namlen)
continue;
if (!memcmp(dir_name, node->d_dir.d_name, name_len)) {
found = node;
break;
}
}
if ((found == NULL) && (create)) {
found = devfs_allocp(Ndir, dir_name, parent, parent->mp, NULL);
}
return found;
}
struct devfs_node *
devfs_resolve_or_create_path(struct devfs_node *parent, char *path, int create)
{
struct devfs_node *node = parent;
char *buf;
size_t idx = 0;
if (path == NULL)
return parent;
buf = kmalloc(PATH_MAX, M_TEMP, M_WAITOK);
while (*path && idx < PATH_MAX - 1) {
if (*path != '/') {
buf[idx++] = *path;
} else {
buf[idx] = '\0';
node = devfs_resolve_or_create_dir(node, buf, idx, create);
if (node == NULL) {
kfree(buf, M_TEMP);
return NULL;
}
idx = 0;
}
++path;
}
buf[idx] = '\0';
node = devfs_resolve_or_create_dir(node, buf, idx, create);
kfree (buf, M_TEMP);
return (node);
}
int
devfs_resolve_name_path(char *fullpath, char *buf, char **pathp, char **namep)
{
char *name = NULL;
char *path = NULL;
size_t len = strlen(fullpath) + 1;
int i;
KKASSERT((fullpath != NULL) && (buf != NULL));
KKASSERT((pathp != NULL) && (namep != NULL));
memcpy(buf, fullpath, len);
for (i = len-1; i>= 0; i--) {
if (buf[i] == '/') {
buf[i] = '\0';
name = &(buf[i+1]);
path = buf;
break;
}
}
*pathp = path;
if (name) {
*namep = name;
} else {
*namep = buf;
}
return 0;
}
struct devfs_node *
devfs_create_device_node(struct devfs_node *root, cdev_t dev,
int *existsp, char *dev_name, char *path_fmt, ...)
{
struct devfs_node *parent, *node = NULL;
char *path = NULL;
char *name;
char *name_buf;
__va_list ap;
int i, found;
char *create_path = NULL;
char *names = "pqrsPQRS";
name_buf = kmalloc(PATH_MAX, M_TEMP, M_WAITOK);
if (existsp)
*existsp = 0;
if (path_fmt != NULL) {
__va_start(ap, path_fmt);
kvasnprintf(&path, PATH_MAX, path_fmt, ap);
__va_end(ap);
}
parent = devfs_resolve_or_create_path(root, path, 1);
KKASSERT(parent);
devfs_resolve_name_path(
((dev_name == NULL) && (dev))?(dev->si_name):(dev_name),
name_buf, &create_path, &name);
if (create_path)
parent = devfs_resolve_or_create_path(parent, create_path, 1);
node = devfs_find_device_node_by_name(parent, name);
if (node) {
if (node->d_dev == dev) {
if (existsp)
*existsp = 1;
} else {
devfs_debug(DEVFS_DEBUG_WARNING,
"devfs_create_device_node: "
"DEVICE %s ALREADY EXISTS!!! "
"Ignoring creation request.\n",
name);
node = NULL;
}
goto out;
}
node = devfs_allocp(Ndev, name, parent, parent->mp, dev);
vfs_timestamp(&parent->mtime);
if ((dev) && (strlen(dev->si_name) >= 4) &&
(!memcmp(dev->si_name, "ptm/", 4))) {
node->parent->flags |= DEVFS_HIDDEN;
node->flags |= DEVFS_HIDDEN;
}
if ((strlen(name) >= 3) && (!memcmp(name, "pty", 3)))
node->flags |= (DEVFS_PTY | DEVFS_INVISIBLE);
if ((strlen(name) >= 3) && (!memcmp(name, "tty", 3))) {
found = 0;
for (i = 0; i < strlen(names); i++) {
if (name[3] == names[i]) {
found = 1;
break;
}
}
if (found)
node->flags |= (DEVFS_PTY | DEVFS_INVISIBLE);
}
out:
kfree(name_buf, M_TEMP);
kvasfree(&path);
return node;
}
void *
devfs_find_device_node_callback(struct devfs_node *node, cdev_t target)
{
if ((node->node_type == Ndev) && (node->d_dev == target)) {
return node;
}
return NULL;
}
struct devfs_node *
devfs_find_device_node_by_name(struct devfs_node *parent, char *target)
{
struct devfs_node *node, *found = NULL;
size_t len = strlen(target);
TAILQ_FOREACH(node, DEVFS_DENODE_HEAD(parent), link) {
if (len != node->d_dir.d_namlen)
continue;
if (!memcmp(node->d_dir.d_name, target, len)) {
found = node;
break;
}
}
return found;
}
static void *
devfs_inode_to_vnode_worker_callback(struct devfs_node *node, ino_t *inop)
{
struct vnode *vp = NULL;
ino_t target = *inop;
if (node->d_dir.d_ino == target) {
if (node->v_node) {
vp = node->v_node;
vget(vp, LK_EXCLUSIVE | LK_RETRY);
vn_unlock(vp);
} else {
devfs_allocv(&vp, node);
vn_unlock(vp);
}
}
return vp;
}
int
devfs_destroy_device_node(struct devfs_node *root, cdev_t target)
{
KKASSERT(target != NULL);
return devfs_destroy_node(root, target->si_name);
}
int
devfs_destroy_node(struct devfs_node *root, char *target)
{
struct devfs_node *node, *parent;
char *name;
char *name_buf;
char *create_path = NULL;
KKASSERT(target);
name_buf = kmalloc(PATH_MAX, M_TEMP, M_WAITOK);
ksnprintf(name_buf, PATH_MAX, "%s", target);
devfs_resolve_name_path(target, name_buf, &create_path, &name);
if (create_path)
parent = devfs_resolve_or_create_path(root, create_path, 0);
else
parent = root;
if (parent == NULL) {
kfree(name_buf, M_TEMP);
return 1;
}
node = devfs_find_device_node_by_name(parent, name);
if (node) {
vfs_timestamp(&node->parent->mtime);
devfs_gc(node);
}
kfree(name_buf, M_TEMP);
return 0;
}
int
devfs_set_perms(struct devfs_node *node, uid_t uid, gid_t gid,
u_short mode, u_long flags)
{
node->mode = mode;
node->uid = uid;
node->gid = gid;
return 0;
}
static int
devfs_propagate_dev(cdev_t dev, int attach)
{
struct devfs_mnt_data *mnt;
TAILQ_FOREACH(mnt, &devfs_mnt_list, link) {
if (attach) {
devfs_create_device_node(mnt->root_node, dev,
NULL, NULL, NULL);
} else {
devfs_alias_remove(dev);
devfs_destroy_device_node(mnt->root_node, dev);
}
}
return 0;
}
cdev_t
devfs_clone(cdev_t dev, const char *name, size_t len, int mode,
struct ucred *cred)
{
int error;
struct devfs_clone_handler *chandler;
struct dev_clone_args ap;
TAILQ_FOREACH(chandler, &devfs_chandler_list, link) {
if (chandler->namlen != len)
continue;
if ((!memcmp(chandler->name, name, len)) &&
(chandler->nhandler)) {
lockmgr(&devfs_lock, LK_RELEASE);
devfs_config();
ap.a_head.a_dev = dev;
ap.a_dev = NULL;
ap.a_name = name;
ap.a_namelen = len;
ap.a_mode = mode;
ap.a_cred = cred;
error = (chandler->nhandler)(&ap);
lockmgr(&devfs_lock, LK_SHARED);
if (error)
continue;
return ap.a_dev;
}
}
return NULL;
}
void
devfs_tracer_add_orphan(struct devfs_node *node)
{
struct devfs_orphan *orphan;
KKASSERT(node);
orphan = kmalloc(sizeof(struct devfs_orphan), M_DEVFS, M_WAITOK);
orphan->node = node;
KKASSERT((node->flags & DEVFS_ORPHANED) == 0);
node->flags |= DEVFS_ORPHANED;
TAILQ_INSERT_TAIL(DEVFS_ORPHANLIST(node->mp), orphan, link);
}
void
devfs_tracer_del_orphan(struct devfs_node *node)
{
struct devfs_orphan *orphan;
KKASSERT(node);
TAILQ_FOREACH(orphan, DEVFS_ORPHANLIST(node->mp), link) {
if (orphan->node == node) {
node->flags &= ~DEVFS_ORPHANED;
TAILQ_REMOVE(DEVFS_ORPHANLIST(node->mp), orphan, link);
kfree(orphan, M_DEVFS);
break;
}
}
}
size_t
devfs_tracer_orphan_count(struct mount *mp, int cleanup)
{
struct devfs_orphan *orphan, *orphan2;
size_t count = 0;
TAILQ_FOREACH_MUTABLE(orphan, DEVFS_ORPHANLIST(mp), link, orphan2) {
count++;
if (cleanup) {
TAILQ_REMOVE(DEVFS_ORPHANLIST(mp), orphan, link);
orphan->node->flags &= ~DEVFS_ORPHANED;
devfs_freep(orphan->node);
kfree(orphan, M_DEVFS);
}
}
return count;
}
static ino_t
devfs_fetch_ino(void)
{
ino_t ret;
spin_lock(&ino_lock);
ret = d_ino++;
spin_unlock(&ino_lock);
return ret;
}
cdev_t
devfs_new_cdev(struct dev_ops *ops, int minor, struct dev_ops *bops)
{
cdev_t dev = sysref_alloc(&cdev_sysref_class);
sysref_activate(&dev->si_sysref);
reference_dev(dev);
bzero(dev, offsetof(struct cdev, si_sysref));
lockmgr(&devfs_lock, LK_EXCLUSIVE);
dev->si_uid = 0;
dev->si_gid = 0;
dev->si_perms = 0;
dev->si_drv1 = NULL;
dev->si_drv2 = NULL;
dev->si_lastread = 0;
dev->si_lastwrite = 0;
dev->si_dict = NULL;
dev->si_parent = NULL;
dev->si_ops = ops;
dev->si_flags = 0;
dev->si_uminor = minor;
dev->si_bops = bops;
if (bops && (bops->head.flags & D_CANFREE)) {
dev->si_flags |= SI_CANFREE;
} else if (ops->head.flags & D_CANFREE) {
dev->si_flags |= SI_CANFREE;
}
if (bops == NULL)
bops = ops;
dev->si_inode = makeudev(devfs_reference_ops(bops), minor);
dev->si_umajor = umajor(dev->si_inode);
lockmgr(&devfs_lock, LK_RELEASE);
return dev;
}
static void
devfs_cdev_terminate(cdev_t dev)
{
KKASSERT((dev->si_flags & SI_DEVFS_LINKED) == 0);
devfs_release_ops((dev->si_bops)?(dev->si_bops):(dev->si_ops));
lockmgr(&devfs_lock, LK_RELEASE);
sysref_put(&dev->si_sysref);
}
static void
devfs_cdev_lock(cdev_t dev)
{
lockmgr(&devfs_lock, LK_EXCLUSIVE);
}
static void
devfs_cdev_unlock(cdev_t dev)
{
lockmgr(&devfs_lock, LK_RELEASE);
}
static int
devfs_detached_filter_eof(struct knote *kn, long hint)
{
kn->kn_flags |= (EV_EOF | EV_NODATA);
return (1);
}
static void
devfs_detached_filter_detach(struct knote *kn)
{
cdev_t dev = (cdev_t)kn->kn_hook;
knote_remove(&dev->si_kqinfo.ki_note, kn);
}
static struct filterops devfs_detached_filterops =
{ FILTEROP_ISFD, NULL,
devfs_detached_filter_detach,
devfs_detached_filter_eof };
void
devfs_assume_knotes(cdev_t dev, struct kqinfo *kqi)
{
knote_assume_knotes(kqi, &dev->si_kqinfo,
&devfs_detached_filterops, (void *)dev);
KNOTE(&dev->si_kqinfo.ki_note, 0);
}
int
devfs_link_dev(cdev_t dev)
{
KKASSERT((dev->si_flags & SI_DEVFS_LINKED) == 0);
dev->si_flags |= SI_DEVFS_LINKED;
TAILQ_INSERT_TAIL(&devfs_dev_list, dev, link);
return 0;
}
static int
devfs_unlink_dev(cdev_t dev)
{
if ((dev->si_flags & SI_DEVFS_LINKED)) {
TAILQ_REMOVE(&devfs_dev_list, dev, link);
dev->si_flags &= ~SI_DEVFS_LINKED;
return (0);
}
return (EALREADY);
}
int
devfs_node_is_accessible(struct devfs_node *node)
{
if ((node) && (!(node->flags & DEVFS_HIDDEN)))
return 1;
else
return 0;
}
static int
devfs_reference_ops(struct dev_ops *ops)
{
int unit;
struct devfs_dev_ops *found = NULL;
struct devfs_dev_ops *devops;
TAILQ_FOREACH(devops, &devfs_dev_ops_list, link) {
if (devops->ops == ops) {
found = devops;
break;
}
}
if (!found) {
found = kmalloc(sizeof(struct devfs_dev_ops),
M_DEVFS, M_WAITOK);
found->ops = ops;
found->ref_count = 0;
TAILQ_INSERT_TAIL(&devfs_dev_ops_list, found, link);
}
KKASSERT(found);
if (found->ref_count == 0) {
found->id =
devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(ops_id), 255);
if (found->id == -1) {
devfs_debug(DEVFS_DEBUG_WARNING,
"devfs_reference_ops: WARNING: ran "
"out of unique ids\n");
}
}
unit = found->id;
++found->ref_count;
return unit;
}
static void
devfs_release_ops(struct dev_ops *ops)
{
struct devfs_dev_ops *found = NULL;
struct devfs_dev_ops *devops;
TAILQ_FOREACH(devops, &devfs_dev_ops_list, link) {
if (devops->ops == ops) {
found = devops;
break;
}
}
KKASSERT(found);
--found->ref_count;
if (found->ref_count == 0) {
TAILQ_REMOVE(&devfs_dev_ops_list, found, link);
lockmgr(&devfs_lock, LK_RELEASE);
devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(ops_id), found->id);
lockmgr(&devfs_lock, LK_EXCLUSIVE);
kfree(found, M_DEVFS);
}
}
void
devfs_config(void)
{
devfs_msg_t msg;
if (devfs_run && curthread != td_core) {
msg = devfs_msg_get();
devfs_msg_send_sync(DEVFS_SYNC, msg);
devfs_msg_put(msg);
}
}
static void
devfs_init(void)
{
devfs_debug(DEVFS_DEBUG_DEBUG, "devfs_init() called\n");
devfs_node_cache = objcache_create("devfs-node-cache", 0, 0,
NULL, NULL, NULL,
objcache_malloc_alloc,
objcache_malloc_free,
&devfs_node_malloc_args );
devfs_msg_cache = objcache_create("devfs-msg-cache", 0, 0,
NULL, NULL, NULL,
objcache_malloc_alloc,
objcache_malloc_free,
&devfs_msg_malloc_args );
devfs_dev_cache = objcache_create("devfs-dev-cache", 0, 0,
NULL, NULL, NULL,
objcache_malloc_alloc,
objcache_malloc_free,
&devfs_dev_malloc_args );
devfs_clone_bitmap_init(&DEVFS_CLONE_BITMAP(ops_id));
lwkt_initport_replyonly(&devfs_dispose_port, devfs_msg_autofree_reply);
lockinit(&devfs_lock, "devfs_core lock", 0, LK_CANRECURSE);
lwkt_token_init(&devfs_token, "devfs_core");
lockmgr(&devfs_lock, LK_EXCLUSIVE);
lwkt_create(devfs_msg_core, NULL, &td_core, NULL,
0, -1, "devfs_msg_core");
while (devfs_run == 0)
lksleep(td_core, &devfs_lock, 0, "devfsc", 0);
lockmgr(&devfs_lock, LK_RELEASE);
devfs_debug(DEVFS_DEBUG_DEBUG, "devfs_init finished\n");
}
static void
devfs_uninit(void)
{
devfs_debug(DEVFS_DEBUG_DEBUG, "devfs_uninit() called\n");
devfs_msg_send(DEVFS_TERMINATE_CORE, NULL);
while (devfs_run)
tsleep(td_core, 0, "devfsc", hz*10);
tsleep(td_core, 0, "devfsc", hz);
devfs_clone_bitmap_uninit(&DEVFS_CLONE_BITMAP(ops_id));
objcache_destroy(devfs_msg_cache);
objcache_destroy(devfs_node_cache);
objcache_destroy(devfs_dev_cache);
devfs_alias_reap();
}
static int
devfs_sysctl_devname_helper(SYSCTL_HANDLER_ARGS)
{
dev_t udev;
cdev_t found;
int error;
if ((error = SYSCTL_IN(req, &udev, sizeof(dev_t))))
return (error);
devfs_debug(DEVFS_DEBUG_DEBUG,
"devfs sysctl, received udev: %d\n", udev);
if (udev == NOUDEV)
return(EINVAL);
if ((found = devfs_find_device_by_devid(udev)) == NULL)
return(ENOENT);
return(SYSCTL_OUT(req, found->si_name, strlen(found->si_name) + 1));
}
SYSCTL_PROC(_kern, OID_AUTO, devname,
CTLTYPE_OPAQUE | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_NOLOCK,
NULL, 0, devfs_sysctl_devname_helper, "",
"helper for devname(3)");
SYSCTL_NODE(_vfs, OID_AUTO, devfs, CTLFLAG_RW, 0, "devfs");
TUNABLE_INT("vfs.devfs.debug", &devfs_debug_enable);
SYSCTL_INT(_vfs_devfs, OID_AUTO, debug, CTLFLAG_RW, &devfs_debug_enable,
0, "Enable DevFS debugging");
SYSINIT(vfs_devfs_register, SI_SUB_DEVFS_CORE, SI_ORDER_FIRST,
devfs_init, NULL);
SYSUNINIT(vfs_devfs_register, SI_SUB_DEVFS_CORE, SI_ORDER_ANY,
devfs_uninit, NULL);
static int
wildCmp(const char **mary, int d, const char *w, const char *s)
{
int i;
for (;;) {
switch(*w) {
case '*':
if (w[1] == 0)
return(0);
if (w[1] != '?' && w[1] != '*') {
for (i = 0; s + i < mary[d]; ++i) {
if (s[i] == w[1] && wildCmp(mary, d + 1, w + 1, s + i) == 0)
return(0);
}
} else {
for (i = 0; s + i < mary[d]; ++i) {
if (wildCmp(mary, d + 1, w + 1, s + i) == 0)
return(0);
}
}
mary[d] = s;
return(-1);
case '?':
if (*s == 0)
return(-1);
++w;
++s;
break;
default:
if (*w != *s)
return(-1);
if (*w == 0)
return(0);
++w;
++s;
break;
}
}
return(-1);
}
static int
wildCaseCmp(const char **mary, int d, const char *w, const char *s)
{
int i;
for (;;) {
switch(*w) {
case '*':
if (w[1] == 0)
return(0);
if (w[1] != '?' && w[1] != '*') {
for (i = 0; s + i < mary[d]; ++i) {
if (s[i] == w[1] && wildCaseCmp(mary, d + 1, w + 1, s + i) == 0)
return(0);
}
} else {
for (i = 0; s + i < mary[d]; ++i) {
if (wildCaseCmp(mary, d + 1, w + 1, s + i) == 0)
return(0);
}
}
mary[d] = s;
return(-1);
case '?':
if (*s == 0)
return(-1);
++w;
++s;
break;
default:
if (*w != *s) {
#define tolower(x) ((x >= 'A' && x <= 'Z')?(x+('a'-'A')):(x))
if (tolower(*w) != tolower(*s))
return(-1);
}
if (*w == 0)
return(0);
++w;
++s;
break;
}
}
return(-1);
}
struct cdev_privdata {
void *cdpd_data;
d_priv_dtor_t *cdpd_dtr;
};
int
devfs_get_cdevpriv(struct file *fp, void **datap)
{
int error;
if (fp == NULL)
return(EBADF);
spin_lock_shared(&fp->f_spin);
if (fp->f_data1 == NULL) {
*datap = NULL;
error = ENOENT;
} else {
struct cdev_privdata *p = fp->f_data1;
*datap = p->cdpd_data;
error = 0;
}
spin_unlock_shared(&fp->f_spin);
return (error);
}
int
devfs_set_cdevpriv(struct file *fp, void *priv, d_priv_dtor_t *dtr)
{
struct cdev_privdata *p;
int error;
if (fp == NULL)
return (ENOENT);
p = kmalloc(sizeof(struct cdev_privdata), M_DEVFS, M_WAITOK);
p->cdpd_data = priv;
p->cdpd_dtr = dtr;
spin_lock(&fp->f_spin);
if (fp->f_data1 == NULL) {
fp->f_data1 = p;
error = 0;
} else {
error = EBUSY;
}
spin_unlock(&fp->f_spin);
if (error)
kfree(p, M_DEVFS);
return error;
}
void
devfs_clear_cdevpriv(struct file *fp)
{
struct cdev_privdata *p;
if (fp == NULL)
return;
spin_lock(&fp->f_spin);
p = fp->f_data1;
fp->f_data1 = NULL;
spin_unlock(&fp->f_spin);
if (p != NULL) {
p->cdpd_dtr(p->cdpd_data);
kfree(p, M_DEVFS);
}
}
int
devfs_WildCmp(const char *w, const char *s)
{
int i;
int c;
int slen = strlen(s);
const char **mary;
for (i = c = 0; w[i]; ++i) {
if (w[i] == '*')
++c;
}
mary = kmalloc(sizeof(char *) * (c + 1), M_DEVFS, M_WAITOK);
for (i = 0; i < c; ++i)
mary[i] = s + slen;
i = wildCmp(mary, 0, w, s);
kfree(mary, M_DEVFS);
return(i);
}
int
devfs_WildCaseCmp(const char *w, const char *s)
{
int i;
int c;
int slen = strlen(s);
const char **mary;
for (i = c = 0; w[i]; ++i) {
if (w[i] == '*')
++c;
}
mary = kmalloc(sizeof(char *) * (c + 1), M_DEVFS, M_WAITOK);
for (i = 0; i < c; ++i)
mary[i] = s + slen;
i = wildCaseCmp(mary, 0, w, s);
kfree(mary, M_DEVFS);
return(i);
}