diff options
Diffstat (limited to 'fs/btrfs/xattr.c')
| -rw-r--r-- | fs/btrfs/xattr.c | 222 | 
1 files changed, 145 insertions, 77 deletions
diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c index 698fdd2c739..ad8328d797e 100644 --- a/fs/btrfs/xattr.c +++ b/fs/btrfs/xattr.c @@ -22,11 +22,13 @@  #include <linux/rwsem.h>  #include <linux/xattr.h>  #include <linux/security.h> +#include <linux/posix_acl_xattr.h>  #include "ctree.h"  #include "btrfs_inode.h"  #include "transaction.h"  #include "xattr.h"  #include "disk-io.h" +#include "props.h"  ssize_t __btrfs_getxattr(struct inode *inode, const char *name, @@ -44,7 +46,7 @@ ssize_t __btrfs_getxattr(struct inode *inode, const char *name,  		return -ENOMEM;  	/* lookup the xattr by name */ -	di = btrfs_lookup_xattr(NULL, root, path, inode->i_ino, name, +	di = btrfs_lookup_xattr(NULL, root, path, btrfs_ino(inode), name,  				strlen(name), 0);  	if (!di) {  		ret = -ENODATA; @@ -102,48 +104,92 @@ static int do_setxattr(struct btrfs_trans_handle *trans,  	if (!path)  		return -ENOMEM; -	/* first lets see if we already have this xattr */ -	di = btrfs_lookup_xattr(trans, root, path, inode->i_ino, name, -				strlen(name), -1); -	if (IS_ERR(di)) { -		ret = PTR_ERR(di); -		goto out; -	} - -	/* ok we already have this xattr, lets remove it */ -	if (di) { -		/* if we want create only exit */ -		if (flags & XATTR_CREATE) { -			ret = -EEXIST; +	if (flags & XATTR_REPLACE) { +		di = btrfs_lookup_xattr(trans, root, path, btrfs_ino(inode), name, +					name_len, -1); +		if (IS_ERR(di)) { +			ret = PTR_ERR(di); +			goto out; +		} else if (!di) { +			ret = -ENODATA;  			goto out;  		} -  		ret = btrfs_delete_one_dir_name(trans, root, path, di); -		BUG_ON(ret); -		btrfs_release_path(root, path); +		if (ret) +			goto out; +		btrfs_release_path(path); -		/* if we don't have a value then we are removing the xattr */ +		/* +		 * remove the attribute +		 */  		if (!value)  			goto out;  	} else { -		btrfs_release_path(root, path); - -		if (flags & XATTR_REPLACE) { -			/* we couldn't find the attr to replace */ -			ret = -ENODATA; +		di = btrfs_lookup_xattr(NULL, root, path, btrfs_ino(inode), +					name, name_len, 0); +		if (IS_ERR(di)) { +			ret = PTR_ERR(di);  			goto out;  		} +		if (!di && !value) +			goto out; +		btrfs_release_path(path);  	} -	/* ok we have to create a completely new xattr */ -	ret = btrfs_insert_xattr_item(trans, root, path, inode->i_ino, +again: +	ret = btrfs_insert_xattr_item(trans, root, path, btrfs_ino(inode),  				      name, name_len, value, size); -	BUG_ON(ret); +	/* +	 * If we're setting an xattr to a new value but the new value is say +	 * exactly BTRFS_MAX_XATTR_SIZE, we could end up with EOVERFLOW getting +	 * back from split_leaf.  This is because it thinks we'll be extending +	 * the existing item size, but we're asking for enough space to add the +	 * item itself.  So if we get EOVERFLOW just set ret to EEXIST and let +	 * the rest of the function figure it out. +	 */ +	if (ret == -EOVERFLOW) +		ret = -EEXIST; + +	if (ret == -EEXIST) { +		if (flags & XATTR_CREATE) +			goto out; +		/* +		 * We can't use the path we already have since we won't have the +		 * proper locking for a delete, so release the path and +		 * re-lookup to delete the thing. +		 */ +		btrfs_release_path(path); +		di = btrfs_lookup_xattr(trans, root, path, btrfs_ino(inode), +					name, name_len, -1); +		if (IS_ERR(di)) { +			ret = PTR_ERR(di); +			goto out; +		} else if (!di) { +			/* Shouldn't happen but just in case... */ +			btrfs_release_path(path); +			goto again; +		} + +		ret = btrfs_delete_one_dir_name(trans, root, path, di); +		if (ret) +			goto out; + +		/* +		 * We have a value to set, so go back and try to insert it now. +		 */ +		if (value) { +			btrfs_release_path(path); +			goto again; +		} +	}  out:  	btrfs_free_path(path);  	return ret;  } +/* + * @value: "" makes the attribute to empty, NULL removes it + */  int __btrfs_setxattr(struct btrfs_trans_handle *trans,  		     struct inode *inode, const char *name,  		     const void *value, size_t size, int flags) @@ -158,17 +204,17 @@ int __btrfs_setxattr(struct btrfs_trans_handle *trans,  	if (IS_ERR(trans))  		return PTR_ERR(trans); -	btrfs_set_trans_block_group(trans, inode); -  	ret = do_setxattr(trans, inode, name, value, size, flags);  	if (ret)  		goto out; +	inode_inc_iversion(inode);  	inode->i_ctime = CURRENT_TIME; +	set_bit(BTRFS_INODE_COPY_EVERYTHING, &BTRFS_I(inode)->runtime_flags);  	ret = btrfs_update_inode(trans, root, inode);  	BUG_ON(ret);  out: -	btrfs_end_transaction_throttle(trans, root); +	btrfs_end_transaction(trans, root);  	return ret;  } @@ -180,18 +226,17 @@ ssize_t btrfs_listxattr(struct dentry *dentry, char *buffer, size_t size)  	struct btrfs_path *path;  	struct extent_buffer *leaf;  	struct btrfs_dir_item *di; -	int ret = 0, slot, advance; +	int ret = 0, slot;  	size_t total_size = 0, size_left = size;  	unsigned long name_ptr;  	size_t name_len; -	u32 nritems;  	/*  	 * ok we want all objects associated with this id.  	 * NOTE: we set key.offset = 0; because we want to start with the  	 * first xattr that we find and walk forward  	 */ -	key.objectid = inode->i_ino; +	key.objectid = btrfs_ino(inode);  	btrfs_set_key_type(&key, BTRFS_XATTR_ITEM_KEY);  	key.offset = 0; @@ -204,34 +249,24 @@ ssize_t btrfs_listxattr(struct dentry *dentry, char *buffer, size_t size)  	ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);  	if (ret < 0)  		goto err; -	advance = 0; +  	while (1) {  		leaf = path->nodes[0]; -		nritems = btrfs_header_nritems(leaf);  		slot = path->slots[0];  		/* this is where we start walking through the path */ -		if (advance || slot >= nritems) { +		if (slot >= btrfs_header_nritems(leaf)) {  			/*  			 * if we've reached the last slot in this leaf we need  			 * to go to the next leaf and reset everything  			 */ -			if (slot >= nritems-1) { -				ret = btrfs_next_leaf(root, path); -				if (ret) -					break; -				leaf = path->nodes[0]; -				nritems = btrfs_header_nritems(leaf); -				slot = path->slots[0]; -			} else { -				/* -				 * just walking through the slots on this leaf -				 */ -				slot++; -				path->slots[0]++; -			} +			ret = btrfs_next_leaf(root, path); +			if (ret < 0) +				goto err; +			else if (ret > 0) +				break; +			continue;  		} -		advance = 1;  		btrfs_item_key_to_cpu(leaf, &found_key, slot); @@ -242,13 +277,15 @@ ssize_t btrfs_listxattr(struct dentry *dentry, char *buffer, size_t size)  			break;  		di = btrfs_item_ptr(leaf, slot, struct btrfs_dir_item); +		if (verify_dir_item(root, leaf, di)) +			goto next;  		name_len = btrfs_dir_name_len(leaf, di);  		total_size += name_len + 1;  		/* we are just looking for how big our buffer needs to be */  		if (!size) -			continue; +			goto next;  		if (!buffer || (name_len + 1) > size_left) {  			ret = -ERANGE; @@ -261,6 +298,8 @@ ssize_t btrfs_listxattr(struct dentry *dentry, char *buffer, size_t size)  		size_left -= name_len + 1;  		buffer += name_len + 1; +next: +		path->slots[0]++;  	}  	ret = total_size; @@ -276,8 +315,8 @@ err:   */  const struct xattr_handler *btrfs_xattr_handlers[] = {  #ifdef CONFIG_BTRFS_FS_POSIX_ACL -	&btrfs_xattr_acl_access_handler, -	&btrfs_xattr_acl_default_handler, +	&posix_acl_access_xattr_handler, +	&posix_acl_default_xattr_handler,  #endif  	NULL,  }; @@ -294,7 +333,8 @@ static bool btrfs_is_valid_xattr(const char *name)  			XATTR_SECURITY_PREFIX_LEN) ||  	       !strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN) ||  	       !strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) || -	       !strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN); +	       !strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) || +		!strncmp(name, XATTR_BTRFS_PREFIX, XATTR_BTRFS_PREFIX_LEN);  }  ssize_t btrfs_getxattr(struct dentry *dentry, const char *name, @@ -316,6 +356,15 @@ ssize_t btrfs_getxattr(struct dentry *dentry, const char *name,  int btrfs_setxattr(struct dentry *dentry, const char *name, const void *value,  		   size_t size, int flags)  { +	struct btrfs_root *root = BTRFS_I(dentry->d_inode)->root; + +	/* +	 * The permission on security.* and system.* is not checked +	 * in permission(). +	 */ +	if (btrfs_root_readonly(root)) +		return -EROFS; +  	/*  	 * If this is a request for a synthetic attribute in the system.*  	 * namespace use the generic infrastructure to resolve a handler @@ -327,6 +376,10 @@ int btrfs_setxattr(struct dentry *dentry, const char *name, const void *value,  	if (!btrfs_is_valid_xattr(name))  		return -EOPNOTSUPP; +	if (!strncmp(name, XATTR_BTRFS_PREFIX, XATTR_BTRFS_PREFIX_LEN)) +		return btrfs_set_prop(dentry->d_inode, name, +				      value, size, flags); +  	if (size == 0)  		value = "";  /* empty EA, do not remove */ @@ -336,6 +389,15 @@ int btrfs_setxattr(struct dentry *dentry, const char *name, const void *value,  int btrfs_removexattr(struct dentry *dentry, const char *name)  { +	struct btrfs_root *root = BTRFS_I(dentry->d_inode)->root; + +	/* +	 * The permission on security.* and system.* is not checked +	 * in permission(). +	 */ +	if (btrfs_root_readonly(root)) +		return -EROFS; +  	/*  	 * If this is a request for a synthetic attribute in the system.*  	 * namespace use the generic infrastructure to resolve a handler @@ -347,38 +409,44 @@ int btrfs_removexattr(struct dentry *dentry, const char *name)  	if (!btrfs_is_valid_xattr(name))  		return -EOPNOTSUPP; +	if (!strncmp(name, XATTR_BTRFS_PREFIX, XATTR_BTRFS_PREFIX_LEN)) +		return btrfs_set_prop(dentry->d_inode, name, +				      NULL, 0, XATTR_REPLACE); +  	return __btrfs_setxattr(NULL, dentry->d_inode, name, NULL, 0,  				XATTR_REPLACE);  } -int btrfs_xattr_security_init(struct btrfs_trans_handle *trans, -			      struct inode *inode, struct inode *dir) +static int btrfs_initxattrs(struct inode *inode, +			    const struct xattr *xattr_array, void *fs_info)  { -	int err; -	size_t len; -	void *value; -	char *suffix; +	const struct xattr *xattr; +	struct btrfs_trans_handle *trans = fs_info;  	char *name; +	int err = 0; -	err = security_inode_init_security(inode, dir, &suffix, &value, &len); -	if (err) { -		if (err == -EOPNOTSUPP) -			return 0; -		return err; -	} - -	name = kmalloc(XATTR_SECURITY_PREFIX_LEN + strlen(suffix) + 1, -		       GFP_NOFS); -	if (!name) { -		err = -ENOMEM; -	} else { +	for (xattr = xattr_array; xattr->name != NULL; xattr++) { +		name = kmalloc(XATTR_SECURITY_PREFIX_LEN + +			       strlen(xattr->name) + 1, GFP_NOFS); +		if (!name) { +			err = -ENOMEM; +			break; +		}  		strcpy(name, XATTR_SECURITY_PREFIX); -		strcpy(name + XATTR_SECURITY_PREFIX_LEN, suffix); -		err = __btrfs_setxattr(trans, inode, name, value, len, 0); +		strcpy(name + XATTR_SECURITY_PREFIX_LEN, xattr->name); +		err = __btrfs_setxattr(trans, inode, name, +				       xattr->value, xattr->value_len, 0);  		kfree(name); +		if (err < 0) +			break;  	} - -	kfree(suffix); -	kfree(value);  	return err;  } + +int btrfs_xattr_security_init(struct btrfs_trans_handle *trans, +			      struct inode *inode, struct inode *dir, +			      const struct qstr *qstr) +{ +	return security_inode_init_security(inode, dir, qstr, +					    &btrfs_initxattrs, trans); +}  | 
