Skip to content

Commit ef2b02d

Browse files
Eric SandeenLinus Torvalds
authored andcommitted
ext34: ensure do_split leaves enough free space in both blocks
The do_split() function for htree dir blocks is intended to split a leaf block to make room for a new entry. It sorts the entries in the original block by hash value, then moves the last half of the entries to the new block - without accounting for how much space this actually moves. (IOW, it moves half of the entry *count* not half of the entry *space*). If by chance we have both large & small entries, and we move only the smallest entries, and we have a large new entry to insert, we may not have created enough space for it. The patch below stores each record size when calculating the dx_map, and then walks the hash-sorted dx_map, calculating how many entries must be moved to more evenly split the existing entries between the old block and the new block, guaranteeing enough space for the new entry. The dx_map "offs" member is reduced to u16 so that the overall map size does not change - it is temporarily stored at the end of the new block, and if it grows too large it may be overwritten. By making offs and size both u16, we won't grow the map size. Also add a few comments to the functions involved. This fixes the testcase reported by [email protected] on the linux-ext4 list, "ext3 dir_index causes an error" Thanks to Andreas Dilger for discussing the problem & solution with me. Signed-off-by: Eric Sandeen <[email protected]> Signed-off-by: Andreas Dilger <[email protected]> Tested-by: Junjiro Okajima <[email protected]> Cc: Theodore Ts'o <[email protected]> Cc: <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent e426019 commit ef2b02d

File tree

2 files changed

+70
-8
lines changed

2 files changed

+70
-8
lines changed

fs/ext3/namei.c

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ struct dx_frame
140140
struct dx_map_entry
141141
{
142142
u32 hash;
143-
u32 offs;
143+
u16 offs;
144+
u16 size;
144145
};
145146

146147
#ifdef CONFIG_EXT3_INDEX
@@ -697,6 +698,10 @@ int ext3_htree_fill_tree(struct file *dir_file, __u32 start_hash,
697698
* Directory block splitting, compacting
698699
*/
699700

701+
/*
702+
* Create map of hash values, offsets, and sizes, stored at end of block.
703+
* Returns number of entries mapped.
704+
*/
700705
static int dx_make_map (struct ext3_dir_entry_2 *de, int size,
701706
struct dx_hash_info *hinfo, struct dx_map_entry *map_tail)
702707
{
@@ -710,7 +715,8 @@ static int dx_make_map (struct ext3_dir_entry_2 *de, int size,
710715
ext3fs_dirhash(de->name, de->name_len, &h);
711716
map_tail--;
712717
map_tail->hash = h.hash;
713-
map_tail->offs = (u32) ((char *) de - base);
718+
map_tail->offs = (u16) ((char *) de - base);
719+
map_tail->size = le16_to_cpu(de->rec_len);
714720
count++;
715721
cond_resched();
716722
}
@@ -720,6 +726,7 @@ static int dx_make_map (struct ext3_dir_entry_2 *de, int size,
720726
return count;
721727
}
722728

729+
/* Sort map by hash value */
723730
static void dx_sort_map (struct dx_map_entry *map, unsigned count)
724731
{
725732
struct dx_map_entry *p, *q, *top = map + count - 1;
@@ -1117,6 +1124,10 @@ static inline void ext3_set_de_type(struct super_block *sb,
11171124
}
11181125

11191126
#ifdef CONFIG_EXT3_INDEX
1127+
/*
1128+
* Move count entries from end of map between two memory locations.
1129+
* Returns pointer to last entry moved.
1130+
*/
11201131
static struct ext3_dir_entry_2 *
11211132
dx_move_dirents(char *from, char *to, struct dx_map_entry *map, int count)
11221133
{
@@ -1135,6 +1146,10 @@ dx_move_dirents(char *from, char *to, struct dx_map_entry *map, int count)
11351146
return (struct ext3_dir_entry_2 *) (to - rec_len);
11361147
}
11371148

1149+
/*
1150+
* Compact each dir entry in the range to the minimal rec_len.
1151+
* Returns pointer to last entry in range.
1152+
*/
11381153
static struct ext3_dir_entry_2* dx_pack_dirents(char *base, int size)
11391154
{
11401155
struct ext3_dir_entry_2 *next, *to, *prev, *de = (struct ext3_dir_entry_2 *) base;
@@ -1157,6 +1172,11 @@ static struct ext3_dir_entry_2* dx_pack_dirents(char *base, int size)
11571172
return prev;
11581173
}
11591174

1175+
/*
1176+
* Split a full leaf block to make room for a new dir entry.
1177+
* Allocate a new block, and move entries so that they are approx. equally full.
1178+
* Returns pointer to de in block into which the new entry will be inserted.
1179+
*/
11601180
static struct ext3_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
11611181
struct buffer_head **bh,struct dx_frame *frame,
11621182
struct dx_hash_info *hinfo, int *error)
@@ -1168,7 +1188,7 @@ static struct ext3_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
11681188
u32 hash2;
11691189
struct dx_map_entry *map;
11701190
char *data1 = (*bh)->b_data, *data2;
1171-
unsigned split;
1191+
unsigned split, move, size, i;
11721192
struct ext3_dir_entry_2 *de = NULL, *de2;
11731193
int err = 0;
11741194

@@ -1196,8 +1216,19 @@ static struct ext3_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
11961216
count = dx_make_map ((struct ext3_dir_entry_2 *) data1,
11971217
blocksize, hinfo, map);
11981218
map -= count;
1199-
split = count/2; // need to adjust to actual middle
12001219
dx_sort_map (map, count);
1220+
/* Split the existing block in the middle, size-wise */
1221+
size = 0;
1222+
move = 0;
1223+
for (i = count-1; i >= 0; i--) {
1224+
/* is more than half of this entry in 2nd half of the block? */
1225+
if (size + map[i].size/2 > blocksize/2)
1226+
break;
1227+
size += map[i].size;
1228+
move++;
1229+
}
1230+
/* map index at which we will split */
1231+
split = count - move;
12011232
hash2 = map[split].hash;
12021233
continued = hash2 == map[split - 1].hash;
12031234
dxtrace(printk("Split block %i at %x, %i/%i\n",

fs/ext4/namei.c

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ struct dx_frame
140140
struct dx_map_entry
141141
{
142142
u32 hash;
143-
u32 offs;
143+
u16 offs;
144+
u16 size;
144145
};
145146

146147
#ifdef CONFIG_EXT4_INDEX
@@ -697,6 +698,10 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
697698
* Directory block splitting, compacting
698699
*/
699700

701+
/*
702+
* Create map of hash values, offsets, and sizes, stored at end of block.
703+
* Returns number of entries mapped.
704+
*/
700705
static int dx_make_map (struct ext4_dir_entry_2 *de, int size,
701706
struct dx_hash_info *hinfo, struct dx_map_entry *map_tail)
702707
{
@@ -710,7 +715,8 @@ static int dx_make_map (struct ext4_dir_entry_2 *de, int size,
710715
ext4fs_dirhash(de->name, de->name_len, &h);
711716
map_tail--;
712717
map_tail->hash = h.hash;
713-
map_tail->offs = (u32) ((char *) de - base);
718+
map_tail->offs = (u16) ((char *) de - base);
719+
map_tail->size = le16_to_cpu(de->rec_len);
714720
count++;
715721
cond_resched();
716722
}
@@ -720,6 +726,7 @@ static int dx_make_map (struct ext4_dir_entry_2 *de, int size,
720726
return count;
721727
}
722728

729+
/* Sort map by hash value */
723730
static void dx_sort_map (struct dx_map_entry *map, unsigned count)
724731
{
725732
struct dx_map_entry *p, *q, *top = map + count - 1;
@@ -1115,6 +1122,10 @@ static inline void ext4_set_de_type(struct super_block *sb,
11151122
}
11161123

11171124
#ifdef CONFIG_EXT4_INDEX
1125+
/*
1126+
* Move count entries from end of map between two memory locations.
1127+
* Returns pointer to last entry moved.
1128+
*/
11181129
static struct ext4_dir_entry_2 *
11191130
dx_move_dirents(char *from, char *to, struct dx_map_entry *map, int count)
11201131
{
@@ -1133,6 +1144,10 @@ dx_move_dirents(char *from, char *to, struct dx_map_entry *map, int count)
11331144
return (struct ext4_dir_entry_2 *) (to - rec_len);
11341145
}
11351146

1147+
/*
1148+
* Compact each dir entry in the range to the minimal rec_len.
1149+
* Returns pointer to last entry in range.
1150+
*/
11361151
static struct ext4_dir_entry_2* dx_pack_dirents(char *base, int size)
11371152
{
11381153
struct ext4_dir_entry_2 *next, *to, *prev, *de = (struct ext4_dir_entry_2 *) base;
@@ -1155,6 +1170,11 @@ static struct ext4_dir_entry_2* dx_pack_dirents(char *base, int size)
11551170
return prev;
11561171
}
11571172

1173+
/*
1174+
* Split a full leaf block to make room for a new dir entry.
1175+
* Allocate a new block, and move entries so that they are approx. equally full.
1176+
* Returns pointer to de in block into which the new entry will be inserted.
1177+
*/
11581178
static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
11591179
struct buffer_head **bh,struct dx_frame *frame,
11601180
struct dx_hash_info *hinfo, int *error)
@@ -1166,7 +1186,7 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
11661186
u32 hash2;
11671187
struct dx_map_entry *map;
11681188
char *data1 = (*bh)->b_data, *data2;
1169-
unsigned split;
1189+
unsigned split, move, size, i;
11701190
struct ext4_dir_entry_2 *de = NULL, *de2;
11711191
int err = 0;
11721192

@@ -1194,8 +1214,19 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
11941214
count = dx_make_map ((struct ext4_dir_entry_2 *) data1,
11951215
blocksize, hinfo, map);
11961216
map -= count;
1197-
split = count/2; // need to adjust to actual middle
11981217
dx_sort_map (map, count);
1218+
/* Split the existing block in the middle, size-wise */
1219+
size = 0;
1220+
move = 0;
1221+
for (i = count-1; i >= 0; i--) {
1222+
/* is more than half of this entry in 2nd half of the block? */
1223+
if (size + map[i].size/2 > blocksize/2)
1224+
break;
1225+
size += map[i].size;
1226+
move++;
1227+
}
1228+
/* map index at which we will split */
1229+
split = count - move;
11991230
hash2 = map[split].hash;
12001231
continued = hash2 == map[split - 1].hash;
12011232
dxtrace(printk("Split block %i at %x, %i/%i\n",

0 commit comments

Comments
 (0)