diff options
Diffstat (limited to 'fs/configfs/symlink.c')
| -rw-r--r-- | fs/configfs/symlink.c | 197 | 
1 files changed, 86 insertions, 111 deletions
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index 91eac6c55e07..dc5dbf6a81d7 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -55,41 +55,63 @@ static void fill_item_path(struct config_item * item, char * buffer, int length)  	}  } +static int configfs_get_target_path(struct config_item *item, +		struct config_item *target, char *path) +{ +	int depth, size; +	char *s; + +	depth = item_depth(item); +	size = item_path_length(target) + depth * 3 - 1; +	if (size > PATH_MAX) +		return -ENAMETOOLONG; + +	pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size); + +	for (s = path; depth--; s += 3) +		strcpy(s,"../"); + +	fill_item_path(target, path, size); +	pr_debug("%s: path = '%s'\n", __func__, path); +	return 0; +} +  static int create_link(struct config_item *parent_item,  		       struct config_item *item,  		       struct dentry *dentry)  {  	struct configfs_dirent *target_sd = item->ci_dentry->d_fsdata; -	struct configfs_symlink *sl; +	char *body;  	int ret; -	ret = -ENOENT;  	if (!configfs_dirent_is_ready(target_sd)) -		goto out; -	ret = -ENOMEM; -	sl = kmalloc(sizeof(struct configfs_symlink), GFP_KERNEL); -	if (sl) { +		return -ENOENT; + +	body = kzalloc(PAGE_SIZE, GFP_KERNEL); +	if (!body) +		return -ENOMEM; + +	configfs_get(target_sd); +	spin_lock(&configfs_dirent_lock); +	if (target_sd->s_type & CONFIGFS_USET_DROPPING) { +		spin_unlock(&configfs_dirent_lock); +		configfs_put(target_sd); +		kfree(body); +		return -ENOENT; +	} +	target_sd->s_links++; +	spin_unlock(&configfs_dirent_lock); +	ret = configfs_get_target_path(item, item, body); +	if (!ret) +		ret = configfs_create_link(target_sd, parent_item->ci_dentry, +					   dentry, body); +	if (ret) {  		spin_lock(&configfs_dirent_lock); -		if (target_sd->s_type & CONFIGFS_USET_DROPPING) { -			spin_unlock(&configfs_dirent_lock); -			kfree(sl); -			return -ENOENT; -		} -		sl->sl_target = config_item_get(item); -		list_add(&sl->sl_list, &target_sd->s_links); +		target_sd->s_links--;  		spin_unlock(&configfs_dirent_lock); -		ret = configfs_create_link(sl, parent_item->ci_dentry, -					   dentry); -		if (ret) { -			spin_lock(&configfs_dirent_lock); -			list_del_init(&sl->sl_list); -			spin_unlock(&configfs_dirent_lock); -			config_item_put(item); -			kfree(sl); -		} +		configfs_put(target_sd); +		kfree(body);  	} - -out:  	return ret;  } @@ -131,9 +153,8 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna  	 * Fake invisibility if dir belongs to a group/default groups hierarchy  	 * being attached  	 */ -	ret = -ENOENT;  	if (!configfs_dirent_is_ready(sd)) -		goto out; +		return -ENOENT;  	parent_item = configfs_get_config_item(dentry->d_parent);  	type = parent_item->ci_type; @@ -143,11 +164,42 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna  	    !type->ct_item_ops->allow_link)  		goto out_put; +	/* +	 * This is really sick.  What they wanted was a hybrid of +	 * link(2) and symlink(2) - they wanted the target resolved +	 * at syscall time (as link(2) would've done), be a directory +	 * (which link(2) would've refused to do) *AND* be a deep +	 * fucking magic, making the target busy from rmdir POV. +	 * symlink(2) is nothing of that sort, and the locking it +	 * gets matches the normal symlink(2) semantics.  Without +	 * attempts to resolve the target (which might very well +	 * not even exist yet) done prior to locking the parent +	 * directory.  This perversion, OTOH, needs to resolve +	 * the target, which would lead to obvious deadlocks if +	 * attempted with any directories locked. +	 * +	 * Unfortunately, that garbage is userland ABI and we should've +	 * said "no" back in 2005.  Too late now, so we get to +	 * play very ugly games with locking. +	 * +	 * Try *ANYTHING* of that sort in new code, and you will +	 * really regret it.  Just ask yourself - what could a BOFH +	 * do to me and do I want to find it out first-hand? +	 * +	 *  AV, a thoroughly annoyed bastard. +	 */ +	inode_unlock(dir);  	ret = get_target(symname, &path, &target_item, dentry->d_sb); +	inode_lock(dir);  	if (ret)  		goto out_put; -	ret = type->ct_item_ops->allow_link(parent_item, target_item); +	if (dentry->d_inode || d_unhashed(dentry)) +		ret = -EEXIST; +	else +		ret = inode_permission(dir, MAY_WRITE | MAY_EXEC); +	if (!ret) +		ret = type->ct_item_ops->allow_link(parent_item, target_item);  	if (!ret) {  		mutex_lock(&configfs_symlink_mutex);  		ret = create_link(parent_item, target_item, dentry); @@ -162,15 +214,12 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna  out_put:  	config_item_put(parent_item); - -out:  	return ret;  }  int configfs_unlink(struct inode *dir, struct dentry *dentry)  { -	struct configfs_dirent *sd = dentry->d_fsdata; -	struct configfs_symlink *sl; +	struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;  	struct config_item *parent_item;  	const struct config_item_type *type;  	int ret; @@ -179,7 +228,7 @@ int configfs_unlink(struct inode *dir, struct dentry *dentry)  	if (!(sd->s_type & CONFIGFS_ITEM_LINK))  		goto out; -	sl = sd->s_element; +	target_sd = sd->s_element;  	parent_item = configfs_get_config_item(dentry->d_parent);  	type = parent_item->ci_type; @@ -193,21 +242,18 @@ int configfs_unlink(struct inode *dir, struct dentry *dentry)  	/*  	 * drop_link() must be called before -	 * list_del_init(&sl->sl_list), so that the order of +	 * decrementing target's ->s_links, so that the order of  	 * drop_link(this, target) and drop_item(target) is preserved.  	 */  	if (type && type->ct_item_ops &&  	    type->ct_item_ops->drop_link)  		type->ct_item_ops->drop_link(parent_item, -					       sl->sl_target); +					       target_sd->s_element);  	spin_lock(&configfs_dirent_lock); -	list_del_init(&sl->sl_list); +	target_sd->s_links--;  	spin_unlock(&configfs_dirent_lock); - -	/* Put reference from create_link() */ -	config_item_put(sl->sl_target); -	kfree(sl); +	configfs_put(target_sd);  	config_item_put(parent_item); @@ -217,79 +263,8 @@ out:  	return ret;  } -static int configfs_get_target_path(struct config_item * item, struct config_item * target, -				   char *path) -{ -	char * s; -	int depth, size; - -	depth = item_depth(item); -	size = item_path_length(target) + depth * 3 - 1; -	if (size > PATH_MAX) -		return -ENAMETOOLONG; - -	pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size); - -	for (s = path; depth--; s += 3) -		strcpy(s,"../"); - -	fill_item_path(target, path, size); -	pr_debug("%s: path = '%s'\n", __func__, path); - -	return 0; -} - -static int configfs_getlink(struct dentry *dentry, char * path) -{ -	struct config_item *item, *target_item; -	int error = 0; - -	item = configfs_get_config_item(dentry->d_parent); -	if (!item) -		return -EINVAL; - -	target_item = configfs_get_config_item(dentry); -	if (!target_item) { -		config_item_put(item); -		return -EINVAL; -	} - -	down_read(&configfs_rename_sem); -	error = configfs_get_target_path(item, target_item, path); -	up_read(&configfs_rename_sem); - -	config_item_put(item); -	config_item_put(target_item); -	return error; - -} - -static const char *configfs_get_link(struct dentry *dentry, -				     struct inode *inode, -				     struct delayed_call *done) -{ -	char *body; -	int error; - -	if (!dentry) -		return ERR_PTR(-ECHILD); - -	body = kzalloc(PAGE_SIZE, GFP_KERNEL); -	if (!body) -		return ERR_PTR(-ENOMEM); - -	error = configfs_getlink(dentry, body); -	if (!error) { -		set_delayed_call(done, kfree_link, body); -		return body; -	} - -	kfree(body); -	return ERR_PTR(error); -} -  const struct inode_operations configfs_symlink_inode_operations = { -	.get_link = configfs_get_link, +	.get_link = simple_get_link,  	.setattr = configfs_setattr,  };  |