ocfs2: Remove xattrs via ocfs2_xa_loc
[linux-2.6.git] / fs / ocfs2 / xattr.c
index 8ae4e5d..22a60a7 100644 (file)
@@ -137,6 +137,51 @@ struct ocfs2_xattr_search {
        int not_found;
 };
 
+/* Operations on struct ocfs2_xa_entry */
+struct ocfs2_xa_loc;
+struct ocfs2_xa_loc_operations {
+       /*
+        * Return a pointer to the appropriate buffer in loc->xl_storage
+        * at the given offset from loc->xl_header.
+        */
+       void *(*xlo_offset_pointer)(struct ocfs2_xa_loc *loc, int offset);
+
+       /*
+        * Remove the name+value at this location.  Do whatever is
+        * appropriate with the remaining name+value pairs.
+        */
+       void (*xlo_wipe_namevalue)(struct ocfs2_xa_loc *loc);
+};
+
+/*
+ * Describes an xattr entry location.  This is a memory structure
+ * tracking the on-disk structure.
+ */
+struct ocfs2_xa_loc {
+       /* The ocfs2_xattr_header inside the on-disk storage. Not NULL. */
+       struct ocfs2_xattr_header *xl_header;
+
+       /* Bytes from xl_header to the end of the storage */
+       int xl_size;
+
+       /*
+        * The ocfs2_xattr_entry this location describes.  If this is
+        * NULL, this location describes the on-disk structure where it
+        * would have been.
+        */
+       struct ocfs2_xattr_entry *xl_entry;
+
+       /*
+        * Internal housekeeping
+        */
+
+       /* Buffer(s) containing this entry */
+       void *xl_storage;
+
+       /* Operations on the storage backing this location */
+       const struct ocfs2_xa_loc_operations *xl_ops;
+};
+
 static int ocfs2_xattr_bucket_get_name_value(struct super_block *sb,
                                             struct ocfs2_xattr_header *xh,
                                             int index,
@@ -1418,6 +1463,192 @@ static int ocfs2_xattr_set_value_outside(struct inode *inode,
 }
 
 /*
+ * Wipe the name+value pair and allow the storage to reclaim it.  This
+ * must be followed by either removal of the entry or a call to
+ * ocfs2_xa_add_namevalue().
+ */
+static void ocfs2_xa_wipe_namevalue(struct ocfs2_xa_loc *loc)
+{
+       loc->xl_ops->xlo_wipe_namevalue(loc);
+}
+
+static void *ocfs2_xa_block_offset_pointer(struct ocfs2_xa_loc *loc,
+                                          int offset)
+{
+       BUG_ON(offset >= loc->xl_size);
+       return (char *)loc->xl_header + offset;
+}
+
+/*
+ * Block storage for xattrs keeps the name+value pairs compacted.  When
+ * we remove one, we have to shift any that preceded it towards the end.
+ */
+static void ocfs2_xa_block_wipe_namevalue(struct ocfs2_xa_loc *loc)
+{
+       int i, offset;
+       int namevalue_offset, first_namevalue_offset, namevalue_size;
+       struct ocfs2_xattr_entry *entry = loc->xl_entry;
+       struct ocfs2_xattr_header *xh = loc->xl_header;
+       u64 value_size = le64_to_cpu(entry->xe_value_size);
+       int count = le16_to_cpu(xh->xh_count);
+
+       namevalue_offset = le16_to_cpu(entry->xe_name_offset);
+       namevalue_size = OCFS2_XATTR_SIZE(entry->xe_name_len);
+       if (value_size > OCFS2_XATTR_INLINE_SIZE)
+               namevalue_size += OCFS2_XATTR_ROOT_SIZE;
+       else
+               namevalue_size += OCFS2_XATTR_SIZE(value_size);
+
+       for (i = 0, first_namevalue_offset = loc->xl_size;
+            i < count; i++) {
+               offset = le16_to_cpu(xh->xh_entries[i].xe_name_offset);
+               if (offset < first_namevalue_offset)
+                       first_namevalue_offset = offset;
+       }
+
+       /* Shift the name+value pairs */
+       memmove((char *)xh + first_namevalue_offset + namevalue_size,
+               (char *)xh + first_namevalue_offset,
+               namevalue_offset - first_namevalue_offset);
+       memset((char *)xh + first_namevalue_offset, 0, namevalue_size);
+
+       /* Now tell xh->xh_entries about it */
+       for (i = 0; i < count; i++) {
+               offset = le16_to_cpu(xh->xh_entries[i].xe_name_offset);
+               if (offset < namevalue_offset)
+                       le16_add_cpu(&xh->xh_entries[i].xe_name_offset,
+                                    namevalue_size);
+       }
+
+       /*
+        * Note that we don't update xh_free_start or xh_name_value_len
+        * because they're not used in block-stored xattrs.
+        */
+}
+
+/*
+ * Operations for xattrs stored in blocks.  This includes inline inode
+ * storage and unindexed ocfs2_xattr_blocks.
+ */
+static const struct ocfs2_xa_loc_operations ocfs2_xa_block_loc_ops = {
+       .xlo_offset_pointer     = ocfs2_xa_block_offset_pointer,
+       .xlo_wipe_namevalue     = ocfs2_xa_block_wipe_namevalue,
+};
+
+static void *ocfs2_xa_bucket_offset_pointer(struct ocfs2_xa_loc *loc,
+                                           int offset)
+{
+       struct ocfs2_xattr_bucket *bucket = loc->xl_storage;
+       int block, block_offset;
+
+       BUG_ON(offset >= OCFS2_XATTR_BUCKET_SIZE);
+
+       /* The header is at the front of the bucket */
+       block = offset >> bucket->bu_inode->i_sb->s_blocksize_bits;
+       block_offset = offset % bucket->bu_inode->i_sb->s_blocksize;
+
+       return bucket_block(bucket, block) + block_offset;
+}
+
+static void ocfs2_xa_bucket_wipe_namevalue(struct ocfs2_xa_loc *loc)
+{
+       int namevalue_size;
+       struct ocfs2_xattr_entry *entry = loc->xl_entry;
+       u64 value_size = le64_to_cpu(entry->xe_value_size);
+
+       namevalue_size = OCFS2_XATTR_SIZE(entry->xe_name_len);
+       if (value_size > OCFS2_XATTR_INLINE_SIZE)
+               namevalue_size += OCFS2_XATTR_ROOT_SIZE;
+       else
+               namevalue_size += OCFS2_XATTR_SIZE(value_size);
+
+       le16_add_cpu(&loc->xl_header->xh_name_value_len, -namevalue_size);
+}
+
+/* Operations for xattrs stored in buckets. */
+static const struct ocfs2_xa_loc_operations ocfs2_xa_bucket_loc_ops = {
+       .xlo_offset_pointer     = ocfs2_xa_bucket_offset_pointer,
+       .xlo_wipe_namevalue     = ocfs2_xa_bucket_wipe_namevalue,
+};
+
+static void ocfs2_xa_remove_entry(struct ocfs2_xa_loc *loc)
+{
+       int index, count;
+       struct ocfs2_xattr_header *xh = loc->xl_header;
+       struct ocfs2_xattr_entry *entry = loc->xl_entry;
+
+       ocfs2_xa_wipe_namevalue(loc);
+       loc->xl_entry = NULL;
+
+       le16_add_cpu(&xh->xh_count, -1);
+       count = le16_to_cpu(xh->xh_count);
+
+       /*
+        * Only zero out the entry if there are more remaining.  This is
+        * important for an empty bucket, as it keeps track of the
+        * bucket's hash value.  It doesn't hurt empty block storage.
+        */
+       if (count) {
+               index = ((char *)entry - (char *)&xh->xh_entries) /
+                       sizeof(struct ocfs2_xattr_entry);
+               memmove(&xh->xh_entries[index], &xh->xh_entries[index + 1],
+                       (count - index) * sizeof(struct ocfs2_xattr_entry));
+               memset(&xh->xh_entries[count], 0,
+                      sizeof(struct ocfs2_xattr_entry));
+       }
+}
+
+static void ocfs2_init_dinode_xa_loc(struct ocfs2_xa_loc *loc,
+                                    struct inode *inode,
+                                    struct buffer_head *bh,
+                                    struct ocfs2_xattr_entry *entry)
+{
+       struct ocfs2_dinode *di = (struct ocfs2_dinode *)bh->b_data;
+
+       loc->xl_ops = &ocfs2_xa_block_loc_ops;
+       loc->xl_storage = bh;
+       loc->xl_entry = entry;
+
+       if (OCFS2_I(inode)->ip_dyn_features & OCFS2_INLINE_XATTR_FL)
+               loc->xl_size = le16_to_cpu(di->i_xattr_inline_size);
+       else {
+               BUG_ON(entry);
+               loc->xl_size = OCFS2_SB(inode->i_sb)->s_xattr_inline_size;
+       }
+       loc->xl_header =
+               (struct ocfs2_xattr_header *)(bh->b_data + bh->b_size -
+                                             loc->xl_size);
+}
+
+static void ocfs2_init_xattr_block_xa_loc(struct ocfs2_xa_loc *loc,
+                                         struct buffer_head *bh,
+                                         struct ocfs2_xattr_entry *entry)
+{
+       struct ocfs2_xattr_block *xb =
+               (struct ocfs2_xattr_block *)bh->b_data;
+
+       BUG_ON(le16_to_cpu(xb->xb_flags) & OCFS2_XATTR_INDEXED);
+
+       loc->xl_ops = &ocfs2_xa_block_loc_ops;
+       loc->xl_storage = bh;
+       loc->xl_header = &(xb->xb_attrs.xb_header);
+       loc->xl_entry = entry;
+       loc->xl_size = bh->b_size - offsetof(struct ocfs2_xattr_block,
+                                            xb_attrs.xb_header);
+}
+
+static void ocfs2_init_xattr_bucket_xa_loc(struct ocfs2_xa_loc *loc,
+                                          struct ocfs2_xattr_bucket *bucket,
+                                          struct ocfs2_xattr_entry *entry)
+{
+       loc->xl_ops = &ocfs2_xa_bucket_loc_ops;
+       loc->xl_storage = bucket;
+       loc->xl_header = bucket_xh(bucket);
+       loc->xl_entry = entry;
+       loc->xl_size = OCFS2_XATTR_BUCKET_SIZE;
+}
+
+/*
  * ocfs2_xattr_set_entry_local()
  *
  * Set, replace or remove extended attribute in local.
@@ -1429,8 +1660,14 @@ static void ocfs2_xattr_set_entry_local(struct inode *inode,
                                        size_t min_offs)
 {
        size_t name_len = strlen(xi->name);
-       int i;
+       struct ocfs2_xa_loc loc;
 
+       if (xs->xattr_bh == xs->inode_bh)
+               ocfs2_init_dinode_xa_loc(&loc, inode, xs->inode_bh,
+                                        xs->not_found ? NULL : xs->here);
+       else
+               ocfs2_init_xattr_block_xa_loc(&loc, xs->xattr_bh,
+                                             xs->not_found ? NULL : xs->here);
        if (xi->value && xs->not_found) {
                /* Insert the new xattr entry. */
                le16_add_cpu(&xs->header->xh_count, 1);
@@ -1469,34 +1706,13 @@ static void ocfs2_xattr_set_entry_local(struct inode *inode,
                               xi->value_len);
                        return;
                }
-               /* Remove the old name+value. */
-               memmove(first_val + size, first_val, val - first_val);
-               memset(first_val, 0, size);
-               xs->here->xe_name_hash = 0;
-               xs->here->xe_name_offset = 0;
-               ocfs2_xattr_set_local(xs->here, 1);
-               xs->here->xe_value_size = 0;
 
-               min_offs += size;
-
-               /* Adjust all value offsets. */
-               last = xs->header->xh_entries;
-               for (i = 0 ; i < le16_to_cpu(xs->header->xh_count); i++) {
-                       size_t o = le16_to_cpu(last->xe_name_offset);
-
-                       if (o < offs)
-                               last->xe_name_offset = cpu_to_le16(o + size);
-                       last += 1;
-               }
+               if (!xi->value)
+                       ocfs2_xa_remove_entry(&loc);
+               else
+                       ocfs2_xa_wipe_namevalue(&loc);
 
-               if (!xi->value) {
-                       /* Remove the old entry. */
-                       last -= 1;
-                       memmove(xs->here, xs->here + 1,
-                               (void *)last - (void *)xs->here);
-                       memset(last, 0, sizeof(struct ocfs2_xattr_entry));
-                       le16_add_cpu(&xs->header->xh_count, -1);
-               }
+               min_offs += size;
        }
        if (xi->value) {
                /* Insert the new name+value. */
@@ -4769,7 +4985,10 @@ static void ocfs2_xattr_set_entry_normal(struct inode *inode,
        size_t blocksize = inode->i_sb->s_blocksize;
        char *val;
        size_t offs, size, new_size;
+       struct ocfs2_xa_loc loc;
 
+       ocfs2_init_xattr_bucket_xa_loc(&loc, xs->bucket,
+                                      xs->not_found ? NULL : xs->here);
        last = &xh->xh_entries[count];
        if (!xs->not_found) {
                xe = xs->here;
@@ -4790,8 +5009,8 @@ static void ocfs2_xattr_set_entry_normal(struct inode *inode,
                new_size = OCFS2_XATTR_SIZE(name_len) +
                           OCFS2_XATTR_SIZE(xi->value_len);
 
-               le16_add_cpu(&xh->xh_name_value_len, -size);
                if (xi->value) {
+                       ocfs2_xa_wipe_namevalue(&loc);
                        if (new_size > size)
                                goto set_new_name_value;
 
@@ -4813,20 +5032,8 @@ static void ocfs2_xattr_set_entry_normal(struct inode *inode,
                        ocfs2_xattr_set_local(xe, local);
                        return;
                } else {
-                       /*
-                        * Remove the old entry if there is more than one.
-                        * We don't remove the last entry so that we can
-                        * use it to indicate the hash value of the empty
-                        * bucket.
-                        */
-                       last -= 1;
-                       le16_add_cpu(&xh->xh_count, -1);
-                       if (xh->xh_count) {
-                               memmove(xe, xe + 1,
-                                       (void *)last - (void *)xe);
-                               memset(last, 0,
-                                      sizeof(struct ocfs2_xattr_entry));
-                       } else
+                       ocfs2_xa_remove_entry(&loc);
+                       if (!xh->xh_count)
                                xh->xh_free_start =
                                        cpu_to_le16(OCFS2_XATTR_BUCKET_SIZE);