diff options
Diffstat (limited to 'drivers/base/devtmpfs.c')
| -rw-r--r-- | drivers/base/devtmpfs.c | 443 | 
1 files changed, 245 insertions, 198 deletions
diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index 82bbb5967aa..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> @@ -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, NULL); +		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 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; +	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, 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;  } @@ -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;  | 
