diff options
Diffstat (limited to 'drivers/base/devtmpfs.c')
-rw-r--r-- | drivers/base/devtmpfs.c | 441 |
1 files changed, 244 insertions, 197 deletions
diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index 82bbb5967aa9..7413d065906b 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -7,9 +7,9 @@ * devtmpfs, a tmpfs-based filesystem is created. Every driver-core * device which requests a device node, will add a node in this * filesystem. - * By default, all devices are named after the the name of the - * device, owned by root and have a default mode of 0600. Subsystems - * can overwrite the default setting if needed. + * By default, all devices are named after the name of the device, + * owned by root and have a default mode of 0600. Subsystems can + * overwrite the default setting if needed. */ #include <linux/kernel.h> @@ -21,12 +21,12 @@ #include <linux/fs.h> #include <linux/shmem_fs.h> #include <linux/ramfs.h> -#include <linux/cred.h> #include <linux/sched.h> -#include <linux/init_task.h> #include <linux/slab.h> +#include <linux/kthread.h> +#include "base.h" -static struct vfsmount *dev_mnt; +static struct task_struct *thread; #if defined CONFIG_DEVTMPFS_MOUNT static int mount_dev = 1; @@ -34,7 +34,18 @@ static int mount_dev = 1; static int mount_dev; #endif -static DEFINE_MUTEX(dirlock); +static DEFINE_SPINLOCK(req_lock); + +static struct req { + struct req *next; + struct completion done; + int err; + const char *name; + umode_t mode; /* 0 => delete */ + kuid_t uid; + kgid_t gid; + struct device *dev; +} *requests; static int __init mount_param(char *str) { @@ -68,164 +79,173 @@ static inline int is_blockdev(struct device *dev) static inline int is_blockdev(struct device *dev) { return 0; } #endif -static int dev_mkdir(const char *name, mode_t mode) +int devtmpfs_create_node(struct device *dev) { - struct nameidata nd; - struct dentry *dentry; - int err; + const char *tmp = NULL; + struct req req; - err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt, - name, LOOKUP_PARENT, &nd); - if (err) - return err; + if (!thread) + return 0; - dentry = lookup_create(&nd, 1); - if (!IS_ERR(dentry)) { - err = vfs_mkdir(nd.path.dentry->d_inode, dentry, mode); - if (!err) - /* mark as kernel-created inode */ - dentry->d_inode->i_private = &dev_mnt; - dput(dentry); - } else { - err = PTR_ERR(dentry); - } + req.mode = 0; + req.uid = GLOBAL_ROOT_UID; + req.gid = GLOBAL_ROOT_GID; + req.name = device_get_devnode(dev, &req.mode, &req.uid, &req.gid, &tmp); + if (!req.name) + return -ENOMEM; - mutex_unlock(&nd.path.dentry->d_inode->i_mutex); - path_put(&nd.path); - return err; -} + if (req.mode == 0) + req.mode = 0600; + if (is_blockdev(dev)) + req.mode |= S_IFBLK; + else + req.mode |= S_IFCHR; -static int create_path(const char *nodepath) -{ - int err; + req.dev = dev; - mutex_lock(&dirlock); - err = dev_mkdir(nodepath, 0755); - if (err == -ENOENT) { - char *path; - char *s; - - /* parent directories do not exist, create them */ - path = kstrdup(nodepath, GFP_KERNEL); - if (!path) { - err = -ENOMEM; - goto out; - } - s = path; - for (;;) { - s = strchr(s, '/'); - if (!s) - break; - s[0] = '\0'; - err = dev_mkdir(path, 0755); - if (err && err != -EEXIST) - break; - s[0] = '/'; - s++; - } - kfree(path); - } -out: - mutex_unlock(&dirlock); - return err; + init_completion(&req.done); + + spin_lock(&req_lock); + req.next = requests; + requests = &req; + spin_unlock(&req_lock); + + wake_up_process(thread); + wait_for_completion(&req.done); + + kfree(tmp); + + return req.err; } -int devtmpfs_create_node(struct device *dev) +int devtmpfs_delete_node(struct device *dev) { const char *tmp = NULL; - const char *nodename; - const struct cred *curr_cred; - mode_t mode = 0; - struct nameidata nd; - struct dentry *dentry; - int err; + struct req req; - if (!dev_mnt) + if (!thread) return 0; - nodename = device_get_devnode(dev, &mode, &tmp); - if (!nodename) + req.name = device_get_devnode(dev, NULL, NULL, NULL, &tmp); + if (!req.name) return -ENOMEM; - if (mode == 0) - mode = 0600; - if (is_blockdev(dev)) - mode |= S_IFBLK; - else - mode |= S_IFCHR; + req.mode = 0; + req.dev = dev; - curr_cred = override_creds(&init_cred); + init_completion(&req.done); - err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt, - nodename, LOOKUP_PARENT, &nd); - if (err == -ENOENT) { - create_path(nodename); - err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt, - nodename, LOOKUP_PARENT, &nd); - } - if (err) - goto out; + spin_lock(&req_lock); + req.next = requests; + requests = &req; + spin_unlock(&req_lock); - dentry = lookup_create(&nd, 0); - if (!IS_ERR(dentry)) { - err = vfs_mknod(nd.path.dentry->d_inode, - dentry, mode, dev->devt); - if (!err) { - struct iattr newattrs; + wake_up_process(thread); + wait_for_completion(&req.done); - /* fixup possibly umasked mode */ - newattrs.ia_mode = mode; - newattrs.ia_valid = ATTR_MODE; - mutex_lock(&dentry->d_inode->i_mutex); - notify_change(dentry, &newattrs); - mutex_unlock(&dentry->d_inode->i_mutex); + kfree(tmp); + return req.err; +} - /* mark as kernel-created inode */ - dentry->d_inode->i_private = &dev_mnt; - } - dput(dentry); - } else { - err = PTR_ERR(dentry); +static int dev_mkdir(const char *name, umode_t mode) +{ + struct dentry *dentry; + struct path path; + int err; + + dentry = kern_path_create(AT_FDCWD, name, &path, LOOKUP_DIRECTORY); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + err = vfs_mkdir(path.dentry->d_inode, dentry, mode); + if (!err) + /* mark as kernel-created inode */ + dentry->d_inode->i_private = &thread; + done_path_create(&path, dentry); + return err; +} + +static int create_path(const char *nodepath) +{ + char *path; + char *s; + int err = 0; + + /* parent directories do not exist, create them */ + path = kstrdup(nodepath, GFP_KERNEL); + if (!path) + return -ENOMEM; + + s = path; + for (;;) { + s = strchr(s, '/'); + if (!s) + break; + s[0] = '\0'; + err = dev_mkdir(path, 0755); + if (err && err != -EEXIST) + break; + s[0] = '/'; + s++; } + kfree(path); + return err; +} - mutex_unlock(&nd.path.dentry->d_inode->i_mutex); - path_put(&nd.path); -out: - kfree(tmp); - revert_creds(curr_cred); +static int handle_create(const char *nodename, umode_t mode, kuid_t uid, + kgid_t gid, struct device *dev) +{ + struct dentry *dentry; + struct path path; + int err; + + dentry = kern_path_create(AT_FDCWD, nodename, &path, 0); + if (dentry == ERR_PTR(-ENOENT)) { + create_path(nodename); + dentry = kern_path_create(AT_FDCWD, nodename, &path, 0); + } + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + err = vfs_mknod(path.dentry->d_inode, dentry, mode, dev->devt); + if (!err) { + struct iattr newattrs; + + newattrs.ia_mode = mode; + newattrs.ia_uid = uid; + newattrs.ia_gid = gid; + newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID; + mutex_lock(&dentry->d_inode->i_mutex); + notify_change(dentry, &newattrs); + mutex_unlock(&dentry->d_inode->i_mutex); + + /* mark as kernel-created inode */ + dentry->d_inode->i_private = &thread; + } + done_path_create(&path, dentry); return err; } static int dev_rmdir(const char *name) { - struct nameidata nd; + struct path parent; struct dentry *dentry; int err; - err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt, - name, LOOKUP_PARENT, &nd); - if (err) - return err; - - mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); - dentry = lookup_one_len(nd.last.name, nd.path.dentry, nd.last.len); - if (!IS_ERR(dentry)) { - if (dentry->d_inode) { - if (dentry->d_inode->i_private == &dev_mnt) - err = vfs_rmdir(nd.path.dentry->d_inode, - dentry); - else - err = -EPERM; - } else { - err = -ENOENT; - } - dput(dentry); + dentry = kern_path_locked(name, &parent); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + if (dentry->d_inode) { + if (dentry->d_inode->i_private == &thread) + err = vfs_rmdir(parent.dentry->d_inode, dentry); + else + err = -EPERM; } else { - err = PTR_ERR(dentry); + err = -ENOENT; } - - mutex_unlock(&nd.path.dentry->d_inode->i_mutex); - path_put(&nd.path); + dput(dentry); + mutex_unlock(&parent.dentry->d_inode->i_mutex); + path_put(&parent); return err; } @@ -238,7 +258,6 @@ static int delete_path(const char *nodepath) if (!path) return -ENOMEM; - mutex_lock(&dirlock); for (;;) { char *base; @@ -250,7 +269,6 @@ static int delete_path(const char *nodepath) if (err) break; } - mutex_unlock(&dirlock); kfree(path); return err; @@ -259,7 +277,7 @@ static int delete_path(const char *nodepath) static int dev_mynode(struct device *dev, struct inode *inode, struct kstat *stat) { /* did we create it */ - if (inode->i_private != &dev_mnt) + if (inode->i_private != &thread) return 0; /* does the dev_t match */ @@ -277,69 +295,48 @@ static int dev_mynode(struct device *dev, struct inode *inode, struct kstat *sta return 1; } -int devtmpfs_delete_node(struct device *dev) +static int handle_remove(const char *nodename, struct device *dev) { - const char *tmp = NULL; - const char *nodename; - const struct cred *curr_cred; - struct nameidata nd; + struct path parent; struct dentry *dentry; - struct kstat stat; int deleted = 1; int err; - if (!dev_mnt) - return 0; - - nodename = device_get_devnode(dev, NULL, &tmp); - if (!nodename) - return -ENOMEM; - - curr_cred = override_creds(&init_cred); - err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt, - nodename, LOOKUP_PARENT, &nd); - if (err) - goto out; + dentry = kern_path_locked(nodename, &parent); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); - mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); - dentry = lookup_one_len(nd.last.name, nd.path.dentry, nd.last.len); - if (!IS_ERR(dentry)) { - if (dentry->d_inode) { - err = vfs_getattr(nd.path.mnt, dentry, &stat); - if (!err && dev_mynode(dev, dentry->d_inode, &stat)) { - struct iattr newattrs; - /* - * before unlinking this node, reset permissions - * of possible references like hardlinks - */ - newattrs.ia_uid = 0; - newattrs.ia_gid = 0; - newattrs.ia_mode = stat.mode & ~0777; - newattrs.ia_valid = - ATTR_UID|ATTR_GID|ATTR_MODE; - mutex_lock(&dentry->d_inode->i_mutex); - notify_change(dentry, &newattrs); - mutex_unlock(&dentry->d_inode->i_mutex); - err = vfs_unlink(nd.path.dentry->d_inode, - dentry); - if (!err || err == -ENOENT) - deleted = 1; - } - } else { - err = -ENOENT; + if (dentry->d_inode) { + struct kstat stat; + struct path p = {.mnt = parent.mnt, .dentry = dentry}; + err = vfs_getattr(&p, &stat); + if (!err && dev_mynode(dev, dentry->d_inode, &stat)) { + struct iattr newattrs; + /* + * before unlinking this node, reset permissions + * of possible references like hardlinks + */ + newattrs.ia_uid = GLOBAL_ROOT_UID; + newattrs.ia_gid = GLOBAL_ROOT_GID; + newattrs.ia_mode = stat.mode & ~0777; + newattrs.ia_valid = + ATTR_UID|ATTR_GID|ATTR_MODE; + mutex_lock(&dentry->d_inode->i_mutex); + notify_change(dentry, &newattrs); + mutex_unlock(&dentry->d_inode->i_mutex); + err = vfs_unlink(parent.dentry->d_inode, dentry); + if (!err || err == -ENOENT) + deleted = 1; } - dput(dentry); } else { - err = PTR_ERR(dentry); + err = -ENOENT; } - mutex_unlock(&nd.path.dentry->d_inode->i_mutex); + dput(dentry); + mutex_unlock(&parent.dentry->d_inode->i_mutex); - path_put(&nd.path); + path_put(&parent); if (deleted && strchr(nodename, '/')) delete_path(nodename); -out: - kfree(tmp); - revert_creds(curr_cred); return err; } @@ -354,7 +351,7 @@ int devtmpfs_mount(const char *mntdir) if (!mount_dev) return 0; - if (!dev_mnt) + if (!thread) return 0; err = sys_mount("devtmpfs", (char *)mntdir, "devtmpfs", MS_SILENT, NULL); @@ -365,31 +362,81 @@ int devtmpfs_mount(const char *mntdir) return err; } +static DECLARE_COMPLETION(setup_done); + +static int handle(const char *name, umode_t mode, kuid_t uid, kgid_t gid, + struct device *dev) +{ + if (mode) + return handle_create(name, mode, uid, gid, dev); + else + return handle_remove(name, dev); +} + +static int devtmpfsd(void *p) +{ + char options[] = "mode=0755"; + int *err = p; + *err = sys_unshare(CLONE_NEWNS); + if (*err) + goto out; + *err = sys_mount("devtmpfs", "/", "devtmpfs", MS_SILENT, options); + if (*err) + goto out; + sys_chdir("/.."); /* will traverse into overmounted root */ + sys_chroot("."); + complete(&setup_done); + while (1) { + spin_lock(&req_lock); + while (requests) { + struct req *req = requests; + requests = NULL; + spin_unlock(&req_lock); + while (req) { + struct req *next = req->next; + req->err = handle(req->name, req->mode, + req->uid, req->gid, req->dev); + complete(&req->done); + req = next; + } + spin_lock(&req_lock); + } + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock(&req_lock); + schedule(); + } + return 0; +out: + complete(&setup_done); + return *err; +} + /* * Create devtmpfs instance, driver-core devices will add their device * nodes here. */ int __init devtmpfs_init(void) { - int err; - struct vfsmount *mnt; - char options[] = "mode=0755"; - - err = register_filesystem(&dev_fs_type); + int err = register_filesystem(&dev_fs_type); if (err) { printk(KERN_ERR "devtmpfs: unable to register devtmpfs " "type %i\n", err); return err; } - mnt = kern_mount_data(&dev_fs_type, options); - if (IS_ERR(mnt)) { - err = PTR_ERR(mnt); + thread = kthread_run(devtmpfsd, &err, "kdevtmpfs"); + if (!IS_ERR(thread)) { + wait_for_completion(&setup_done); + } else { + err = PTR_ERR(thread); + thread = NULL; + } + + if (err) { printk(KERN_ERR "devtmpfs: unable to create devtmpfs %i\n", err); unregister_filesystem(&dev_fs_type); return err; } - dev_mnt = mnt; printk(KERN_INFO "devtmpfs: initialized\n"); return 0; |