Ext4 bug fixes (including a regression fix) for 5.5
-----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEK2m5VNv+CHkogTfJ8vlZVpUNgaMFAl3/fDEACgkQ8vlZVpUN gaMZ6Qf/f973waBpA1E9GgAvB4AymRvGbqPJhW2lDDhEl36oXVpUw6EgIKWgNQPS HP6NhYXZakrpEak6Uk2MtiTmcm+6lqDJ+bCslCMylNh9/Y1yUrED2r8l7S3nGv4g hVB7Eah7E+sutDyrDQhYhcQo3GJjt8CbwRLgo8fbhSVrZ7qdfb0lWQmVnruc+72b 3VAeMzPJb0wRY6myxLN4Pw6oEMR1WKVsXm3I9gNXboE2XvgVvnNn2tJxP+xml8rW uGxzWTo7QQNN2bUyjZBa6Mm44lMpHr7JT0nMwkIGV5v3eAYuBgeSwIXUskfw29q7 sP9xNP2voU3M6TyWuT0+cHpoeZasPg== =K63f -----END PGP SIGNATURE----- Merge tag 'ext4_for_linus_stable' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4 Pull ext4 bug fixes from Ted Ts'o: "Ext4 bug fixes, including a regression fix" * tag 'ext4_for_linus_stable' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4: ext4: clarify impact of 'commit' mount option ext4: fix unused-but-set-variable warning in ext4_add_entry() jbd2: fix kernel-doc notation warning ext4: use RCU API in debug_print_tree ext4: validate the debug_want_extra_isize mount option at parse time ext4: reserve revoke credits in __ext4_new_inode ext4: unlock on error in ext4_expand_extra_isize() ext4: optimize __ext4_check_dir_entry() ext4: check for directory entries too close to block end ext4: fix ext4_empty_dir() for directories with holes
This commit is contained in:
commit
a396560706
|
@ -181,14 +181,17 @@ When mounting an ext4 filesystem, the following option are accepted:
|
|||
system after its metadata has been committed to the journal.
|
||||
|
||||
commit=nrsec (*)
|
||||
Ext4 can be told to sync all its data and metadata every 'nrsec'
|
||||
seconds. The default value is 5 seconds. This means that if you lose
|
||||
your power, you will lose as much as the latest 5 seconds of work (your
|
||||
filesystem will not be damaged though, thanks to the journaling). This
|
||||
default value (or any low value) will hurt performance, but it's good
|
||||
for data-safety. Setting it to 0 will have the same effect as leaving
|
||||
it at the default (5 seconds). Setting it to very large values will
|
||||
improve performance.
|
||||
This setting limits the maximum age of the running transaction to
|
||||
'nrsec' seconds. The default value is 5 seconds. This means that if
|
||||
you lose your power, you will lose as much as the latest 5 seconds of
|
||||
metadata changes (your filesystem will not be damaged though, thanks
|
||||
to the journaling). This default value (or any low value) will hurt
|
||||
performance, but it's good for data-safety. Setting it to 0 will have
|
||||
the same effect as leaving it at the default (5 seconds). Setting it
|
||||
to very large values will improve performance. Note that due to
|
||||
delayed allocation even older data can be lost on power failure since
|
||||
writeback of those data begins only after time set in
|
||||
/proc/sys/vm/dirty_expire_centisecs.
|
||||
|
||||
barrier=<0|1(*)>, barrier(*), nobarrier
|
||||
This enables/disables the use of write barriers in the jbd code.
|
||||
|
|
|
@ -133,10 +133,13 @@ static void debug_print_tree(struct ext4_sb_info *sbi)
|
|||
{
|
||||
struct rb_node *node;
|
||||
struct ext4_system_zone *entry;
|
||||
struct ext4_system_blocks *system_blks;
|
||||
int first = 1;
|
||||
|
||||
printk(KERN_INFO "System zones: ");
|
||||
node = rb_first(&sbi->system_blks->root);
|
||||
rcu_read_lock();
|
||||
system_blks = rcu_dereference(sbi->system_blks);
|
||||
node = rb_first(&system_blks->root);
|
||||
while (node) {
|
||||
entry = rb_entry(node, struct ext4_system_zone, node);
|
||||
printk(KERN_CONT "%s%llu-%llu", first ? "" : ", ",
|
||||
|
@ -144,6 +147,7 @@ static void debug_print_tree(struct ext4_sb_info *sbi)
|
|||
first = 0;
|
||||
node = rb_next(node);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
printk(KERN_CONT "\n");
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,
|
|||
const char *error_msg = NULL;
|
||||
const int rlen = ext4_rec_len_from_disk(de->rec_len,
|
||||
dir->i_sb->s_blocksize);
|
||||
const int next_offset = ((char *) de - buf) + rlen;
|
||||
|
||||
if (unlikely(rlen < EXT4_DIR_REC_LEN(1)))
|
||||
error_msg = "rec_len is smaller than minimal";
|
||||
|
@ -79,8 +80,11 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,
|
|||
error_msg = "rec_len % 4 != 0";
|
||||
else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len)))
|
||||
error_msg = "rec_len is too small for name_len";
|
||||
else if (unlikely(((char *) de - buf) + rlen > size))
|
||||
else if (unlikely(next_offset > size))
|
||||
error_msg = "directory entry overrun";
|
||||
else if (unlikely(next_offset > size - EXT4_DIR_REC_LEN(1) &&
|
||||
next_offset != size))
|
||||
error_msg = "directory entry too close to block end";
|
||||
else if (unlikely(le32_to_cpu(de->inode) >
|
||||
le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count)))
|
||||
error_msg = "inode out of bounds";
|
||||
|
|
|
@ -921,8 +921,8 @@ repeat_in_this_group:
|
|||
if (!handle) {
|
||||
BUG_ON(nblocks <= 0);
|
||||
handle = __ext4_journal_start_sb(dir->i_sb, line_no,
|
||||
handle_type, nblocks,
|
||||
0, 0);
|
||||
handle_type, nblocks, 0,
|
||||
ext4_trans_default_revoke_credits(sb));
|
||||
if (IS_ERR(handle)) {
|
||||
err = PTR_ERR(handle);
|
||||
ext4_std_error(sb, err);
|
||||
|
|
|
@ -5692,7 +5692,7 @@ int ext4_expand_extra_isize(struct inode *inode,
|
|||
error = ext4_journal_get_write_access(handle, iloc->bh);
|
||||
if (error) {
|
||||
brelse(iloc->bh);
|
||||
goto out_stop;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
error = __ext4_expand_extra_isize(inode, new_extra_isize, iloc,
|
||||
|
@ -5702,8 +5702,8 @@ int ext4_expand_extra_isize(struct inode *inode,
|
|||
if (!error)
|
||||
error = rc;
|
||||
|
||||
out_unlock:
|
||||
ext4_write_unlock_xattr(inode, &no_expand);
|
||||
out_stop:
|
||||
ext4_journal_stop(handle);
|
||||
return error;
|
||||
}
|
||||
|
|
|
@ -2164,7 +2164,9 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
|
|||
struct buffer_head *bh = NULL;
|
||||
struct ext4_dir_entry_2 *de;
|
||||
struct super_block *sb;
|
||||
#ifdef CONFIG_UNICODE
|
||||
struct ext4_sb_info *sbi;
|
||||
#endif
|
||||
struct ext4_filename fname;
|
||||
int retval;
|
||||
int dx_fallback=0;
|
||||
|
@ -2176,12 +2178,12 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
|
|||
csum_size = sizeof(struct ext4_dir_entry_tail);
|
||||
|
||||
sb = dir->i_sb;
|
||||
sbi = EXT4_SB(sb);
|
||||
blocksize = sb->s_blocksize;
|
||||
if (!dentry->d_name.len)
|
||||
return -EINVAL;
|
||||
|
||||
#ifdef CONFIG_UNICODE
|
||||
sbi = EXT4_SB(sb);
|
||||
if (ext4_has_strict_mode(sbi) && IS_CASEFOLDED(dir) &&
|
||||
sbi->s_encoding && utf8_validate(sbi->s_encoding, &dentry->d_name))
|
||||
return -EINVAL;
|
||||
|
@ -2822,7 +2824,7 @@ bool ext4_empty_dir(struct inode *inode)
|
|||
{
|
||||
unsigned int offset;
|
||||
struct buffer_head *bh;
|
||||
struct ext4_dir_entry_2 *de, *de1;
|
||||
struct ext4_dir_entry_2 *de;
|
||||
struct super_block *sb;
|
||||
|
||||
if (ext4_has_inline_data(inode)) {
|
||||
|
@ -2847,19 +2849,25 @@ bool ext4_empty_dir(struct inode *inode)
|
|||
return true;
|
||||
|
||||
de = (struct ext4_dir_entry_2 *) bh->b_data;
|
||||
de1 = ext4_next_entry(de, sb->s_blocksize);
|
||||
if (le32_to_cpu(de->inode) != inode->i_ino ||
|
||||
le32_to_cpu(de1->inode) == 0 ||
|
||||
strcmp(".", de->name) || strcmp("..", de1->name)) {
|
||||
ext4_warning_inode(inode, "directory missing '.' and/or '..'");
|
||||
if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size,
|
||||
0) ||
|
||||
le32_to_cpu(de->inode) != inode->i_ino || strcmp(".", de->name)) {
|
||||
ext4_warning_inode(inode, "directory missing '.'");
|
||||
brelse(bh);
|
||||
return true;
|
||||
}
|
||||
offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize) +
|
||||
ext4_rec_len_from_disk(de1->rec_len, sb->s_blocksize);
|
||||
de = ext4_next_entry(de1, sb->s_blocksize);
|
||||
offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
|
||||
de = ext4_next_entry(de, sb->s_blocksize);
|
||||
if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size,
|
||||
offset) ||
|
||||
le32_to_cpu(de->inode) == 0 || strcmp("..", de->name)) {
|
||||
ext4_warning_inode(inode, "directory missing '..'");
|
||||
brelse(bh);
|
||||
return true;
|
||||
}
|
||||
offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
|
||||
while (offset < inode->i_size) {
|
||||
if ((void *) de >= (void *) (bh->b_data+sb->s_blocksize)) {
|
||||
if (!(offset & (sb->s_blocksize - 1))) {
|
||||
unsigned int lblock;
|
||||
brelse(bh);
|
||||
lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb);
|
||||
|
@ -2870,12 +2878,11 @@ bool ext4_empty_dir(struct inode *inode)
|
|||
}
|
||||
if (IS_ERR(bh))
|
||||
return true;
|
||||
de = (struct ext4_dir_entry_2 *) bh->b_data;
|
||||
}
|
||||
de = (struct ext4_dir_entry_2 *) (bh->b_data +
|
||||
(offset & (sb->s_blocksize - 1)));
|
||||
if (ext4_check_dir_entry(inode, NULL, de, bh,
|
||||
bh->b_data, bh->b_size, offset)) {
|
||||
de = (struct ext4_dir_entry_2 *)(bh->b_data +
|
||||
sb->s_blocksize);
|
||||
offset = (offset | (sb->s_blocksize - 1)) + 1;
|
||||
continue;
|
||||
}
|
||||
|
@ -2884,7 +2891,6 @@ bool ext4_empty_dir(struct inode *inode)
|
|||
return false;
|
||||
}
|
||||
offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
|
||||
de = ext4_next_entry(de, sb->s_blocksize);
|
||||
}
|
||||
brelse(bh);
|
||||
return true;
|
||||
|
|
143
fs/ext4/super.c
143
fs/ext4/super.c
|
@ -1900,6 +1900,13 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token,
|
|||
}
|
||||
sbi->s_commit_interval = HZ * arg;
|
||||
} else if (token == Opt_debug_want_extra_isize) {
|
||||
if ((arg & 1) ||
|
||||
(arg < 4) ||
|
||||
(arg > (sbi->s_inode_size - EXT4_GOOD_OLD_INODE_SIZE))) {
|
||||
ext4_msg(sb, KERN_ERR,
|
||||
"Invalid want_extra_isize %d", arg);
|
||||
return -1;
|
||||
}
|
||||
sbi->s_want_extra_isize = arg;
|
||||
} else if (token == Opt_max_batch_time) {
|
||||
sbi->s_max_batch_time = arg;
|
||||
|
@ -3554,40 +3561,6 @@ int ext4_calculate_overhead(struct super_block *sb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void ext4_clamp_want_extra_isize(struct super_block *sb)
|
||||
{
|
||||
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
||||
struct ext4_super_block *es = sbi->s_es;
|
||||
unsigned def_extra_isize = sizeof(struct ext4_inode) -
|
||||
EXT4_GOOD_OLD_INODE_SIZE;
|
||||
|
||||
if (sbi->s_inode_size == EXT4_GOOD_OLD_INODE_SIZE) {
|
||||
sbi->s_want_extra_isize = 0;
|
||||
return;
|
||||
}
|
||||
if (sbi->s_want_extra_isize < 4) {
|
||||
sbi->s_want_extra_isize = def_extra_isize;
|
||||
if (ext4_has_feature_extra_isize(sb)) {
|
||||
if (sbi->s_want_extra_isize <
|
||||
le16_to_cpu(es->s_want_extra_isize))
|
||||
sbi->s_want_extra_isize =
|
||||
le16_to_cpu(es->s_want_extra_isize);
|
||||
if (sbi->s_want_extra_isize <
|
||||
le16_to_cpu(es->s_min_extra_isize))
|
||||
sbi->s_want_extra_isize =
|
||||
le16_to_cpu(es->s_min_extra_isize);
|
||||
}
|
||||
}
|
||||
/* Check if enough inode space is available */
|
||||
if ((sbi->s_want_extra_isize > sbi->s_inode_size) ||
|
||||
(EXT4_GOOD_OLD_INODE_SIZE + sbi->s_want_extra_isize >
|
||||
sbi->s_inode_size)) {
|
||||
sbi->s_want_extra_isize = def_extra_isize;
|
||||
ext4_msg(sb, KERN_INFO,
|
||||
"required extra inode space not available");
|
||||
}
|
||||
}
|
||||
|
||||
static void ext4_set_resv_clusters(struct super_block *sb)
|
||||
{
|
||||
ext4_fsblk_t resv_clusters;
|
||||
|
@ -3795,6 +3768,68 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
|
|||
*/
|
||||
sbi->s_li_wait_mult = EXT4_DEF_LI_WAIT_MULT;
|
||||
|
||||
if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV) {
|
||||
sbi->s_inode_size = EXT4_GOOD_OLD_INODE_SIZE;
|
||||
sbi->s_first_ino = EXT4_GOOD_OLD_FIRST_INO;
|
||||
} else {
|
||||
sbi->s_inode_size = le16_to_cpu(es->s_inode_size);
|
||||
sbi->s_first_ino = le32_to_cpu(es->s_first_ino);
|
||||
if (sbi->s_first_ino < EXT4_GOOD_OLD_FIRST_INO) {
|
||||
ext4_msg(sb, KERN_ERR, "invalid first ino: %u",
|
||||
sbi->s_first_ino);
|
||||
goto failed_mount;
|
||||
}
|
||||
if ((sbi->s_inode_size < EXT4_GOOD_OLD_INODE_SIZE) ||
|
||||
(!is_power_of_2(sbi->s_inode_size)) ||
|
||||
(sbi->s_inode_size > blocksize)) {
|
||||
ext4_msg(sb, KERN_ERR,
|
||||
"unsupported inode size: %d",
|
||||
sbi->s_inode_size);
|
||||
goto failed_mount;
|
||||
}
|
||||
/*
|
||||
* i_atime_extra is the last extra field available for
|
||||
* [acm]times in struct ext4_inode. Checking for that
|
||||
* field should suffice to ensure we have extra space
|
||||
* for all three.
|
||||
*/
|
||||
if (sbi->s_inode_size >= offsetof(struct ext4_inode, i_atime_extra) +
|
||||
sizeof(((struct ext4_inode *)0)->i_atime_extra)) {
|
||||
sb->s_time_gran = 1;
|
||||
sb->s_time_max = EXT4_EXTRA_TIMESTAMP_MAX;
|
||||
} else {
|
||||
sb->s_time_gran = NSEC_PER_SEC;
|
||||
sb->s_time_max = EXT4_NON_EXTRA_TIMESTAMP_MAX;
|
||||
}
|
||||
sb->s_time_min = EXT4_TIMESTAMP_MIN;
|
||||
}
|
||||
if (sbi->s_inode_size > EXT4_GOOD_OLD_INODE_SIZE) {
|
||||
sbi->s_want_extra_isize = sizeof(struct ext4_inode) -
|
||||
EXT4_GOOD_OLD_INODE_SIZE;
|
||||
if (ext4_has_feature_extra_isize(sb)) {
|
||||
unsigned v, max = (sbi->s_inode_size -
|
||||
EXT4_GOOD_OLD_INODE_SIZE);
|
||||
|
||||
v = le16_to_cpu(es->s_want_extra_isize);
|
||||
if (v > max) {
|
||||
ext4_msg(sb, KERN_ERR,
|
||||
"bad s_want_extra_isize: %d", v);
|
||||
goto failed_mount;
|
||||
}
|
||||
if (sbi->s_want_extra_isize < v)
|
||||
sbi->s_want_extra_isize = v;
|
||||
|
||||
v = le16_to_cpu(es->s_min_extra_isize);
|
||||
if (v > max) {
|
||||
ext4_msg(sb, KERN_ERR,
|
||||
"bad s_min_extra_isize: %d", v);
|
||||
goto failed_mount;
|
||||
}
|
||||
if (sbi->s_want_extra_isize < v)
|
||||
sbi->s_want_extra_isize = v;
|
||||
}
|
||||
}
|
||||
|
||||
if (sbi->s_es->s_mount_opts[0]) {
|
||||
char *s_mount_opts = kstrndup(sbi->s_es->s_mount_opts,
|
||||
sizeof(sbi->s_es->s_mount_opts),
|
||||
|
@ -4033,42 +4068,6 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
|
|||
has_huge_files);
|
||||
sb->s_maxbytes = ext4_max_size(sb->s_blocksize_bits, has_huge_files);
|
||||
|
||||
if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV) {
|
||||
sbi->s_inode_size = EXT4_GOOD_OLD_INODE_SIZE;
|
||||
sbi->s_first_ino = EXT4_GOOD_OLD_FIRST_INO;
|
||||
} else {
|
||||
sbi->s_inode_size = le16_to_cpu(es->s_inode_size);
|
||||
sbi->s_first_ino = le32_to_cpu(es->s_first_ino);
|
||||
if (sbi->s_first_ino < EXT4_GOOD_OLD_FIRST_INO) {
|
||||
ext4_msg(sb, KERN_ERR, "invalid first ino: %u",
|
||||
sbi->s_first_ino);
|
||||
goto failed_mount;
|
||||
}
|
||||
if ((sbi->s_inode_size < EXT4_GOOD_OLD_INODE_SIZE) ||
|
||||
(!is_power_of_2(sbi->s_inode_size)) ||
|
||||
(sbi->s_inode_size > blocksize)) {
|
||||
ext4_msg(sb, KERN_ERR,
|
||||
"unsupported inode size: %d",
|
||||
sbi->s_inode_size);
|
||||
goto failed_mount;
|
||||
}
|
||||
/*
|
||||
* i_atime_extra is the last extra field available for [acm]times in
|
||||
* struct ext4_inode. Checking for that field should suffice to ensure
|
||||
* we have extra space for all three.
|
||||
*/
|
||||
if (sbi->s_inode_size >= offsetof(struct ext4_inode, i_atime_extra) +
|
||||
sizeof(((struct ext4_inode *)0)->i_atime_extra)) {
|
||||
sb->s_time_gran = 1;
|
||||
sb->s_time_max = EXT4_EXTRA_TIMESTAMP_MAX;
|
||||
} else {
|
||||
sb->s_time_gran = NSEC_PER_SEC;
|
||||
sb->s_time_max = EXT4_NON_EXTRA_TIMESTAMP_MAX;
|
||||
}
|
||||
|
||||
sb->s_time_min = EXT4_TIMESTAMP_MIN;
|
||||
}
|
||||
|
||||
sbi->s_desc_size = le16_to_cpu(es->s_desc_size);
|
||||
if (ext4_has_feature_64bit(sb)) {
|
||||
if (sbi->s_desc_size < EXT4_MIN_DESC_SIZE_64BIT ||
|
||||
|
@ -4517,8 +4516,6 @@ no_journal:
|
|||
} else if (ret)
|
||||
goto failed_mount4a;
|
||||
|
||||
ext4_clamp_want_extra_isize(sb);
|
||||
|
||||
ext4_set_resv_clusters(sb);
|
||||
|
||||
err = ext4_setup_system_zone(sb);
|
||||
|
@ -5306,8 +5303,6 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data)
|
|||
goto restore_opts;
|
||||
}
|
||||
|
||||
ext4_clamp_want_extra_isize(sb);
|
||||
|
||||
if ((old_opts.s_mount_opt & EXT4_MOUNT_JOURNAL_CHECKSUM) ^
|
||||
test_opt(sb, JOURNAL_CHECKSUM)) {
|
||||
ext4_msg(sb, KERN_ERR, "changing journal_checksum "
|
||||
|
|
|
@ -457,7 +457,7 @@ struct jbd2_revoke_table_s;
|
|||
* @h_journal: Which journal handle belongs to - used iff h_reserved set.
|
||||
* @h_rsv_handle: Handle reserved for finishing the logical operation.
|
||||
* @h_total_credits: Number of remaining buffers we are allowed to add to
|
||||
journal. These are dirty buffers and revoke descriptor blocks.
|
||||
* journal. These are dirty buffers and revoke descriptor blocks.
|
||||
* @h_revoke_credits: Number of remaining revoke records available for handle
|
||||
* @h_ref: Reference count on this handle.
|
||||
* @h_err: Field for caller's use to track errors through large fs operations.
|
||||
|
|
Loading…
Reference in New Issue