Merge branch 'akpm' (patches from Andrew)
[cascardo/linux.git] / fs / ext4 / xattr.c
index 2eb935c..c15d633 100644 (file)
@@ -199,6 +199,8 @@ ext4_xattr_check_names(struct ext4_xattr_entry *entry, void *end,
        }
 
        while (!IS_LAST_ENTRY(entry)) {
+               if (entry->e_value_block != 0)
+                       return -EFSCORRUPTED;
                if (entry->e_value_size != 0 &&
                    (value_start + le16_to_cpu(entry->e_value_offs) <
                     (void *)e + sizeof(__u32) ||
@@ -641,7 +643,7 @@ static size_t ext4_xattr_free_space(struct ext4_xattr_entry *last,
                                    size_t *min_offs, void *base, int *total)
 {
        for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
-               if (!last->e_value_block && last->e_value_size) {
+               if (last->e_value_size) {
                        size_t offs = le16_to_cpu(last->e_value_offs);
                        if (offs < *min_offs)
                                *min_offs = offs;
@@ -661,7 +663,7 @@ ext4_xattr_set_entry(struct ext4_xattr_info *i, struct ext4_xattr_search *s)
        /* Compute min_offs and last. */
        last = s->first;
        for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
-               if (!last->e_value_block && last->e_value_size) {
+               if (last->e_value_size) {
                        size_t offs = le16_to_cpu(last->e_value_offs);
                        if (offs < min_offs)
                                min_offs = offs;
@@ -669,7 +671,7 @@ ext4_xattr_set_entry(struct ext4_xattr_info *i, struct ext4_xattr_search *s)
        }
        free = min_offs - ((void *)last - s->base) - sizeof(__u32);
        if (!s->not_found) {
-               if (!s->here->e_value_block && s->here->e_value_size) {
+               if (s->here->e_value_size) {
                        size_t size = le32_to_cpu(s->here->e_value_size);
                        free += EXT4_XATTR_SIZE(size);
                }
@@ -691,7 +693,7 @@ ext4_xattr_set_entry(struct ext4_xattr_info *i, struct ext4_xattr_search *s)
                s->here->e_name_len = name_len;
                memcpy(s->here->e_name, i->name, name_len);
        } else {
-               if (!s->here->e_value_block && s->here->e_value_size) {
+               if (s->here->e_value_size) {
                        void *first_val = s->base + min_offs;
                        size_t offs = le16_to_cpu(s->here->e_value_offs);
                        void *val = s->base + offs;
@@ -725,8 +727,7 @@ ext4_xattr_set_entry(struct ext4_xattr_info *i, struct ext4_xattr_search *s)
                        last = s->first;
                        while (!IS_LAST_ENTRY(last)) {
                                size_t o = le16_to_cpu(last->e_value_offs);
-                               if (!last->e_value_block &&
-                                   last->e_value_size && o < offs)
+                               if (last->e_value_size && o < offs)
                                        last->e_value_offs =
                                                cpu_to_le16(o + size);
                                last = EXT4_XATTR_NEXT(last);
@@ -1318,18 +1319,19 @@ retry:
  */
 static void ext4_xattr_shift_entries(struct ext4_xattr_entry *entry,
                                     int value_offs_shift, void *to,
-                                    void *from, size_t n, int blocksize)
+                                    void *from, size_t n)
 {
        struct ext4_xattr_entry *last = entry;
        int new_offs;
 
+       /* We always shift xattr headers further thus offsets get lower */
+       BUG_ON(value_offs_shift > 0);
+
        /* Adjust the value offsets of the entries */
        for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
-               if (!last->e_value_block && last->e_value_size) {
+               if (last->e_value_size) {
                        new_offs = le16_to_cpu(last->e_value_offs) +
                                                        value_offs_shift;
-                       BUG_ON(new_offs + le32_to_cpu(last->e_value_size)
-                                > blocksize);
                        last->e_value_offs = cpu_to_le16(new_offs);
                }
        }
@@ -1337,6 +1339,141 @@ static void ext4_xattr_shift_entries(struct ext4_xattr_entry *entry,
        memmove(to, from, n);
 }
 
+/*
+ * Move xattr pointed to by 'entry' from inode into external xattr block
+ */
+static int ext4_xattr_move_to_block(handle_t *handle, struct inode *inode,
+                                   struct ext4_inode *raw_inode,
+                                   struct ext4_xattr_entry *entry)
+{
+       struct ext4_xattr_ibody_find *is = NULL;
+       struct ext4_xattr_block_find *bs = NULL;
+       char *buffer = NULL, *b_entry_name = NULL;
+       size_t value_offs, value_size;
+       struct ext4_xattr_info i = {
+               .value = NULL,
+               .value_len = 0,
+               .name_index = entry->e_name_index,
+       };
+       struct ext4_xattr_ibody_header *header = IHDR(inode, raw_inode);
+       int error;
+
+       value_offs = le16_to_cpu(entry->e_value_offs);
+       value_size = le32_to_cpu(entry->e_value_size);
+
+       is = kzalloc(sizeof(struct ext4_xattr_ibody_find), GFP_NOFS);
+       bs = kzalloc(sizeof(struct ext4_xattr_block_find), GFP_NOFS);
+       buffer = kmalloc(value_size, GFP_NOFS);
+       b_entry_name = kmalloc(entry->e_name_len + 1, GFP_NOFS);
+       if (!is || !bs || !buffer || !b_entry_name) {
+               error = -ENOMEM;
+               goto out;
+       }
+
+       is->s.not_found = -ENODATA;
+       bs->s.not_found = -ENODATA;
+       is->iloc.bh = NULL;
+       bs->bh = NULL;
+
+       /* Save the entry name and the entry value */
+       memcpy(buffer, (void *)IFIRST(header) + value_offs, value_size);
+       memcpy(b_entry_name, entry->e_name, entry->e_name_len);
+       b_entry_name[entry->e_name_len] = '\0';
+       i.name = b_entry_name;
+
+       error = ext4_get_inode_loc(inode, &is->iloc);
+       if (error)
+               goto out;
+
+       error = ext4_xattr_ibody_find(inode, &i, is);
+       if (error)
+               goto out;
+
+       /* Remove the chosen entry from the inode */
+       error = ext4_xattr_ibody_set(handle, inode, &i, is);
+       if (error)
+               goto out;
+
+       i.name = b_entry_name;
+       i.value = buffer;
+       i.value_len = value_size;
+       error = ext4_xattr_block_find(inode, &i, bs);
+       if (error)
+               goto out;
+
+       /* Add entry which was removed from the inode into the block */
+       error = ext4_xattr_block_set(handle, inode, &i, bs);
+       if (error)
+               goto out;
+       error = 0;
+out:
+       kfree(b_entry_name);
+       kfree(buffer);
+       if (is)
+               brelse(is->iloc.bh);
+       kfree(is);
+       kfree(bs);
+
+       return error;
+}
+
+static int ext4_xattr_make_inode_space(handle_t *handle, struct inode *inode,
+                                      struct ext4_inode *raw_inode,
+                                      int isize_diff, size_t ifree,
+                                      size_t bfree, int *total_ino)
+{
+       struct ext4_xattr_ibody_header *header = IHDR(inode, raw_inode);
+       struct ext4_xattr_entry *small_entry;
+       struct ext4_xattr_entry *entry;
+       struct ext4_xattr_entry *last;
+       unsigned int entry_size;        /* EA entry size */
+       unsigned int total_size;        /* EA entry size + value size */
+       unsigned int min_total_size;
+       int error;
+
+       while (isize_diff > ifree) {
+               entry = NULL;
+               small_entry = NULL;
+               min_total_size = ~0U;
+               last = IFIRST(header);
+               /* Find the entry best suited to be pushed into EA block */
+               for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
+                       total_size =
+                       EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
+                                       EXT4_XATTR_LEN(last->e_name_len);
+                       if (total_size <= bfree &&
+                           total_size < min_total_size) {
+                               if (total_size + ifree < isize_diff) {
+                                       small_entry = last;
+                               } else {
+                                       entry = last;
+                                       min_total_size = total_size;
+                               }
+                       }
+               }
+
+               if (entry == NULL) {
+                       if (small_entry == NULL)
+                               return -ENOSPC;
+                       entry = small_entry;
+               }
+
+               entry_size = EXT4_XATTR_LEN(entry->e_name_len);
+               total_size = entry_size +
+                       EXT4_XATTR_SIZE(le32_to_cpu(entry->e_value_size));
+               error = ext4_xattr_move_to_block(handle, inode, raw_inode,
+                                                entry);
+               if (error)
+                       return error;
+
+               *total_ino -= entry_size;
+               ifree += total_size;
+               bfree -= total_size;
+       }
+
+       return 0;
+}
+
 /*
  * Expand an inode by new_extra_isize bytes when EAs are present.
  * Returns 0 on success or negative error number on failure.
@@ -1345,14 +1482,11 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
                               struct ext4_inode *raw_inode, handle_t *handle)
 {
        struct ext4_xattr_ibody_header *header;
-       struct ext4_xattr_entry *entry, *last, *first;
        struct buffer_head *bh = NULL;
-       struct ext4_xattr_ibody_find *is = NULL;
-       struct ext4_xattr_block_find *bs = NULL;
-       char *buffer = NULL, *b_entry_name = NULL;
-       size_t min_offs, free;
+       size_t min_offs;
+       size_t ifree, bfree;
        int total_ino;
-       void *base, *start, *end;
+       void *base, *end;
        int error = 0, tried_min_extra_isize = 0;
        int s_min_extra_isize = le16_to_cpu(EXT4_SB(inode->i_sb)->s_es->s_min_extra_isize);
        int isize_diff; /* How much do we need to grow i_extra_isize */
@@ -1368,34 +1502,24 @@ retry:
                goto out;
 
        header = IHDR(inode, raw_inode);
-       entry = IFIRST(header);
 
        /*
         * Check if enough free space is available in the inode to shift the
         * entries ahead by new_extra_isize.
         */
 
-       base = start = entry;
+       base = IFIRST(header);
        end = (void *)raw_inode + EXT4_SB(inode->i_sb)->s_inode_size;
        min_offs = end - base;
-       last = entry;
        total_ino = sizeof(struct ext4_xattr_ibody_header);
 
        error = xattr_check_inode(inode, header, end);
        if (error)
                goto cleanup;
 
-       free = ext4_xattr_free_space(last, &min_offs, base, &total_ino);
-       if (free >= isize_diff) {
-               entry = IFIRST(header);
-               ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize
-                               - new_extra_isize, (void *)raw_inode +
-                               EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
-                               (void *)header, total_ino,
-                               inode->i_sb->s_blocksize);
-               EXT4_I(inode)->i_extra_isize = new_extra_isize;
-               goto out;
-       }
+       ifree = ext4_xattr_free_space(base, &min_offs, base, &total_ino);
+       if (ifree >= isize_diff)
+               goto shift;
 
        /*
         * Enough free space isn't available in the inode, check if
@@ -1413,146 +1537,44 @@ retry:
                        goto cleanup;
                }
                base = BHDR(bh);
-               first = BFIRST(bh);
                end = bh->b_data + bh->b_size;
                min_offs = end - base;
-               free = ext4_xattr_free_space(first, &min_offs, base, NULL);
-               if (free < isize_diff) {
+               bfree = ext4_xattr_free_space(BFIRST(bh), &min_offs, base,
+                                             NULL);
+               if (bfree + ifree < isize_diff) {
                        if (!tried_min_extra_isize && s_min_extra_isize) {
                                tried_min_extra_isize++;
                                new_extra_isize = s_min_extra_isize;
                                brelse(bh);
                                goto retry;
                        }
-                       error = -1;
+                       error = -ENOSPC;
                        goto cleanup;
                }
        } else {
-               free = inode->i_sb->s_blocksize;
+               bfree = inode->i_sb->s_blocksize;
        }
 
-       while (isize_diff > 0) {
-               size_t offs, size, entry_size;
-               struct ext4_xattr_entry *small_entry = NULL;
-               struct ext4_xattr_info i = {
-                       .value = NULL,
-                       .value_len = 0,
-               };
-               unsigned int total_size;  /* EA entry size + value size */
-               unsigned int shift_bytes; /* No. of bytes to shift EAs by? */
-               unsigned int min_total_size = ~0U;
-
-               is = kzalloc(sizeof(struct ext4_xattr_ibody_find), GFP_NOFS);
-               bs = kzalloc(sizeof(struct ext4_xattr_block_find), GFP_NOFS);
-               if (!is || !bs) {
-                       error = -ENOMEM;
-                       goto cleanup;
-               }
-
-               is->s.not_found = -ENODATA;
-               bs->s.not_found = -ENODATA;
-               is->iloc.bh = NULL;
-               bs->bh = NULL;
-
-               last = IFIRST(header);
-               /* Find the entry best suited to be pushed into EA block */
-               entry = NULL;
-               for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
-                       total_size =
-                       EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
-                                       EXT4_XATTR_LEN(last->e_name_len);
-                       if (total_size <= free && total_size < min_total_size) {
-                               if (total_size < isize_diff) {
-                                       small_entry = last;
-                               } else {
-                                       entry = last;
-                                       min_total_size = total_size;
-                               }
-                       }
-               }
-
-               if (entry == NULL) {
-                       if (small_entry) {
-                               entry = small_entry;
-                       } else {
-                               if (!tried_min_extra_isize &&
-                                   s_min_extra_isize) {
-                                       tried_min_extra_isize++;
-                                       new_extra_isize = s_min_extra_isize;
-                                       kfree(is); is = NULL;
-                                       kfree(bs); bs = NULL;
-                                       brelse(bh);
-                                       goto retry;
-                               }
-                               error = -1;
-                               goto cleanup;
-                       }
-               }
-               offs = le16_to_cpu(entry->e_value_offs);
-               size = le32_to_cpu(entry->e_value_size);
-               entry_size = EXT4_XATTR_LEN(entry->e_name_len);
-               i.name_index = entry->e_name_index,
-               buffer = kmalloc(EXT4_XATTR_SIZE(size), GFP_NOFS);
-               b_entry_name = kmalloc(entry->e_name_len + 1, GFP_NOFS);
-               if (!buffer || !b_entry_name) {
-                       error = -ENOMEM;
-                       goto cleanup;
+       error = ext4_xattr_make_inode_space(handle, inode, raw_inode,
+                                           isize_diff, ifree, bfree,
+                                           &total_ino);
+       if (error) {
+               if (error == -ENOSPC && !tried_min_extra_isize &&
+                   s_min_extra_isize) {
+                       tried_min_extra_isize++;
+                       new_extra_isize = s_min_extra_isize;
+                       brelse(bh);
+                       goto retry;
                }
-               /* Save the entry name and the entry value */
-               memcpy(buffer, (void *)IFIRST(header) + offs,
-                      EXT4_XATTR_SIZE(size));
-               memcpy(b_entry_name, entry->e_name, entry->e_name_len);
-               b_entry_name[entry->e_name_len] = '\0';
-               i.name = b_entry_name;
-
-               error = ext4_get_inode_loc(inode, &is->iloc);
-               if (error)
-                       goto cleanup;
-
-               error = ext4_xattr_ibody_find(inode, &i, is);
-               if (error)
-                       goto cleanup;
-
-               /* Remove the chosen entry from the inode */
-               error = ext4_xattr_ibody_set(handle, inode, &i, is);
-               if (error)
-                       goto cleanup;
-               total_ino -= entry_size;
-
-               entry = IFIRST(header);
-               if (entry_size + EXT4_XATTR_SIZE(size) >= isize_diff)
-                       shift_bytes = isize_diff;
-               else
-                       shift_bytes = entry_size + EXT4_XATTR_SIZE(size);
-               /* Adjust the offsets and shift the remaining entries ahead */
-               ext4_xattr_shift_entries(entry, -shift_bytes,
-                       (void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
-                       EXT4_I(inode)->i_extra_isize + shift_bytes,
-                       (void *)header, total_ino, inode->i_sb->s_blocksize);
-
-               isize_diff -= shift_bytes;
-               EXT4_I(inode)->i_extra_isize += shift_bytes;
-               header = IHDR(inode, raw_inode);
-
-               i.name = b_entry_name;
-               i.value = buffer;
-               i.value_len = size;
-               error = ext4_xattr_block_find(inode, &i, bs);
-               if (error)
-                       goto cleanup;
-
-               /* Add entry which was removed from the inode into the block */
-               error = ext4_xattr_block_set(handle, inode, &i, bs);
-               if (error)
-                       goto cleanup;
-               kfree(b_entry_name);
-               kfree(buffer);
-               b_entry_name = NULL;
-               buffer = NULL;
-               brelse(is->iloc.bh);
-               kfree(is);
-               kfree(bs);
+               goto cleanup;
        }
+shift:
+       /* Adjust the offsets and shift the remaining entries ahead */
+       ext4_xattr_shift_entries(IFIRST(header), EXT4_I(inode)->i_extra_isize
+                       - new_extra_isize, (void *)raw_inode +
+                       EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
+                       (void *)header, total_ino);
+       EXT4_I(inode)->i_extra_isize = new_extra_isize;
        brelse(bh);
 out:
        ext4_clear_inode_state(inode, EXT4_STATE_NO_EXPAND);
@@ -1560,12 +1582,6 @@ out:
        return 0;
 
 cleanup:
-       kfree(b_entry_name);
-       kfree(buffer);
-       if (is)
-               brelse(is->iloc.bh);
-       kfree(is);
-       kfree(bs);
        brelse(bh);
        /*
         * We deliberately leave EXT4_STATE_NO_EXPAND set here since inode
@@ -1734,7 +1750,7 @@ static inline void ext4_xattr_hash_entry(struct ext4_xattr_header *header,
                       *name++;
        }
 
-       if (entry->e_value_block == 0 && entry->e_value_size != 0) {
+       if (entry->e_value_size != 0) {
                __le32 *value = (__le32 *)((char *)header +
                        le16_to_cpu(entry->e_value_offs));
                for (n = (le32_to_cpu(entry->e_value_size) +