|
@@ -29,6 +29,7 @@
|
|
|
#include "xattr.h"
|
|
|
#include "disk-io.h"
|
|
|
#include "props.h"
|
|
|
+#include "locking.h"
|
|
|
|
|
|
|
|
|
ssize_t __btrfs_getxattr(struct inode *inode, const char *name,
|
|
@@ -91,7 +92,7 @@ static int do_setxattr(struct btrfs_trans_handle *trans,
|
|
|
struct inode *inode, const char *name,
|
|
|
const void *value, size_t size, int flags)
|
|
|
{
|
|
|
- struct btrfs_dir_item *di;
|
|
|
+ struct btrfs_dir_item *di = NULL;
|
|
|
struct btrfs_root *root = BTRFS_I(inode)->root;
|
|
|
struct btrfs_path *path;
|
|
|
size_t name_len = strlen(name);
|
|
@@ -103,84 +104,119 @@ static int do_setxattr(struct btrfs_trans_handle *trans,
|
|
|
path = btrfs_alloc_path();
|
|
|
if (!path)
|
|
|
return -ENOMEM;
|
|
|
+ path->skip_release_on_error = 1;
|
|
|
+
|
|
|
+ if (!value) {
|
|
|
+ di = btrfs_lookup_xattr(trans, root, path, btrfs_ino(inode),
|
|
|
+ name, name_len, -1);
|
|
|
+ if (!di && (flags & XATTR_REPLACE))
|
|
|
+ ret = -ENODATA;
|
|
|
+ else if (di)
|
|
|
+ ret = btrfs_delete_one_dir_name(trans, root, path, di);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
|
|
|
+ /*
|
|
|
+ * For a replace we can't just do the insert blindly.
|
|
|
+ * Do a lookup first (read-only btrfs_search_slot), and return if xattr
|
|
|
+ * doesn't exist. If it exists, fall down below to the insert/replace
|
|
|
+ * path - we can't race with a concurrent xattr delete, because the VFS
|
|
|
+ * locks the inode's i_mutex before calling setxattr or removexattr.
|
|
|
+ */
|
|
|
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) {
|
|
|
+ ASSERT(mutex_is_locked(&inode->i_mutex));
|
|
|
+ di = btrfs_lookup_xattr(NULL, root, path, btrfs_ino(inode),
|
|
|
+ name, name_len, 0);
|
|
|
+ if (!di) {
|
|
|
ret = -ENODATA;
|
|
|
goto out;
|
|
|
}
|
|
|
- ret = btrfs_delete_one_dir_name(trans, root, path, di);
|
|
|
- if (ret)
|
|
|
- goto out;
|
|
|
btrfs_release_path(path);
|
|
|
+ di = NULL;
|
|
|
+ }
|
|
|
|
|
|
+ ret = btrfs_insert_xattr_item(trans, root, path, btrfs_ino(inode),
|
|
|
+ name, name_len, value, size);
|
|
|
+ if (ret == -EOVERFLOW) {
|
|
|
/*
|
|
|
- * remove the attribute
|
|
|
+ * We have an existing item in a leaf, split_leaf couldn't
|
|
|
+ * expand it. That item might have or not a dir_item that
|
|
|
+ * matches our target xattr, so lets check.
|
|
|
*/
|
|
|
- if (!value)
|
|
|
- goto out;
|
|
|
- } else {
|
|
|
- di = btrfs_lookup_xattr(NULL, root, path, btrfs_ino(inode),
|
|
|
- name, name_len, 0);
|
|
|
- if (IS_ERR(di)) {
|
|
|
- ret = PTR_ERR(di);
|
|
|
+ ret = 0;
|
|
|
+ btrfs_assert_tree_locked(path->nodes[0]);
|
|
|
+ di = btrfs_match_dir_item_name(root, path, name, name_len);
|
|
|
+ if (!di && !(flags & XATTR_REPLACE)) {
|
|
|
+ ret = -ENOSPC;
|
|
|
goto out;
|
|
|
}
|
|
|
- if (!di && !value)
|
|
|
- goto out;
|
|
|
- btrfs_release_path(path);
|
|
|
+ } else if (ret == -EEXIST) {
|
|
|
+ ret = 0;
|
|
|
+ di = btrfs_match_dir_item_name(root, path, name, name_len);
|
|
|
+ ASSERT(di); /* logic error */
|
|
|
+ } else if (ret) {
|
|
|
+ goto out;
|
|
|
}
|
|
|
|
|
|
-again:
|
|
|
- ret = btrfs_insert_xattr_item(trans, root, path, btrfs_ino(inode),
|
|
|
- name, name_len, value, size);
|
|
|
- /*
|
|
|
- * 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)
|
|
|
+ if (di && (flags & XATTR_CREATE)) {
|
|
|
ret = -EEXIST;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
|
|
|
- if (ret == -EEXIST) {
|
|
|
- if (flags & XATTR_CREATE)
|
|
|
- goto out;
|
|
|
+ if (di) {
|
|
|
/*
|
|
|
- * 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.
|
|
|
+ * We're doing a replace, and it must be atomic, that is, at
|
|
|
+ * any point in time we have either the old or the new xattr
|
|
|
+ * value in the tree. We don't want readers (getxattr and
|
|
|
+ * listxattrs) to miss a value, this is specially important
|
|
|
+ * for ACLs.
|
|
|
*/
|
|
|
- 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;
|
|
|
+ const int slot = path->slots[0];
|
|
|
+ struct extent_buffer *leaf = path->nodes[0];
|
|
|
+ const u16 old_data_len = btrfs_dir_data_len(leaf, di);
|
|
|
+ const u32 item_size = btrfs_item_size_nr(leaf, slot);
|
|
|
+ const u32 data_size = sizeof(*di) + name_len + size;
|
|
|
+ struct btrfs_item *item;
|
|
|
+ unsigned long data_ptr;
|
|
|
+ char *ptr;
|
|
|
+
|
|
|
+ if (size > old_data_len) {
|
|
|
+ if (btrfs_leaf_free_space(root, leaf) <
|
|
|
+ (size - old_data_len)) {
|
|
|
+ ret = -ENOSPC;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- ret = btrfs_delete_one_dir_name(trans, root, path, di);
|
|
|
- if (ret)
|
|
|
- goto out;
|
|
|
+ if (old_data_len + name_len + sizeof(*di) == item_size) {
|
|
|
+ /* No other xattrs packed in the same leaf item. */
|
|
|
+ if (size > old_data_len)
|
|
|
+ btrfs_extend_item(root, path,
|
|
|
+ size - old_data_len);
|
|
|
+ else if (size < old_data_len)
|
|
|
+ btrfs_truncate_item(root, path, data_size, 1);
|
|
|
+ } else {
|
|
|
+ /* There are other xattrs packed in the same item. */
|
|
|
+ ret = btrfs_delete_one_dir_name(trans, root, path, di);
|
|
|
+ if (ret)
|
|
|
+ goto out;
|
|
|
+ btrfs_extend_item(root, path, data_size);
|
|
|
+ }
|
|
|
|
|
|
+ item = btrfs_item_nr(slot);
|
|
|
+ ptr = btrfs_item_ptr(leaf, slot, char);
|
|
|
+ ptr += btrfs_item_size(leaf, item) - data_size;
|
|
|
+ di = (struct btrfs_dir_item *)ptr;
|
|
|
+ btrfs_set_dir_data_len(leaf, di, size);
|
|
|
+ data_ptr = ((unsigned long)(di + 1)) + name_len;
|
|
|
+ write_extent_buffer(leaf, value, data_ptr, size);
|
|
|
+ btrfs_mark_buffer_dirty(leaf);
|
|
|
+ } else {
|
|
|
/*
|
|
|
- * We have a value to set, so go back and try to insert it now.
|
|
|
+ * Insert, and we had space for the xattr, so path->slots[0] is
|
|
|
+ * where our xattr dir_item is and btrfs_insert_xattr_item()
|
|
|
+ * filled it.
|
|
|
*/
|
|
|
- if (value) {
|
|
|
- btrfs_release_path(path);
|
|
|
- goto again;
|
|
|
- }
|
|
|
}
|
|
|
out:
|
|
|
btrfs_free_path(path);
|