diff options
Diffstat (limited to 'drivers/base/devtmpfs.c')
| -rw-r--r-- | drivers/base/devtmpfs.c | 444 |
1 files changed, 256 insertions, 188 deletions
diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index a1cb5afe680..25798db1455 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> @@ -20,34 +20,53 @@ #include <linux/namei.h> #include <linux/fs.h> #include <linux/shmem_fs.h> -#include <linux/cred.h> +#include <linux/ramfs.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 dev_mount = 1; +static int mount_dev = 1; #else -static int dev_mount; +static int mount_dev; #endif +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) { - dev_mount = simple_strtoul(str, NULL, 0); + mount_dev = simple_strtoul(str, NULL, 0); return 1; } __setup("devtmpfs.mount=", mount_param); -static int dev_get_sb(struct file_system_type *fs_type, int flags, - const char *dev_name, void *data, struct vfsmount *mnt) +static struct dentry *dev_mount(struct file_system_type *fs_type, int flags, + const char *dev_name, void *data) { - return get_sb_single(fs_type, flags, data, shmem_fill_super, mnt); +#ifdef CONFIG_TMPFS + return mount_single(fs_type, flags, data, shmem_fill_super); +#else + return mount_single(fs_type, flags, data, ramfs_fill_super); +#endif } static struct file_system_type dev_fs_type = { .name = "devtmpfs", - .get_sb = dev_get_sb, + .mount = dev_mount, .kill_sb = kill_litter_super, }; @@ -60,162 +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) +{ + const char *tmp = NULL; + struct req req; + + if (!thread) + return 0; + + 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; + + if (req.mode == 0) + req.mode = 0600; + if (is_blockdev(dev)) + req.mode |= S_IFBLK; + else + req.mode |= S_IFCHR; + + req.dev = dev; + + 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_delete_node(struct device *dev) +{ + const char *tmp = NULL; + struct req req; + + if (!thread) + return 0; + + req.name = device_get_devnode(dev, NULL, NULL, NULL, &tmp); + if (!req.name) + return -ENOMEM; + + req.mode = 0; + req.dev = dev; + + 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; +} + +static int dev_mkdir(const char *name, umode_t mode) { - struct nameidata nd; struct dentry *dentry; + struct path path; int err; - err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt, - name, LOOKUP_PARENT, &nd); - if (err) - return err; - - dentry = lookup_create(&nd, 1); - if (!IS_ERR(dentry)) { - err = vfs_mkdir(nd.path.dentry->d_inode, dentry, mode); - dput(dentry); - } else { - err = PTR_ERR(dentry); - } - mutex_unlock(&nd.path.dentry->d_inode->i_mutex); + dentry = kern_path_create(AT_FDCWD, name, &path, LOOKUP_DIRECTORY); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); - path_put(&nd.path); + 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; - struct nameidata nd; + char *s; int err = 0; + /* parent directories do not exist, create them */ path = kstrdup(nodepath, GFP_KERNEL); if (!path) return -ENOMEM; - err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt, - path, LOOKUP_PARENT, &nd); - if (err == 0) { - struct dentry *dentry; - - /* create directory right away */ - dentry = lookup_create(&nd, 1); - if (!IS_ERR(dentry)) { - err = vfs_mkdir(nd.path.dentry->d_inode, - dentry, 0755); - dput(dentry); - } - mutex_unlock(&nd.path.dentry->d_inode->i_mutex); - - path_put(&nd.path); - } else if (err == -ENOENT) { - char *s; - - /* parent directories do not exist, create them */ - s = path; - while (1) { - s = strchr(s, '/'); - if (!s) - break; - s[0] = '\0'; - err = dev_mkdir(path, 0755); - if (err && err != -EEXIST) - break; - s[0] = '/'; - s++; - } + 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; } -int devtmpfs_create_node(struct device *dev) +static int handle_create(const char *nodename, umode_t mode, kuid_t uid, + kgid_t gid, 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; + struct path path; int err; - if (!dev_mnt) - return 0; - - nodename = device_get_devnode(dev, &mode, &tmp); - if (!nodename) - return -ENOMEM; - - if (mode == 0) - mode = 0600; - if (is_blockdev(dev)) - mode |= S_IFBLK; - else - mode |= S_IFCHR; - - curr_cred = override_creds(&init_cred); - err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt, - nodename, LOOKUP_PARENT, &nd); - if (err == -ENOENT) { - /* create missing parent directories */ + dentry = kern_path_create(AT_FDCWD, nodename, &path, 0); + if (dentry == ERR_PTR(-ENOENT)) { create_path(nodename); - err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt, - nodename, LOOKUP_PARENT, &nd); - if (err) - goto out; + dentry = kern_path_create(AT_FDCWD, nodename, &path, 0); } - - dentry = lookup_create(&nd, 0); - if (!IS_ERR(dentry)) { - int umask; - - umask = sys_umask(0000); - err = vfs_mknod(nd.path.dentry->d_inode, - dentry, mode, dev->devt); - sys_umask(umask); - /* mark as kernel created inode */ - if (!err) - dentry->d_inode->i_private = &dev_mnt; - dput(dentry); - } else { - err = PTR_ERR(dentry); + 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, NULL); + mutex_unlock(&dentry->d_inode->i_mutex); + + /* mark as kernel-created inode */ + dentry->d_inode->i_private = &thread; } - mutex_unlock(&nd.path.dentry->d_inode->i_mutex); - - path_put(&nd.path); -out: - kfree(tmp); - revert_creds(curr_cred); + 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) - err = vfs_rmdir(nd.path.dentry->d_inode, 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 = -ENOENT; - dput(dentry); + 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; } @@ -228,7 +258,7 @@ static int delete_path(const char *nodepath) if (!path) return -ENOMEM; - while (1) { + for (;;) { char *base; base = strrchr(path, '/'); @@ -247,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 */ @@ -265,56 +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 deleted = 0; 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; - - 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)) { - err = vfs_unlink(nd.path.dentry->d_inode, - dentry); - if (!err || err == -ENOENT) - deleted = 1; - } - } else { - err = -ENOENT; + dentry = kern_path_locked(nodename, &parent); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + 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, NULL); + mutex_unlock(&dentry->d_inode->i_mutex); + err = vfs_unlink(parent.dentry->d_inode, dentry, NULL); + 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; } @@ -322,53 +344,99 @@ out: * If configured, or requested by the commandline, devtmpfs will be * auto-mounted after the kernel mounted the root filesystem. */ -int devtmpfs_mount(const char *mountpoint) +int devtmpfs_mount(const char *mntdir) { - struct path path; int err; - if (!dev_mount) + if (!mount_dev) return 0; - if (!dev_mnt) + if (!thread) return 0; - err = kern_path(mountpoint, LOOKUP_FOLLOW, &path); - if (err) - return err; - err = do_add_mount(dev_mnt, &path, 0, NULL); + err = sys_mount("devtmpfs", (char *)mntdir, "devtmpfs", MS_SILENT, NULL); if (err) printk(KERN_INFO "devtmpfs: error mounting %i\n", err); else printk(KERN_INFO "devtmpfs: mounted\n"); - path_put(&path); 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; - - 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(&dev_fs_type); - 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; |
