Add stable@vger.kernel.org annotations
[ext4-patch-queue.git] / fixup-free-space-calculations-when-expanding-inodes
blob28bfcb1d180a066c06f1fdcba6f2b0ede4b22e15
1 ext4: fixup free space calculations when expanding inodes
3 From: Jan Kara <jack@suse.cz>
5 Conditions checking whether there is enough free space in an xattr block
6 and when xattr is large enough to make enough space in the inode forgot
7 to account for the fact that inode need not be completely filled up with
8 xattrs. Thus we could move unnecessarily many xattrs out of inode or
9 even falsely claim there is not enough space to expand the inode. We
10 also forgot to update the amount of free space in xattr block when moving
11 more xattrs and thus could decide to move too big xattr resulting in
12 unexpected failure.
14 Fix these problems by properly updating free space in the inode and
15 xattr block as we move xattrs. To simplify the math, avoid shifting
16 xattrs after removing each one xattr and instead just shift xattrs only
17 once there is enough free space in the inode.
19 Signed-off-by: Jan Kara <jack@suse.cz>
20 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
21 ---
22  fs/ext4/xattr.c | 58 ++++++++++++++++++++++++---------------------------------
23  1 file changed, 24 insertions(+), 34 deletions(-)
25 diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
26 index 2eb935ca5d9e..22d2ebcd1f09 100644
27 --- a/fs/ext4/xattr.c
28 +++ b/fs/ext4/xattr.c
29 @@ -1350,7 +1350,8 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
30         struct ext4_xattr_ibody_find *is = NULL;
31         struct ext4_xattr_block_find *bs = NULL;
32         char *buffer = NULL, *b_entry_name = NULL;
33 -       size_t min_offs, free;
34 +       size_t min_offs;
35 +       size_t ifree, bfree;
36         int total_ino;
37         void *base, *start, *end;
38         int error = 0, tried_min_extra_isize = 0;
39 @@ -1385,17 +1386,9 @@ retry:
40         if (error)
41                 goto cleanup;
43 -       free = ext4_xattr_free_space(last, &min_offs, base, &total_ino);
44 -       if (free >= isize_diff) {
45 -               entry = IFIRST(header);
46 -               ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize
47 -                               - new_extra_isize, (void *)raw_inode +
48 -                               EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
49 -                               (void *)header, total_ino,
50 -                               inode->i_sb->s_blocksize);
51 -               EXT4_I(inode)->i_extra_isize = new_extra_isize;
52 -               goto out;
53 -       }
54 +       ifree = ext4_xattr_free_space(last, &min_offs, base, &total_ino);
55 +       if (ifree >= isize_diff)
56 +               goto shift;
58         /*
59          * Enough free space isn't available in the inode, check if
60 @@ -1416,8 +1409,8 @@ retry:
61                 first = BFIRST(bh);
62                 end = bh->b_data + bh->b_size;
63                 min_offs = end - base;
64 -               free = ext4_xattr_free_space(first, &min_offs, base, NULL);
65 -               if (free < isize_diff) {
66 +               bfree = ext4_xattr_free_space(first, &min_offs, base, NULL);
67 +               if (bfree + ifree < isize_diff) {
68                         if (!tried_min_extra_isize && s_min_extra_isize) {
69                                 tried_min_extra_isize++;
70                                 new_extra_isize = s_min_extra_isize;
71 @@ -1428,10 +1421,10 @@ retry:
72                         goto cleanup;
73                 }
74         } else {
75 -               free = inode->i_sb->s_blocksize;
76 +               bfree = inode->i_sb->s_blocksize;
77         }
79 -       while (isize_diff > 0) {
80 +       while (isize_diff > ifree) {
81                 size_t offs, size, entry_size;
82                 struct ext4_xattr_entry *small_entry = NULL;
83                 struct ext4_xattr_info i = {
84 @@ -1439,7 +1432,6 @@ retry:
85                         .value_len = 0,
86                 };
87                 unsigned int total_size;  /* EA entry size + value size */
88 -               unsigned int shift_bytes; /* No. of bytes to shift EAs by? */
89                 unsigned int min_total_size = ~0U;
91                 is = kzalloc(sizeof(struct ext4_xattr_ibody_find), GFP_NOFS);
92 @@ -1461,8 +1453,9 @@ retry:
93                         total_size =
94                         EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
95                                         EXT4_XATTR_LEN(last->e_name_len);
96 -                       if (total_size <= free && total_size < min_total_size) {
97 -                               if (total_size < isize_diff) {
98 +                       if (total_size <= bfree &&
99 +                           total_size < min_total_size) {
100 +                               if (total_size + ifree < isize_diff) {
101                                         small_entry = last;
102                                 } else {
103                                         entry = last;
104 @@ -1491,6 +1484,7 @@ retry:
105                 offs = le16_to_cpu(entry->e_value_offs);
106                 size = le32_to_cpu(entry->e_value_size);
107                 entry_size = EXT4_XATTR_LEN(entry->e_name_len);
108 +               total_size = entry_size + EXT4_XATTR_SIZE(size);
109                 i.name_index = entry->e_name_index,
110                 buffer = kmalloc(EXT4_XATTR_SIZE(size), GFP_NOFS);
111                 b_entry_name = kmalloc(entry->e_name_len + 1, GFP_NOFS);
112 @@ -1518,21 +1512,8 @@ retry:
113                 if (error)
114                         goto cleanup;
115                 total_ino -= entry_size;
117 -               entry = IFIRST(header);
118 -               if (entry_size + EXT4_XATTR_SIZE(size) >= isize_diff)
119 -                       shift_bytes = isize_diff;
120 -               else
121 -                       shift_bytes = entry_size + EXT4_XATTR_SIZE(size);
122 -               /* Adjust the offsets and shift the remaining entries ahead */
123 -               ext4_xattr_shift_entries(entry, -shift_bytes,
124 -                       (void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
125 -                       EXT4_I(inode)->i_extra_isize + shift_bytes,
126 -                       (void *)header, total_ino, inode->i_sb->s_blocksize);
128 -               isize_diff -= shift_bytes;
129 -               EXT4_I(inode)->i_extra_isize += shift_bytes;
130 -               header = IHDR(inode, raw_inode);
131 +               ifree += total_size;
132 +               bfree -= total_size;
134                 i.name = b_entry_name;
135                 i.value = buffer;
136 @@ -1553,6 +1534,15 @@ retry:
137                 kfree(is);
138                 kfree(bs);
139         }
141 +shift:
142 +       /* Adjust the offsets and shift the remaining entries ahead */
143 +       entry = IFIRST(header);
144 +       ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize
145 +                       - new_extra_isize, (void *)raw_inode +
146 +                       EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
147 +                       (void *)header, total_ino, inode->i_sb->s_blocksize);
148 +       EXT4_I(inode)->i_extra_isize = new_extra_isize;
149         brelse(bh);
150  out:
151         ext4_clear_inode_state(inode, EXT4_STATE_NO_EXPAND);
152 -- 
153 2.6.6
156 To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
157 the body of a message to majordomo@vger.kernel.org
158 More majordomo info at  http://vger.kernel.org/majordomo-info.html