Commit 57aff997 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'ext4_for_linus-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4

Pull ext4 updates from Ted Ts'o:
 "Cleanup ext4's multi-block allocator, including adding some unit
  tests, as well as cleaning how we update the backup superblock after
  online resizes or updating the label or uuid.

  Optimize handling of released data blocks in ext4's commit machinery
  to avoid a potential lock contention on s_md_lock spinlock.

  Fix a number of ext4 bugs:

   - fix race between writepages and remount

   - fix racy may inline data check in dio write

   - add missed brelse in an error path in update_backups

   - fix umask handling when ACL support is disabled

   - fix lost EIO error when a journal commit races with a fsync of the
     blockdev

   - fix potential improper i_size when there is a crash right after an
     O_SYNC direct write.

   - check extent node for validity before potentially using what might
     be an invalid pointer

   - fix potential stale data exposure when writing to an unwritten
     extent and the file system is nearly out of space

   - fix potential accounting error around block reservations when
     writing partial delayed allocation writes to a bigalloc cluster

   - avoid memory allocation failure when tracking partial delayed
     allocation writes to a bigalloc cluster

   - fix various debugging print messages"

* tag 'ext4_for_linus-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4: (41 commits)
  ext4: properly sync file size update after O_SYNC direct IO
  ext4: fix racy may inline data check in dio write
  ext4: run mballoc test with different layouts setting
  ext4: add first unit test for ext4_mb_new_blocks_simple in mballoc
  ext4: add some kunit stub for mballoc kunit test
  ext4: call ext4_mb_mark_context in ext4_group_add_blocks()
  ext4: Separate block bitmap and buddy bitmap freeing in ext4_group_add_blocks()
  ext4: call ext4_mb_mark_context in ext4_mb_clear_bb
  ext4: Separate block bitmap and buddy bitmap freeing in ext4_mb_clear_bb()
  ext4: call ext4_mb_mark_context in ext4_mb_mark_diskspace_used
  ext4: extend ext4_mb_mark_context to support allocation under journal
  ext4: call ext4_mb_mark_context in ext4_free_blocks_simple
  ext4: factor out codes to update block bitmap and group descriptor on disk from ext4_mb_mark_bb
  ext4: make state in ext4_mb_mark_bb to be bool
  jbd2: fix potential data lost in recovering journal raced with synchronizing fs bdev
  ext4: apply umask if ACL support is disabled
  ext4: mark buffer new if it is unwritten to avoid stale data exposure
  ext4: move 'ix' sanity check to corrent position
  jbd2: fix printk format type for 'io_block' in do_one_pass()
  jbd2: print io_block if check data block checksum failed when do recovery
  ...
parents 91a683cd 91562895
...@@ -68,6 +68,11 @@ extern int ext4_init_acl(handle_t *, struct inode *, struct inode *); ...@@ -68,6 +68,11 @@ extern int ext4_init_acl(handle_t *, struct inode *, struct inode *);
static inline int static inline int
ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir) ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
{ {
/* usually, the umask is applied by posix_acl_create(), but if
ext4 ACL support is disabled at compile time, we need to do
it here, because posix_acl_create() will never be called */
inode->i_mode &= ~current_umask();
return 0; return 0;
} }
#endif /* CONFIG_EXT4_FS_POSIX_ACL */ #endif /* CONFIG_EXT4_FS_POSIX_ACL */
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "mballoc.h" #include "mballoc.h"
#include <trace/events/ext4.h> #include <trace/events/ext4.h>
#include <kunit/static_stub.h>
static unsigned ext4_num_base_meta_clusters(struct super_block *sb, static unsigned ext4_num_base_meta_clusters(struct super_block *sb,
ext4_group_t block_group); ext4_group_t block_group);
...@@ -111,10 +112,8 @@ static unsigned ext4_num_overhead_clusters(struct super_block *sb, ...@@ -111,10 +112,8 @@ static unsigned ext4_num_overhead_clusters(struct super_block *sb,
itbl_blk_start = ext4_inode_table(sb, gdp); itbl_blk_start = ext4_inode_table(sb, gdp);
itbl_blk_end = itbl_blk_start + sbi->s_itb_per_group - 1; itbl_blk_end = itbl_blk_start + sbi->s_itb_per_group - 1;
if (itbl_blk_start <= end && itbl_blk_end >= start) { if (itbl_blk_start <= end && itbl_blk_end >= start) {
itbl_blk_start = itbl_blk_start >= start ? itbl_blk_start = max(itbl_blk_start, start);
itbl_blk_start : start; itbl_blk_end = min(itbl_blk_end, end);
itbl_blk_end = itbl_blk_end <= end ?
itbl_blk_end : end;
itbl_cluster_start = EXT4_B2C(sbi, itbl_blk_start - start); itbl_cluster_start = EXT4_B2C(sbi, itbl_blk_start - start);
itbl_cluster_end = EXT4_B2C(sbi, itbl_blk_end - start); itbl_cluster_end = EXT4_B2C(sbi, itbl_blk_end - start);
...@@ -274,6 +273,9 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb, ...@@ -274,6 +273,9 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb,
struct ext4_sb_info *sbi = EXT4_SB(sb); struct ext4_sb_info *sbi = EXT4_SB(sb);
struct buffer_head *bh_p; struct buffer_head *bh_p;
KUNIT_STATIC_STUB_REDIRECT(ext4_get_group_desc,
sb, block_group, bh);
if (block_group >= ngroups) { if (block_group >= ngroups) {
ext4_error(sb, "block_group >= groups_count - block_group = %u," ext4_error(sb, "block_group >= groups_count - block_group = %u,"
" groups_count = %u", block_group, ngroups); " groups_count = %u", block_group, ngroups);
...@@ -468,6 +470,9 @@ ext4_read_block_bitmap_nowait(struct super_block *sb, ext4_group_t block_group, ...@@ -468,6 +470,9 @@ ext4_read_block_bitmap_nowait(struct super_block *sb, ext4_group_t block_group,
ext4_fsblk_t bitmap_blk; ext4_fsblk_t bitmap_blk;
int err; int err;
KUNIT_STATIC_STUB_REDIRECT(ext4_read_block_bitmap_nowait,
sb, block_group, ignore_locked);
desc = ext4_get_group_desc(sb, block_group, NULL); desc = ext4_get_group_desc(sb, block_group, NULL);
if (!desc) if (!desc)
return ERR_PTR(-EFSCORRUPTED); return ERR_PTR(-EFSCORRUPTED);
...@@ -563,6 +568,9 @@ int ext4_wait_block_bitmap(struct super_block *sb, ext4_group_t block_group, ...@@ -563,6 +568,9 @@ int ext4_wait_block_bitmap(struct super_block *sb, ext4_group_t block_group,
{ {
struct ext4_group_desc *desc; struct ext4_group_desc *desc;
KUNIT_STATIC_STUB_REDIRECT(ext4_wait_block_bitmap,
sb, block_group, bh);
if (!buffer_new(bh)) if (!buffer_new(bh))
return 0; return 0;
desc = ext4_get_group_desc(sb, block_group, NULL); desc = ext4_get_group_desc(sb, block_group, NULL);
......
...@@ -1504,6 +1504,7 @@ struct ext4_sb_info { ...@@ -1504,6 +1504,7 @@ struct ext4_sb_info {
loff_t s_bitmap_maxbytes; /* max bytes for bitmap files */ loff_t s_bitmap_maxbytes; /* max bytes for bitmap files */
struct buffer_head * s_sbh; /* Buffer containing the super block */ struct buffer_head * s_sbh; /* Buffer containing the super block */
struct ext4_super_block *s_es; /* Pointer to the super block in the buffer */ struct ext4_super_block *s_es; /* Pointer to the super block in the buffer */
/* Array of bh's for the block group descriptors */
struct buffer_head * __rcu *s_group_desc; struct buffer_head * __rcu *s_group_desc;
unsigned int s_mount_opt; unsigned int s_mount_opt;
unsigned int s_mount_opt2; unsigned int s_mount_opt2;
...@@ -1574,7 +1575,7 @@ struct ext4_sb_info { ...@@ -1574,7 +1575,7 @@ struct ext4_sb_info {
unsigned int *s_mb_maxs; unsigned int *s_mb_maxs;
unsigned int s_group_info_size; unsigned int s_group_info_size;
unsigned int s_mb_free_pending; unsigned int s_mb_free_pending;
struct list_head s_freed_data_list; /* List of blocks to be freed struct list_head s_freed_data_list[2]; /* List of blocks to be freed
after commit completed */ after commit completed */
struct list_head s_discard_list; struct list_head s_discard_list;
struct work_struct s_discard_work; struct work_struct s_discard_work;
...@@ -1686,7 +1687,8 @@ struct ext4_sb_info { ...@@ -1686,7 +1687,8 @@ struct ext4_sb_info {
/* /*
* Barrier between writepages ops and changing any inode's JOURNAL_DATA * Barrier between writepages ops and changing any inode's JOURNAL_DATA
* or EXTENTS flag. * or EXTENTS flag or between writepages ops and changing DELALLOC or
* DIOREAD_NOLOCK mount options on remount.
*/ */
struct percpu_rw_semaphore s_writepages_rwsem; struct percpu_rw_semaphore s_writepages_rwsem;
struct dax_device *s_daxdev; struct dax_device *s_daxdev;
...@@ -2934,7 +2936,7 @@ extern int ext4_group_add_blocks(handle_t *handle, struct super_block *sb, ...@@ -2934,7 +2936,7 @@ extern int ext4_group_add_blocks(handle_t *handle, struct super_block *sb,
extern int ext4_trim_fs(struct super_block *, struct fstrim_range *); extern int ext4_trim_fs(struct super_block *, struct fstrim_range *);
extern void ext4_process_freed_data(struct super_block *sb, tid_t commit_tid); extern void ext4_process_freed_data(struct super_block *sb, tid_t commit_tid);
extern void ext4_mb_mark_bb(struct super_block *sb, ext4_fsblk_t block, extern void ext4_mb_mark_bb(struct super_block *sb, ext4_fsblk_t block,
int len, int state); int len, bool state);
static inline bool ext4_mb_cr_expensive(enum criteria cr) static inline bool ext4_mb_cr_expensive(enum criteria cr)
{ {
return cr >= CR_GOAL_LEN_SLOW; return cr >= CR_GOAL_LEN_SLOW;
......
...@@ -1010,6 +1010,11 @@ static int ext4_ext_insert_index(handle_t *handle, struct inode *inode, ...@@ -1010,6 +1010,11 @@ static int ext4_ext_insert_index(handle_t *handle, struct inode *inode,
ix = curp->p_idx; ix = curp->p_idx;
} }
if (unlikely(ix > EXT_MAX_INDEX(curp->p_hdr))) {
EXT4_ERROR_INODE(inode, "ix > EXT_MAX_INDEX!");
return -EFSCORRUPTED;
}
len = EXT_LAST_INDEX(curp->p_hdr) - ix + 1; len = EXT_LAST_INDEX(curp->p_hdr) - ix + 1;
BUG_ON(len < 0); BUG_ON(len < 0);
if (len > 0) { if (len > 0) {
...@@ -1019,11 +1024,6 @@ static int ext4_ext_insert_index(handle_t *handle, struct inode *inode, ...@@ -1019,11 +1024,6 @@ static int ext4_ext_insert_index(handle_t *handle, struct inode *inode,
memmove(ix + 1, ix, len * sizeof(struct ext4_extent_idx)); memmove(ix + 1, ix, len * sizeof(struct ext4_extent_idx));
} }
if (unlikely(ix > EXT_MAX_INDEX(curp->p_hdr))) {
EXT4_ERROR_INODE(inode, "ix > EXT_MAX_INDEX!");
return -EFSCORRUPTED;
}
ix->ei_block = cpu_to_le32(logical); ix->ei_block = cpu_to_le32(logical);
ext4_idx_store_pblock(ix, ptr); ext4_idx_store_pblock(ix, ptr);
le16_add_cpu(&curp->p_hdr->eh_entries, 1); le16_add_cpu(&curp->p_hdr->eh_entries, 1);
...@@ -6081,13 +6081,13 @@ int ext4_ext_clear_bb(struct inode *inode) ...@@ -6081,13 +6081,13 @@ int ext4_ext_clear_bb(struct inode *inode)
for (j = 0; j < path->p_depth; j++) { for (j = 0; j < path->p_depth; j++) {
ext4_mb_mark_bb(inode->i_sb, ext4_mb_mark_bb(inode->i_sb,
path[j].p_block, 1, 0); path[j].p_block, 1, false);
ext4_fc_record_regions(inode->i_sb, inode->i_ino, ext4_fc_record_regions(inode->i_sb, inode->i_ino,
0, path[j].p_block, 1, 1); 0, path[j].p_block, 1, 1);
} }
ext4_free_ext_path(path); ext4_free_ext_path(path);
} }
ext4_mb_mark_bb(inode->i_sb, map.m_pblk, map.m_len, 0); ext4_mb_mark_bb(inode->i_sb, map.m_pblk, map.m_len, false);
ext4_fc_record_regions(inode->i_sb, inode->i_ino, ext4_fc_record_regions(inode->i_sb, inode->i_ino,
map.m_lblk, map.m_pblk, map.m_len, 1); map.m_lblk, map.m_pblk, map.m_len, 1);
} }
......
...@@ -152,8 +152,9 @@ static int __es_remove_extent(struct inode *inode, ext4_lblk_t lblk, ...@@ -152,8 +152,9 @@ static int __es_remove_extent(struct inode *inode, ext4_lblk_t lblk,
static int es_reclaim_extents(struct ext4_inode_info *ei, int *nr_to_scan); static int es_reclaim_extents(struct ext4_inode_info *ei, int *nr_to_scan);
static int __es_shrink(struct ext4_sb_info *sbi, int nr_to_scan, static int __es_shrink(struct ext4_sb_info *sbi, int nr_to_scan,
struct ext4_inode_info *locked_ei); struct ext4_inode_info *locked_ei);
static void __revise_pending(struct inode *inode, ext4_lblk_t lblk, static int __revise_pending(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t len); ext4_lblk_t len,
struct pending_reservation **prealloc);
int __init ext4_init_es(void) int __init ext4_init_es(void)
{ {
...@@ -448,6 +449,19 @@ static void ext4_es_list_del(struct inode *inode) ...@@ -448,6 +449,19 @@ static void ext4_es_list_del(struct inode *inode)
spin_unlock(&sbi->s_es_lock); spin_unlock(&sbi->s_es_lock);
} }
static inline struct pending_reservation *__alloc_pending(bool nofail)
{
if (!nofail)
return kmem_cache_alloc(ext4_pending_cachep, GFP_ATOMIC);
return kmem_cache_zalloc(ext4_pending_cachep, GFP_KERNEL | __GFP_NOFAIL);
}
static inline void __free_pending(struct pending_reservation *pr)
{
kmem_cache_free(ext4_pending_cachep, pr);
}
/* /*
* Returns true if we cannot fail to allocate memory for this extent_status * Returns true if we cannot fail to allocate memory for this extent_status
* entry and cannot reclaim it until its status changes. * entry and cannot reclaim it until its status changes.
...@@ -836,11 +850,12 @@ void ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk, ...@@ -836,11 +850,12 @@ void ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk,
{ {
struct extent_status newes; struct extent_status newes;
ext4_lblk_t end = lblk + len - 1; ext4_lblk_t end = lblk + len - 1;
int err1 = 0; int err1 = 0, err2 = 0, err3 = 0;
int err2 = 0;
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
struct extent_status *es1 = NULL; struct extent_status *es1 = NULL;
struct extent_status *es2 = NULL; struct extent_status *es2 = NULL;
struct pending_reservation *pr = NULL;
bool revise_pending = false;
if (EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY) if (EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY)
return; return;
...@@ -868,11 +883,17 @@ void ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk, ...@@ -868,11 +883,17 @@ void ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk,
ext4_es_insert_extent_check(inode, &newes); ext4_es_insert_extent_check(inode, &newes);
revise_pending = sbi->s_cluster_ratio > 1 &&
test_opt(inode->i_sb, DELALLOC) &&
(status & (EXTENT_STATUS_WRITTEN |
EXTENT_STATUS_UNWRITTEN));
retry: retry:
if (err1 && !es1) if (err1 && !es1)
es1 = __es_alloc_extent(true); es1 = __es_alloc_extent(true);
if ((err1 || err2) && !es2) if ((err1 || err2) && !es2)
es2 = __es_alloc_extent(true); es2 = __es_alloc_extent(true);
if ((err1 || err2 || err3) && revise_pending && !pr)
pr = __alloc_pending(true);
write_lock(&EXT4_I(inode)->i_es_lock); write_lock(&EXT4_I(inode)->i_es_lock);
err1 = __es_remove_extent(inode, lblk, end, NULL, es1); err1 = __es_remove_extent(inode, lblk, end, NULL, es1);
...@@ -897,13 +918,18 @@ void ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk, ...@@ -897,13 +918,18 @@ void ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk,
es2 = NULL; es2 = NULL;
} }
if (sbi->s_cluster_ratio > 1 && test_opt(inode->i_sb, DELALLOC) && if (revise_pending) {
(status & EXTENT_STATUS_WRITTEN || err3 = __revise_pending(inode, lblk, len, &pr);
status & EXTENT_STATUS_UNWRITTEN)) if (err3 != 0)
__revise_pending(inode, lblk, len); goto error;
if (pr) {
__free_pending(pr);
pr = NULL;
}
}
error: error:
write_unlock(&EXT4_I(inode)->i_es_lock); write_unlock(&EXT4_I(inode)->i_es_lock);
if (err1 || err2) if (err1 || err2 || err3)
goto retry; goto retry;
ext4_es_print_tree(inode); ext4_es_print_tree(inode);
...@@ -1311,7 +1337,7 @@ static unsigned int get_rsvd(struct inode *inode, ext4_lblk_t end, ...@@ -1311,7 +1337,7 @@ static unsigned int get_rsvd(struct inode *inode, ext4_lblk_t end,
rc->ndelonly--; rc->ndelonly--;
node = rb_next(&pr->rb_node); node = rb_next(&pr->rb_node);
rb_erase(&pr->rb_node, &tree->root); rb_erase(&pr->rb_node, &tree->root);
kmem_cache_free(ext4_pending_cachep, pr); __free_pending(pr);
if (!node) if (!node)
break; break;
pr = rb_entry(node, struct pending_reservation, pr = rb_entry(node, struct pending_reservation,
...@@ -1405,8 +1431,8 @@ static int __es_remove_extent(struct inode *inode, ext4_lblk_t lblk, ...@@ -1405,8 +1431,8 @@ static int __es_remove_extent(struct inode *inode, ext4_lblk_t lblk,
} }
} }
if (count_reserved) if (count_reserved)
count_rsvd(inode, lblk, orig_es.es_len - len1 - len2, count_rsvd(inode, orig_es.es_lblk + len1,
&orig_es, &rc); orig_es.es_len - len1 - len2, &orig_es, &rc);
goto out_get_reserved; goto out_get_reserved;
} }
...@@ -1907,11 +1933,13 @@ static struct pending_reservation *__get_pending(struct inode *inode, ...@@ -1907,11 +1933,13 @@ static struct pending_reservation *__get_pending(struct inode *inode,
* *
* @inode - file containing the cluster * @inode - file containing the cluster
* @lblk - logical block in the cluster to be added * @lblk - logical block in the cluster to be added
* @prealloc - preallocated pending entry
* *
* Returns 0 on successful insertion and -ENOMEM on failure. If the * Returns 0 on successful insertion and -ENOMEM on failure. If the
* pending reservation is already in the set, returns successfully. * pending reservation is already in the set, returns successfully.
*/ */
static int __insert_pending(struct inode *inode, ext4_lblk_t lblk) static int __insert_pending(struct inode *inode, ext4_lblk_t lblk,
struct pending_reservation **prealloc)
{ {
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
struct ext4_pending_tree *tree = &EXT4_I(inode)->i_pending_tree; struct ext4_pending_tree *tree = &EXT4_I(inode)->i_pending_tree;
...@@ -1937,10 +1965,15 @@ static int __insert_pending(struct inode *inode, ext4_lblk_t lblk) ...@@ -1937,10 +1965,15 @@ static int __insert_pending(struct inode *inode, ext4_lblk_t lblk)
} }
} }
pr = kmem_cache_alloc(ext4_pending_cachep, GFP_ATOMIC); if (likely(*prealloc == NULL)) {
if (pr == NULL) { pr = __alloc_pending(false);
ret = -ENOMEM; if (!pr) {
goto out; ret = -ENOMEM;
goto out;
}
} else {
pr = *prealloc;
*prealloc = NULL;
} }
pr->lclu = lclu; pr->lclu = lclu;
...@@ -1970,7 +2003,7 @@ static void __remove_pending(struct inode *inode, ext4_lblk_t lblk) ...@@ -1970,7 +2003,7 @@ static void __remove_pending(struct inode *inode, ext4_lblk_t lblk)
if (pr != NULL) { if (pr != NULL) {
tree = &EXT4_I(inode)->i_pending_tree; tree = &EXT4_I(inode)->i_pending_tree;
rb_erase(&pr->rb_node, &tree->root); rb_erase(&pr->rb_node, &tree->root);
kmem_cache_free(ext4_pending_cachep, pr); __free_pending(pr);
} }
} }
...@@ -2029,10 +2062,10 @@ void ext4_es_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk, ...@@ -2029,10 +2062,10 @@ void ext4_es_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk,
bool allocated) bool allocated)
{ {
struct extent_status newes; struct extent_status newes;
int err1 = 0; int err1 = 0, err2 = 0, err3 = 0;
int err2 = 0;
struct extent_status *es1 = NULL; struct extent_status *es1 = NULL;
struct extent_status *es2 = NULL; struct extent_status *es2 = NULL;
struct pending_reservation *pr = NULL;
if (EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY) if (EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY)
return; return;
...@@ -2052,6 +2085,8 @@ void ext4_es_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk, ...@@ -2052,6 +2085,8 @@ void ext4_es_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk,
es1 = __es_alloc_extent(true); es1 = __es_alloc_extent(true);
if ((err1 || err2) && !es2) if ((err1 || err2) && !es2)
es2 = __es_alloc_extent(true); es2 = __es_alloc_extent(true);
if ((err1 || err2 || err3) && allocated && !pr)
pr = __alloc_pending(true);
write_lock(&EXT4_I(inode)->i_es_lock); write_lock(&EXT4_I(inode)->i_es_lock);
err1 = __es_remove_extent(inode, lblk, lblk, NULL, es1); err1 = __es_remove_extent(inode, lblk, lblk, NULL, es1);
...@@ -2074,11 +2109,18 @@ void ext4_es_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk, ...@@ -2074,11 +2109,18 @@ void ext4_es_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk,
es2 = NULL; es2 = NULL;
} }
if (allocated) if (allocated) {
__insert_pending(inode, lblk); err3 = __insert_pending(inode, lblk, &pr);
if (err3 != 0)
goto error;
if (pr) {
__free_pending(pr);
pr = NULL;
}
}
error: error:
write_unlock(&EXT4_I(inode)->i_es_lock); write_unlock(&EXT4_I(inode)->i_es_lock);
if (err1 || err2) if (err1 || err2 || err3)
goto retry; goto retry;
ext4_es_print_tree(inode); ext4_es_print_tree(inode);
...@@ -2184,21 +2226,24 @@ unsigned int ext4_es_delayed_clu(struct inode *inode, ext4_lblk_t lblk, ...@@ -2184,21 +2226,24 @@ unsigned int ext4_es_delayed_clu(struct inode *inode, ext4_lblk_t lblk,
* @inode - file containing the range * @inode - file containing the range
* @lblk - logical block defining the start of range * @lblk - logical block defining the start of range
* @len - length of range in blocks * @len - length of range in blocks
* @prealloc - preallocated pending entry
* *
* Used after a newly allocated extent is added to the extents status tree. * Used after a newly allocated extent is added to the extents status tree.
* Requires that the extents in the range have either written or unwritten * Requires that the extents in the range have either written or unwritten
* status. Must be called while holding i_es_lock. * status. Must be called while holding i_es_lock.
*/ */
static void __revise_pending(struct inode *inode, ext4_lblk_t lblk, static int __revise_pending(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t len) ext4_lblk_t len,
struct pending_reservation **prealloc)
{ {
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
ext4_lblk_t end = lblk + len - 1; ext4_lblk_t end = lblk + len - 1;
ext4_lblk_t first, last; ext4_lblk_t first, last;
bool f_del = false, l_del = false; bool f_del = false, l_del = false;
int ret = 0;
if (len == 0) if (len == 0)
return; return 0;
/* /*
* Two cases - block range within single cluster and block range * Two cases - block range within single cluster and block range
...@@ -2219,7 +2264,9 @@ static void __revise_pending(struct inode *inode, ext4_lblk_t lblk, ...@@ -2219,7 +2264,9 @@ static void __revise_pending(struct inode *inode, ext4_lblk_t lblk,
f_del = __es_scan_range(inode, &ext4_es_is_delonly, f_del = __es_scan_range(inode, &ext4_es_is_delonly,
first, lblk - 1); first, lblk - 1);
if (f_del) { if (f_del) {
__insert_pending(inode, first); ret = __insert_pending(inode, first, prealloc);
if (ret < 0)
goto out;
} else { } else {
last = EXT4_LBLK_CMASK(sbi, end) + last = EXT4_LBLK_CMASK(sbi, end) +
sbi->s_cluster_ratio - 1; sbi->s_cluster_ratio - 1;
...@@ -2227,9 +2274,11 @@ static void __revise_pending(struct inode *inode, ext4_lblk_t lblk, ...@@ -2227,9 +2274,11 @@ static void __revise_pending(struct inode *inode, ext4_lblk_t lblk,
l_del = __es_scan_range(inode, l_del = __es_scan_range(inode,
&ext4_es_is_delonly, &ext4_es_is_delonly,
end + 1, last); end + 1, last);
if (l_del) if (l_del) {
__insert_pending(inode, last); ret = __insert_pending(inode, last, prealloc);
else if (ret < 0)
goto out;
} else
__remove_pending(inode, last); __remove_pending(inode, last);
} }
} else { } else {
...@@ -2237,18 +2286,24 @@ static void __revise_pending(struct inode *inode, ext4_lblk_t lblk, ...@@ -2237,18 +2286,24 @@ static void __revise_pending(struct inode *inode, ext4_lblk_t lblk,
if (first != lblk) if (first != lblk)
f_del = __es_scan_range(inode, &ext4_es_is_delonly, f_del = __es_scan_range(inode, &ext4_es_is_delonly,
first, lblk - 1); first, lblk - 1);
if (f_del) if (f_del) {
__insert_pending(inode, first); ret = __insert_pending(inode, first, prealloc);
else if (ret < 0)
goto out;
} else
__remove_pending(inode, first); __remove_pending(inode, first);
last = EXT4_LBLK_CMASK(sbi, end) + sbi->s_cluster_ratio - 1; last = EXT4_LBLK_CMASK(sbi, end) + sbi->s_cluster_ratio - 1;
if (last != end) if (last != end)
l_del = __es_scan_range(inode, &ext4_es_is_delonly, l_del = __es_scan_range(inode, &ext4_es_is_delonly,
end + 1, last); end + 1, last);
if (l_del) if (l_del) {
__insert_pending(inode, last); ret = __insert_pending(inode, last, prealloc);
else if (ret < 0)
goto out;
} else
__remove_pending(inode, last); __remove_pending(inode, last);
} }
out:
return ret;
} }
...@@ -1806,7 +1806,7 @@ static int ext4_fc_replay_add_range(struct super_block *sb, ...@@ -1806,7 +1806,7 @@ static int ext4_fc_replay_add_range(struct super_block *sb,
* at the end of the FC replay using our array of * at the end of the FC replay using our array of
* modified inodes. * modified inodes.
*/ */
ext4_mb_mark_bb(inode->i_sb, map.m_pblk, map.m_len, 0); ext4_mb_mark_bb(inode->i_sb, map.m_pblk, map.m_len, false);
goto next; goto next;
} }
...@@ -1875,7 +1875,7 @@ ext4_fc_replay_del_range(struct super_block *sb, ...@@ -1875,7 +1875,7 @@ ext4_fc_replay_del_range(struct super_block *sb,
if (ret > 0) { if (ret > 0) {
remaining -= ret; remaining -= ret;
cur += ret; cur += ret;
ext4_mb_mark_bb(inode->i_sb, map.m_pblk, map.m_len, 0); ext4_mb_mark_bb(inode->i_sb, map.m_pblk, map.m_len, false);
} else { } else {
remaining -= map.m_len; remaining -= map.m_len;
cur += map.m_len; cur += map.m_len;
...@@ -1934,12 +1934,12 @@ static void ext4_fc_set_bitmaps_and_counters(struct super_block *sb) ...@@ -1934,12 +1934,12 @@ static void ext4_fc_set_bitmaps_and_counters(struct super_block *sb)
if (!IS_ERR(path)) { if (!IS_ERR(path)) {
for (j = 0; j < path->p_depth; j++) for (j = 0; j < path->p_depth; j++)
ext4_mb_mark_bb(inode->i_sb, ext4_mb_mark_bb(inode->i_sb,
path[j].p_block, 1, 1); path[j].p_block, 1, true);
ext4_free_ext_path(path); ext4_free_ext_path(path);
} }
cur += ret; cur += ret;
ext4_mb_mark_bb(inode->i_sb, map.m_pblk, ext4_mb_mark_bb(inode->i_sb, map.m_pblk,
map.m_len, 1); map.m_len, true);
} else { } else {
cur = cur + (map.m_len ? map.m_len : 1); cur = cur + (map.m_len ? map.m_len : 1);
} }
......
...@@ -306,80 +306,38 @@ static ssize_t ext4_buffered_write_iter(struct kiocb *iocb, ...@@ -306,80 +306,38 @@ static ssize_t ext4_buffered_write_iter(struct kiocb *iocb,
} }
static ssize_t ext4_handle_inode_extension(struct inode *inode, loff_t offset, static ssize_t ext4_handle_inode_extension(struct inode *inode, loff_t offset,
ssize_t written, size_t count) ssize_t count)
{ {
handle_t *handle; handle_t *handle;
bool truncate = false;
u8 blkbits = inode->i_blkbits;
ext4_lblk_t written_blk, end_blk;
int ret;
/*
* Note that EXT4_I(inode)->i_disksize can get extended up to
* inode->i_size while the I/O was running due to writeback of delalloc
* blocks. But, the code in ext4_iomap_alloc() is careful to use
* zeroed/unwritten extents if this is possible; thus we won't leave
* uninitialized blocks in a file even if we didn't succeed in writing
* as much as we intended.
*/
WARN_ON_ONCE(i_size_read(inode) < EXT4_I(inode)->i_disksize);
if (offset + count <= EXT4_I(inode)->i_disksize) {
/*
* We need to ensure that the inode is removed from the orphan
* list if it has been added prematurely, due to writeback of
* delalloc blocks.
*/
if (!list_empty(&EXT4_I(inode)->i_orphan) && inode->i_nlink) {
handle = ext4_journal_start(inode, EXT4_HT_INODE, 2);
if (IS_ERR(handle)) {
ext4_orphan_del(NULL, inode);
return PTR_ERR(handle);
}
ext4_orphan_del(handle, inode);
ext4_journal_stop(handle);
}
return written;
}
if (written < 0)
goto truncate;
lockdep_assert_held_write(&inode->i_rwsem);
handle = ext4_journal_start(inode, EXT4_HT_INODE, 2); handle = ext4_journal_start(inode, EXT4_HT_INODE, 2);
if (IS_ERR(handle)) { if (IS_ERR(handle))
written = PTR_ERR(handle); return PTR_ERR(handle);
goto truncate;
}
if (ext4_update_inode_size(inode, offset + written)) { if (ext4_update_inode_size(inode, offset + count)) {
ret = ext4_mark_inode_dirty(handle, inode); int ret = ext4_mark_inode_dirty(handle, inode);
if (unlikely(ret)) { if (unlikely(ret)) {
written = ret;
ext4_journal_stop(handle); ext4_journal_stop(handle);
goto truncate; return ret;
} }
} }
/* if (inode->i_nlink)
* We may need to truncate allocated but not written blocks beyond EOF.
*/
written_blk = ALIGN(offset + written, 1 << blkbits);
end_blk = ALIGN(offset + count, 1 << blkbits);
if (written_blk < end_blk && ext4_can_truncate(inode))
truncate = true;
/*
* Remove the inode from the orphan list if it has been extended and
* everything went OK.
*/
if (!truncate && inode->i_nlink)
ext4_orphan_del(handle, inode); ext4_orphan_del(handle, inode);
ext4_journal_stop(handle); ext4_journal_stop(handle);
if (truncate) { return count;
truncate: }
/*
* Clean up the inode after DIO or DAX extending write has completed and the
* inode size has been updated using ext4_handle_inode_extension().
*/
static void ext4_inode_extension_cleanup(struct inode *inode, ssize_t count)
{
lockdep_assert_held_write(&inode->i_rwsem);
if (count < 0) {
ext4_truncate_failed_write(inode); ext4_truncate_failed_write(inode);
/* /*
* If the truncate operation failed early, then the inode may * If the truncate operation failed early, then the inode may
...@@ -388,9 +346,28 @@ static ssize_t ext4_handle_inode_extension(struct inode *inode, loff_t offset, ...@@ -388,9 +346,28 @@ static ssize_t ext4_handle_inode_extension(struct inode *inode, loff_t offset,
*/ */
if (inode->i_nlink) if (inode->i_nlink)
ext4_orphan_del(NULL, inode); ext4_orphan_del(NULL, inode);
return;
} }
/*
* If i_disksize got extended due to writeback of delalloc blocks while
* the DIO was running we could fail to cleanup the orphan list in
* ext4_handle_inode_extension(). Do it now.
*/
if (!list_empty(&EXT4_I(inode)->i_orphan) && inode->i_nlink) {
handle_t *handle = ext4_journal_start(inode, EXT4_HT_INODE, 2);
return written; if (IS_ERR(handle)) {
/*
* The write has successfully completed. Not much to
* do with the error here so just cleanup the orphan
* list and hope for the best.
*/
ext4_orphan_del(NULL, inode);
return;
}
ext4_orphan_del(handle, inode);
ext4_journal_stop(handle);
}
} }
static int ext4_dio_write_end_io(struct kiocb *iocb, ssize_t size, static int ext4_dio_write_end_io(struct kiocb *iocb, ssize_t size,
...@@ -399,31 +376,22 @@ static int ext4_dio_write_end_io(struct kiocb *iocb, ssize_t size, ...@@ -399,31 +376,22 @@ static int ext4_dio_write_end_io(struct kiocb *iocb, ssize_t size,
loff_t pos = iocb->ki_pos; loff_t pos = iocb->ki_pos;
struct inode *inode = file_inode(iocb->ki_filp); struct inode *inode = file_inode(iocb->ki_filp);
if (!error && size && flags & IOMAP_DIO_UNWRITTEN)
error = ext4_convert_unwritten_extents(NULL, inode, pos, size);
if (error) if (error)
return error; return error;
if (size && flags & IOMAP_DIO_UNWRITTEN) {
error = ext4_convert_unwritten_extents(NULL, inode, pos, size);
if (error < 0)
return error;
}
/* /*
* If we are extending the file, we have to update i_size here before * Note that EXT4_I(inode)->i_disksize can get extended up to
* page cache gets invalidated in iomap_dio_rw(). Otherwise racing * inode->i_size while the I/O was running due to writeback of delalloc
* buffered reads could zero out too much from page cache pages. Update * blocks. But the code in ext4_iomap_alloc() is careful to use
* of on-disk size will happen later in ext4_dio_write_iter() where * zeroed/unwritten extents if this is possible; thus we won't leave
* we have enough information to also perform orphan list handling etc. * uninitialized blocks in a file even if we didn't succeed in writing
* Note that we perform all extending writes synchronously under * as much as we intended.
* i_rwsem held exclusively so i_size update is safe here in that case.
* If the write was not extending, we cannot see pos > i_size here
* because operations reducing i_size like truncate wait for all
* outstanding DIO before updating i_size.
*/ */
pos += size; WARN_ON_ONCE(i_size_read(inode) < READ_ONCE(EXT4_I(inode)->i_disksize));
if (pos > i_size_read(inode)) if (pos + size <= READ_ONCE(EXT4_I(inode)->i_disksize))
i_size_write(inode, pos); return size;
return ext4_handle_inode_extension(inode, pos, size);
return 0;
} }
static const struct iomap_dio_ops ext4_dio_write_ops = { static const struct iomap_dio_ops ext4_dio_write_ops = {
...@@ -569,18 +537,20 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from) ...@@ -569,18 +537,20 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
return ext4_buffered_write_iter(iocb, from); return ext4_buffered_write_iter(iocb, from);
} }
/*
* Prevent inline data from being created since we are going to allocate
* blocks for DIO. We know the inode does not currently have inline data
* because ext4_should_use_dio() checked for it, but we have to clear
* the state flag before the write checks because a lock cycle could
* introduce races with other writers.
*/
ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
ret = ext4_dio_write_checks(iocb, from, &ilock_shared, &extend, ret = ext4_dio_write_checks(iocb, from, &ilock_shared, &extend,
&unwritten, &dio_flags); &unwritten, &dio_flags);
if (ret <= 0) if (ret <= 0)
return ret; return ret;
/*
* Make sure inline data cannot be created anymore since we are going
* to allocate blocks for DIO. We know the inode does not have any
* inline data now because ext4_dio_supported() checked for that.
*/
ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
offset = iocb->ki_pos; offset = iocb->ki_pos;
count = ret; count = ret;
...@@ -606,9 +576,16 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from) ...@@ -606,9 +576,16 @@ static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
dio_flags, NULL, 0); dio_flags, NULL, 0);
if (ret == -ENOTBLK) if (ret == -ENOTBLK)
ret = 0; ret = 0;
if (extend) {
if (extend) /*
ret = ext4_handle_inode_extension(inode, offset, ret, count); * We always perform extending DIO write synchronously so by
* now the IO is completed and ext4_handle_inode_extension()
* was called. Cleanup the inode in case of error or race with
* writeback of delalloc blocks.
*/
WARN_ON_ONCE(ret == -EIOCBQUEUED);
ext4_inode_extension_cleanup(inode, ret);
}
out: out:
if (ilock_shared) if (ilock_shared)
...@@ -689,8 +666,10 @@ ext4_dax_write_iter(struct kiocb *iocb, struct iov_iter *from) ...@@ -689,8 +666,10 @@ ext4_dax_write_iter(struct kiocb *iocb, struct iov_iter *from)
ret = dax_iomap_rw(iocb, from, &ext4_iomap_ops); ret = dax_iomap_rw(iocb, from, &ext4_iomap_ops);
if (extend) if (extend) {
ret = ext4_handle_inode_extension(inode, offset, ret, count); ret = ext4_handle_inode_extension(inode, offset, ret);
ext4_inode_extension_cleanup(inode, ret);
}
out: out:
inode_unlock(inode); inode_unlock(inode);
if (ret > 0) if (ret > 0)
......
...@@ -789,10 +789,22 @@ int ext4_get_block(struct inode *inode, sector_t iblock, ...@@ -789,10 +789,22 @@ int ext4_get_block(struct inode *inode, sector_t iblock,
int ext4_get_block_unwritten(struct inode *inode, sector_t iblock, int ext4_get_block_unwritten(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create) struct buffer_head *bh_result, int create)
{ {
int ret = 0;
ext4_debug("ext4_get_block_unwritten: inode %lu, create flag %d\n", ext4_debug("ext4_get_block_unwritten: inode %lu, create flag %d\n",
inode->i_ino, create); inode->i_ino, create);
return _ext4_get_block(inode, iblock, bh_result, ret = _ext4_get_block(inode, iblock, bh_result,
EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT); EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT);
/*
* If the buffer is marked unwritten, mark it as new to make sure it is
* zeroed out correctly in case of partial writes. Otherwise, there is
* a chance of stale data getting exposed.
*/
if (ret == 0 && buffer_unwritten(bh_result))
set_buffer_new(bh_result);
return ret;
} }
/* Maximum number of blocks we map for direct IO at once. */ /* Maximum number of blocks we map for direct IO at once. */
......
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit test of ext4 multiblocks allocation.
*/
#include <kunit/test.h>
#include <kunit/static_stub.h>
#include "ext4.h"
struct mbt_grp_ctx {
struct buffer_head bitmap_bh;
/* desc and gd_bh are just the place holders for now */
struct ext4_group_desc desc;
struct buffer_head gd_bh;
};
struct mbt_ctx {
struct mbt_grp_ctx *grp_ctx;
};
struct mbt_ext4_super_block {
struct super_block sb;
struct mbt_ctx mbt_ctx;
};
#define MBT_CTX(_sb) (&(container_of((_sb), struct mbt_ext4_super_block, sb)->mbt_ctx))
#define MBT_GRP_CTX(_sb, _group) (&MBT_CTX(_sb)->grp_ctx[_group])
static struct super_block *mbt_ext4_alloc_super_block(void)
{
struct ext4_super_block *es = kzalloc(sizeof(*es), GFP_KERNEL);
struct ext4_sb_info *sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
struct mbt_ext4_super_block *fsb = kzalloc(sizeof(*fsb), GFP_KERNEL);
if (fsb == NULL || sbi == NULL || es == NULL)
goto out;
sbi->s_es = es;
fsb->sb.s_fs_info = sbi;
return &fsb->sb;
out:
kfree(fsb);
kfree(sbi);
kfree(es);
return NULL;
}
static void mbt_ext4_free_super_block(struct super_block *sb)
{
struct mbt_ext4_super_block *fsb =
container_of(sb, struct mbt_ext4_super_block, sb);
struct ext4_sb_info *sbi = EXT4_SB(sb);
kfree(sbi->s_es);
kfree(sbi);
kfree(fsb);
}
struct mbt_ext4_block_layout {
unsigned char blocksize_bits;
unsigned int cluster_bits;
uint32_t blocks_per_group;
ext4_group_t group_count;
uint16_t desc_size;
};
static void mbt_init_sb_layout(struct super_block *sb,
struct mbt_ext4_block_layout *layout)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_super_block *es = sbi->s_es;
sb->s_blocksize = 1UL << layout->blocksize_bits;
sb->s_blocksize_bits = layout->blocksize_bits;
sbi->s_groups_count = layout->group_count;
sbi->s_blocks_per_group = layout->blocks_per_group;
sbi->s_cluster_bits = layout->cluster_bits;
sbi->s_cluster_ratio = 1U << layout->cluster_bits;
sbi->s_clusters_per_group = layout->blocks_per_group >>
layout->cluster_bits;
sbi->s_desc_size = layout->desc_size;
es->s_first_data_block = cpu_to_le32(0);
es->s_blocks_count_lo = cpu_to_le32(layout->blocks_per_group *
layout->group_count);
}
static int mbt_grp_ctx_init(struct super_block *sb,
struct mbt_grp_ctx *grp_ctx)
{
grp_ctx->bitmap_bh.b_data = kzalloc(EXT4_BLOCK_SIZE(sb), GFP_KERNEL);
if (grp_ctx->bitmap_bh.b_data == NULL)
return -ENOMEM;
return 0;
}
static void mbt_grp_ctx_release(struct mbt_grp_ctx *grp_ctx)
{
kfree(grp_ctx->bitmap_bh.b_data);
grp_ctx->bitmap_bh.b_data = NULL;
}
static void mbt_ctx_mark_used(struct super_block *sb, ext4_group_t group,
unsigned int start, unsigned int len)
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group);
mb_set_bits(grp_ctx->bitmap_bh.b_data, start, len);
}
/* called after mbt_init_sb_layout */
static int mbt_ctx_init(struct super_block *sb)
{
struct mbt_ctx *ctx = MBT_CTX(sb);
ext4_group_t i, ngroups = ext4_get_groups_count(sb);
ctx->grp_ctx = kcalloc(ngroups, sizeof(struct mbt_grp_ctx),
GFP_KERNEL);
if (ctx->grp_ctx == NULL)
return -ENOMEM;
for (i = 0; i < ngroups; i++)
if (mbt_grp_ctx_init(sb, &ctx->grp_ctx[i]))
goto out;
/*
* first data block(first cluster in first group) is used by
* metadata, mark it used to avoid to alloc data block at first
* block which will fail ext4_sb_block_valid check.
*/
mb_set_bits(ctx->grp_ctx[0].bitmap_bh.b_data, 0, 1);
return 0;
out:
while (i-- > 0)
mbt_grp_ctx_release(&ctx->grp_ctx[i]);
kfree(ctx->grp_ctx);
return -ENOMEM;
}
static void mbt_ctx_release(struct super_block *sb)
{
struct mbt_ctx *ctx = MBT_CTX(sb);
ext4_group_t i, ngroups = ext4_get_groups_count(sb);
for (i = 0; i < ngroups; i++)
mbt_grp_ctx_release(&ctx->grp_ctx[i]);
kfree(ctx->grp_ctx);
}
static struct buffer_head *
ext4_read_block_bitmap_nowait_stub(struct super_block *sb, ext4_group_t block_group,
bool ignore_locked)
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, block_group);
/* paired with brelse from caller of ext4_read_block_bitmap_nowait */
get_bh(&grp_ctx->bitmap_bh);
return &grp_ctx->bitmap_bh;
}
static int ext4_wait_block_bitmap_stub(struct super_block *sb,
ext4_group_t block_group,
struct buffer_head *bh)
{
return 0;
}
static struct ext4_group_desc *
ext4_get_group_desc_stub(struct super_block *sb, ext4_group_t block_group,
struct buffer_head **bh)
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, block_group);
if (bh != NULL)
*bh = &grp_ctx->gd_bh;
return &grp_ctx->desc;
}
static int
ext4_mb_mark_context_stub(handle_t *handle, struct super_block *sb, bool state,
ext4_group_t group, ext4_grpblk_t blkoff,
ext4_grpblk_t len, int flags,
ext4_grpblk_t *ret_changed)
{
struct mbt_grp_ctx *grp_ctx = MBT_GRP_CTX(sb, group);
struct buffer_head *bitmap_bh = &grp_ctx->bitmap_bh;
if (state)
mb_set_bits(bitmap_bh->b_data, blkoff, len);
else
mb_clear_bits(bitmap_bh->b_data, blkoff, len);
return 0;
}
#define TEST_GOAL_GROUP 1
static int mbt_kunit_init(struct kunit *test)
{
struct mbt_ext4_block_layout *layout =
(struct mbt_ext4_block_layout *)(test->param_value);
struct super_block *sb;
int ret;
sb = mbt_ext4_alloc_super_block();
if (sb == NULL)
return -ENOMEM;
mbt_init_sb_layout(sb, layout);
ret = mbt_ctx_init(sb);
if (ret != 0) {
mbt_ext4_free_super_block(sb);
return ret;
}
test->priv = sb;
kunit_activate_static_stub(test,
ext4_read_block_bitmap_nowait,
ext4_read_block_bitmap_nowait_stub);
kunit_activate_static_stub(test,
ext4_wait_block_bitmap,
ext4_wait_block_bitmap_stub);
kunit_activate_static_stub(test,
ext4_get_group_desc,
ext4_get_group_desc_stub);
kunit_activate_static_stub(test,
ext4_mb_mark_context,
ext4_mb_mark_context_stub);
return 0;
}
static void mbt_kunit_exit(struct kunit *test)
{
struct super_block *sb = (struct super_block *)test->priv;
mbt_ctx_release(sb);
mbt_ext4_free_super_block(sb);
}
static void test_new_blocks_simple(struct kunit *test)
{
struct super_block *sb = (struct super_block *)test->priv;
struct inode inode = { .i_sb = sb, };
struct ext4_allocation_request ar;
ext4_group_t i, goal_group = TEST_GOAL_GROUP;
int err = 0;
ext4_fsblk_t found;
struct ext4_sb_info *sbi = EXT4_SB(sb);
ar.inode = &inode;
/* get block at goal */
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_EQ_MSG(test, ar.goal, found,
"failed to alloc block at goal, expected %llu found %llu",
ar.goal, found);
/* get block after goal in goal group */
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_EQ_MSG(test, ar.goal + EXT4_C2B(sbi, 1), found,
"failed to alloc block after goal in goal group, expected %llu found %llu",
ar.goal + 1, found);
/* get block after goal group */
mbt_ctx_mark_used(sb, goal_group, 0, EXT4_CLUSTERS_PER_GROUP(sb));
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_EQ_MSG(test,
ext4_group_first_block_no(sb, goal_group + 1), found,
"failed to alloc block after goal group, expected %llu found %llu",
ext4_group_first_block_no(sb, goal_group + 1), found);
/* get block before goal group */
for (i = goal_group; i < ext4_get_groups_count(sb); i++)
mbt_ctx_mark_used(sb, i, 0, EXT4_CLUSTERS_PER_GROUP(sb));
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_EQ_MSG(test,
ext4_group_first_block_no(sb, 0) + EXT4_C2B(sbi, 1), found,
"failed to alloc block before goal group, expected %llu found %llu",
ext4_group_first_block_no(sb, 0 + EXT4_C2B(sbi, 1)), found);
/* no block available, fail to allocate block */
for (i = 0; i < ext4_get_groups_count(sb); i++)
mbt_ctx_mark_used(sb, i, 0, EXT4_CLUSTERS_PER_GROUP(sb));
ar.goal = ext4_group_first_block_no(sb, goal_group);
found = ext4_mb_new_blocks_simple(&ar, &err);
KUNIT_ASSERT_NE_MSG(test, err, 0,
"unexpectedly get block when no block is available");
}
static const struct mbt_ext4_block_layout mbt_test_layouts[] = {
{
.blocksize_bits = 10,
.cluster_bits = 3,
.blocks_per_group = 8192,
.group_count = 4,
.desc_size = 64,
},
{
.blocksize_bits = 12,
.cluster_bits = 3,
.blocks_per_group = 8192,
.group_count = 4,
.desc_size = 64,
},
{
.blocksize_bits = 16,
.cluster_bits = 3,
.blocks_per_group = 8192,
.group_count = 4,
.desc_size = 64,
},
};
static void mbt_show_layout(const struct mbt_ext4_block_layout *layout,
char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "block_bits=%d cluster_bits=%d "
"blocks_per_group=%d group_count=%d desc_size=%d\n",
layout->blocksize_bits, layout->cluster_bits,
layout->blocks_per_group, layout->group_count,
layout->desc_size);
}
KUNIT_ARRAY_PARAM(mbt_layouts, mbt_test_layouts, mbt_show_layout);
static struct kunit_case mbt_test_cases[] = {
KUNIT_CASE_PARAM(test_new_blocks_simple, mbt_layouts_gen_params),
{}
};
static struct kunit_suite mbt_test_suite = {
.name = "ext4_mballoc_test",
.init = mbt_kunit_init,
.exit = mbt_kunit_exit,
.test_cases = mbt_test_cases,
};
kunit_test_suites(&mbt_test_suite);
MODULE_LICENSE("GPL");
This diff is collapsed.
...@@ -2280,8 +2280,7 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname, ...@@ -2280,8 +2280,7 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
top = data2 + len; top = data2 + len;
while ((char *)(de2 = ext4_next_entry(de, blocksize)) < top) { while ((char *)(de2 = ext4_next_entry(de, blocksize)) < top) {
if (ext4_check_dir_entry(dir, NULL, de, bh2, data2, len, if (ext4_check_dir_entry(dir, NULL, de, bh2, data2, len,
(data2 + (blocksize - csum_size) - (char *)de - data2)) {
(char *) de))) {
brelse(bh2); brelse(bh2);
brelse(bh); brelse(bh);
return -EFSCORRUPTED; return -EFSCORRUPTED;
......
...@@ -10,8 +10,6 @@ ...@@ -10,8 +10,6 @@
*/ */
#define EXT4FS_DEBUG
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/jiffies.h> #include <linux/jiffies.h>
...@@ -57,7 +55,7 @@ int ext4_resize_begin(struct super_block *sb) ...@@ -57,7 +55,7 @@ int ext4_resize_begin(struct super_block *sb)
* If the reserved GDT blocks is non-zero, the resize_inode feature * If the reserved GDT blocks is non-zero, the resize_inode feature
* should always be set. * should always be set.
*/ */
if (EXT4_SB(sb)->s_es->s_reserved_gdt_blocks && if (sbi->s_es->s_reserved_gdt_blocks &&
!ext4_has_feature_resize_inode(sb)) { !ext4_has_feature_resize_inode(sb)) {
ext4_error(sb, "resize_inode disabled but reserved GDT blocks non-zero"); ext4_error(sb, "resize_inode disabled but reserved GDT blocks non-zero");
return -EFSCORRUPTED; return -EFSCORRUPTED;
...@@ -69,9 +67,9 @@ int ext4_resize_begin(struct super_block *sb) ...@@ -69,9 +67,9 @@ int ext4_resize_begin(struct super_block *sb)
* bad time to do it anyways. * bad time to do it anyways.
*/ */
if (EXT4_B2C(sbi, sbi->s_sbh->b_blocknr) != if (EXT4_B2C(sbi, sbi->s_sbh->b_blocknr) !=
le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block)) { le32_to_cpu(sbi->s_es->s_first_data_block)) {
ext4_warning(sb, "won't resize using backup superblock at %llu", ext4_warning(sb, "won't resize using backup superblock at %llu",
(unsigned long long)EXT4_SB(sb)->s_sbh->b_blocknr); (unsigned long long)sbi->s_sbh->b_blocknr);
return -EPERM; return -EPERM;
} }
...@@ -79,7 +77,7 @@ int ext4_resize_begin(struct super_block *sb) ...@@ -79,7 +77,7 @@ int ext4_resize_begin(struct super_block *sb)
* We are not allowed to do online-resizing on a filesystem mounted * We are not allowed to do online-resizing on a filesystem mounted
* with error, because it can destroy the filesystem easily. * with error, because it can destroy the filesystem easily.
*/ */
if (EXT4_SB(sb)->s_mount_state & EXT4_ERROR_FS) { if (sbi->s_mount_state & EXT4_ERROR_FS) {
ext4_warning(sb, "There are errors in the filesystem, " ext4_warning(sb, "There are errors in the filesystem, "
"so online resizing is not allowed"); "so online resizing is not allowed");
return -EPERM; return -EPERM;
...@@ -91,7 +89,7 @@ int ext4_resize_begin(struct super_block *sb) ...@@ -91,7 +89,7 @@ int ext4_resize_begin(struct super_block *sb)
} }
if (test_and_set_bit_lock(EXT4_FLAGS_RESIZING, if (test_and_set_bit_lock(EXT4_FLAGS_RESIZING,
&EXT4_SB(sb)->s_ext4_flags)) &sbi->s_ext4_flags))
ret = -EBUSY; ret = -EBUSY;
return ret; return ret;
...@@ -106,18 +104,6 @@ int ext4_resize_end(struct super_block *sb, bool update_backups) ...@@ -106,18 +104,6 @@ int ext4_resize_end(struct super_block *sb, bool update_backups)
return 0; return 0;
} }
static ext4_group_t ext4_meta_bg_first_group(struct super_block *sb,
ext4_group_t group) {
return (group >> EXT4_DESC_PER_BLOCK_BITS(sb)) <<
EXT4_DESC_PER_BLOCK_BITS(sb);
}
static ext4_fsblk_t ext4_meta_bg_first_block_no(struct super_block *sb,
ext4_group_t group) {
group = ext4_meta_bg_first_group(sb, group);
return ext4_group_first_block_no(sb, group);
}
static ext4_grpblk_t ext4_group_overhead_blocks(struct super_block *sb, static ext4_grpblk_t ext4_group_overhead_blocks(struct super_block *sb,
ext4_group_t group) { ext4_group_t group) {
ext4_grpblk_t overhead; ext4_grpblk_t overhead;
...@@ -154,8 +140,9 @@ static int verify_group_input(struct super_block *sb, ...@@ -154,8 +140,9 @@ static int verify_group_input(struct super_block *sb,
overhead = ext4_group_overhead_blocks(sb, group); overhead = ext4_group_overhead_blocks(sb, group);
metaend = start + overhead; metaend = start + overhead;
input->free_clusters_count = free_blocks_count = free_blocks_count = input->blocks_count - 2 - overhead -
input->blocks_count - 2 - overhead - sbi->s_itb_per_group; sbi->s_itb_per_group;
input->free_clusters_count = EXT4_B2C(sbi, free_blocks_count);
if (test_opt(sb, DEBUG)) if (test_opt(sb, DEBUG))
printk(KERN_DEBUG "EXT4-fs: adding %s group %u: %u blocks " printk(KERN_DEBUG "EXT4-fs: adding %s group %u: %u blocks "
...@@ -460,8 +447,7 @@ static int set_flexbg_block_bitmap(struct super_block *sb, handle_t *handle, ...@@ -460,8 +447,7 @@ static int set_flexbg_block_bitmap(struct super_block *sb, handle_t *handle,
ext4_debug("mark clusters [%llu-%llu] used\n", first_cluster, ext4_debug("mark clusters [%llu-%llu] used\n", first_cluster,
last_cluster); last_cluster);
for (count2 = count; count > 0; for (; count > 0; count -= count2, first_cluster += count2) {
count -= count2, first_cluster += count2) {
ext4_fsblk_t start; ext4_fsblk_t start;
struct buffer_head *bh; struct buffer_head *bh;
ext4_group_t group; ext4_group_t group;
...@@ -560,13 +546,8 @@ static int setup_new_flex_group_blocks(struct super_block *sb, ...@@ -560,13 +546,8 @@ static int setup_new_flex_group_blocks(struct super_block *sb,
if (meta_bg == 0 && !ext4_bg_has_super(sb, group)) if (meta_bg == 0 && !ext4_bg_has_super(sb, group))
goto handle_itb; goto handle_itb;
if (meta_bg == 1) { if (meta_bg == 1)
ext4_group_t first_group; goto handle_itb;
first_group = ext4_meta_bg_first_group(sb, group);
if (first_group != group + 1 &&
first_group != group + EXT4_DESC_PER_BLOCK(sb) - 1)
goto handle_itb;
}
block = start + ext4_bg_has_super(sb, group); block = start + ext4_bg_has_super(sb, group);
/* Copy all of the GDT blocks into the backup in this group */ /* Copy all of the GDT blocks into the backup in this group */
...@@ -614,7 +595,7 @@ static int setup_new_flex_group_blocks(struct super_block *sb, ...@@ -614,7 +595,7 @@ static int setup_new_flex_group_blocks(struct super_block *sb,
} }
handle_itb: handle_itb:
/* Initialize group tables of the grop @group */ /* Initialize group tables of the group @group */
if (!(bg_flags[i] & EXT4_BG_INODE_ZEROED)) if (!(bg_flags[i] & EXT4_BG_INODE_ZEROED))
goto handle_bb; goto handle_bb;
...@@ -704,16 +685,14 @@ static int setup_new_flex_group_blocks(struct super_block *sb, ...@@ -704,16 +685,14 @@ static int setup_new_flex_group_blocks(struct super_block *sb,
block = start; block = start;
} }
if (count) { err = set_flexbg_block_bitmap(sb, handle,
err = set_flexbg_block_bitmap(sb, handle, flex_gd,
flex_gd, EXT4_B2C(sbi, start),
EXT4_B2C(sbi, start), EXT4_B2C(sbi,
EXT4_B2C(sbi, start + count
start + count - 1));
- 1)); if (err)
if (err) goto out;
goto out;
}
} }
out: out:
...@@ -952,7 +931,13 @@ static int add_new_gdb(handle_t *handle, struct inode *inode, ...@@ -952,7 +931,13 @@ static int add_new_gdb(handle_t *handle, struct inode *inode,
} }
/* /*
* add_new_gdb_meta_bg is the sister of add_new_gdb. * If there is no available space in the existing block group descriptors for
* the new block group and there are no reserved block group descriptors, then
* the meta_bg feature will get enabled, and es->s_first_meta_bg will get set
* to the first block group that is managed using meta_bg and s_first_meta_bg
* must be a multiple of EXT4_DESC_PER_BLOCK(sb).
* This function will be called when first group of meta_bg is added to bring
* new group descriptors block of new added meta_bg.
*/ */
static int add_new_gdb_meta_bg(struct super_block *sb, static int add_new_gdb_meta_bg(struct super_block *sb,
handle_t *handle, ext4_group_t group) { handle_t *handle, ext4_group_t group) {
...@@ -962,8 +947,8 @@ static int add_new_gdb_meta_bg(struct super_block *sb, ...@@ -962,8 +947,8 @@ static int add_new_gdb_meta_bg(struct super_block *sb,
unsigned long gdb_num = group / EXT4_DESC_PER_BLOCK(sb); unsigned long gdb_num = group / EXT4_DESC_PER_BLOCK(sb);
int err; int err;
gdblock = ext4_meta_bg_first_block_no(sb, group) + gdblock = ext4_group_first_block_no(sb, group) +
ext4_bg_has_super(sb, group); ext4_bg_has_super(sb, group);
gdb_bh = ext4_sb_bread(sb, gdblock, 0); gdb_bh = ext4_sb_bread(sb, gdblock, 0);
if (IS_ERR(gdb_bh)) if (IS_ERR(gdb_bh))
return PTR_ERR(gdb_bh); return PTR_ERR(gdb_bh);
...@@ -1087,9 +1072,6 @@ static int reserve_backup_gdb(handle_t *handle, struct inode *inode, ...@@ -1087,9 +1072,6 @@ static int reserve_backup_gdb(handle_t *handle, struct inode *inode,
for (i = 0; i < reserved_gdb; i++) { for (i = 0; i < reserved_gdb; i++) {
int err2; int err2;
data = (__le32 *)primary[i]->b_data; data = (__le32 *)primary[i]->b_data;
/* printk("reserving backup %lu[%u] = %lu\n",
primary[i]->b_blocknr, gdbackups,
blk + primary[i]->b_blocknr); */
data[gdbackups] = cpu_to_le32(blk + primary[i]->b_blocknr); data[gdbackups] = cpu_to_le32(blk + primary[i]->b_blocknr);
err2 = ext4_handle_dirty_metadata(handle, NULL, primary[i]); err2 = ext4_handle_dirty_metadata(handle, NULL, primary[i]);
if (!err) if (!err)
...@@ -1191,8 +1173,10 @@ static void update_backups(struct super_block *sb, sector_t blk_off, char *data, ...@@ -1191,8 +1173,10 @@ static void update_backups(struct super_block *sb, sector_t blk_off, char *data,
ext4_group_first_block_no(sb, group)); ext4_group_first_block_no(sb, group));
BUFFER_TRACE(bh, "get_write_access"); BUFFER_TRACE(bh, "get_write_access");
if ((err = ext4_journal_get_write_access(handle, sb, bh, if ((err = ext4_journal_get_write_access(handle, sb, bh,
EXT4_JTR_NONE))) EXT4_JTR_NONE))) {
brelse(bh);
break; break;
}
lock_buffer(bh); lock_buffer(bh);
memcpy(bh->b_data, data, size); memcpy(bh->b_data, data, size);
if (rest) if (rest)
...@@ -1601,7 +1585,8 @@ static int ext4_flex_group_add(struct super_block *sb, ...@@ -1601,7 +1585,8 @@ static int ext4_flex_group_add(struct super_block *sb,
int gdb_num_end = ((group + flex_gd->count - 1) / int gdb_num_end = ((group + flex_gd->count - 1) /
EXT4_DESC_PER_BLOCK(sb)); EXT4_DESC_PER_BLOCK(sb));
int meta_bg = ext4_has_feature_meta_bg(sb); int meta_bg = ext4_has_feature_meta_bg(sb);
sector_t old_gdb = 0; sector_t padding_blocks = meta_bg ? 0 : sbi->s_sbh->b_blocknr -
ext4_group_first_block_no(sb, 0);
update_backups(sb, ext4_group_first_block_no(sb, 0), update_backups(sb, ext4_group_first_block_no(sb, 0),
(char *)es, sizeof(struct ext4_super_block), 0); (char *)es, sizeof(struct ext4_super_block), 0);
...@@ -1610,11 +1595,8 @@ static int ext4_flex_group_add(struct super_block *sb, ...@@ -1610,11 +1595,8 @@ static int ext4_flex_group_add(struct super_block *sb,
gdb_bh = sbi_array_rcu_deref(sbi, s_group_desc, gdb_bh = sbi_array_rcu_deref(sbi, s_group_desc,
gdb_num); gdb_num);
if (old_gdb == gdb_bh->b_blocknr) update_backups(sb, gdb_bh->b_blocknr - padding_blocks,
continue; gdb_bh->b_data, gdb_bh->b_size, meta_bg);
update_backups(sb, gdb_bh->b_blocknr, gdb_bh->b_data,
gdb_bh->b_size, meta_bg);
old_gdb = gdb_bh->b_blocknr;
} }
} }
exit: exit:
...@@ -1980,9 +1962,7 @@ static int ext4_convert_meta_bg(struct super_block *sb, struct inode *inode) ...@@ -1980,9 +1962,7 @@ static int ext4_convert_meta_bg(struct super_block *sb, struct inode *inode)
errout: errout:
ret = ext4_journal_stop(handle); ret = ext4_journal_stop(handle);
if (!err) return err ? err : ret;
err = ret;
return ret;
invalid_resize_inode: invalid_resize_inode:
ext4_error(sb, "corrupted/inconsistent resize inode"); ext4_error(sb, "corrupted/inconsistent resize inode");
......
...@@ -768,7 +768,8 @@ static void update_super_work(struct work_struct *work) ...@@ -768,7 +768,8 @@ static void update_super_work(struct work_struct *work)
*/ */
if (!sb_rdonly(sbi->s_sb) && journal) { if (!sb_rdonly(sbi->s_sb) && journal) {
struct buffer_head *sbh = sbi->s_sbh; struct buffer_head *sbh = sbi->s_sbh;
bool call_notify_err; bool call_notify_err = false;
handle = jbd2_journal_start(journal, 1); handle = jbd2_journal_start(journal, 1);
if (IS_ERR(handle)) if (IS_ERR(handle))
goto write_directly; goto write_directly;
...@@ -6444,6 +6445,7 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb) ...@@ -6444,6 +6445,7 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb)
struct ext4_mount_options old_opts; struct ext4_mount_options old_opts;
ext4_group_t g; ext4_group_t g;
int err = 0; int err = 0;
int alloc_ctx;
#ifdef CONFIG_QUOTA #ifdef CONFIG_QUOTA
int enable_quota = 0; int enable_quota = 0;
int i, j; int i, j;
...@@ -6484,7 +6486,16 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb) ...@@ -6484,7 +6486,16 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb)
} }
/*
* Changing the DIOREAD_NOLOCK or DELALLOC mount options may cause
* two calls to ext4_should_dioread_nolock() to return inconsistent
* values, triggering WARN_ON in ext4_add_complete_io(). we grab
* here s_writepages_rwsem to avoid race between writepages ops and
* remount.
*/
alloc_ctx = ext4_writepages_down_write(sb);
ext4_apply_options(fc, sb); ext4_apply_options(fc, sb);
ext4_writepages_up_write(sb, alloc_ctx);
if ((old_opts.s_mount_opt & EXT4_MOUNT_JOURNAL_CHECKSUM) ^ if ((old_opts.s_mount_opt & EXT4_MOUNT_JOURNAL_CHECKSUM) ^
test_opt(sb, JOURNAL_CHECKSUM)) { test_opt(sb, JOURNAL_CHECKSUM)) {
...@@ -6702,6 +6713,8 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb) ...@@ -6702,6 +6713,8 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb)
if (sb_rdonly(sb) && !(old_sb_flags & SB_RDONLY) && if (sb_rdonly(sb) && !(old_sb_flags & SB_RDONLY) &&
sb_any_quota_suspended(sb)) sb_any_quota_suspended(sb))
dquot_resume(sb, -1); dquot_resume(sb, -1);
alloc_ctx = ext4_writepages_down_write(sb);
sb->s_flags = old_sb_flags; sb->s_flags = old_sb_flags;
sbi->s_mount_opt = old_opts.s_mount_opt; sbi->s_mount_opt = old_opts.s_mount_opt;
sbi->s_mount_opt2 = old_opts.s_mount_opt2; sbi->s_mount_opt2 = old_opts.s_mount_opt2;
...@@ -6710,6 +6723,8 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb) ...@@ -6710,6 +6723,8 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb)
sbi->s_commit_interval = old_opts.s_commit_interval; sbi->s_commit_interval = old_opts.s_commit_interval;
sbi->s_min_batch_time = old_opts.s_min_batch_time; sbi->s_min_batch_time = old_opts.s_min_batch_time;
sbi->s_max_batch_time = old_opts.s_max_batch_time; sbi->s_max_batch_time = old_opts.s_max_batch_time;
ext4_writepages_up_write(sb, alloc_ctx);
if (!test_opt(sb, BLOCK_VALIDITY) && sbi->s_system_blks) if (!test_opt(sb, BLOCK_VALIDITY) && sbi->s_system_blks)
ext4_release_system_zone(sb); ext4_release_system_zone(sb);
#ifdef CONFIG_QUOTA #ifdef CONFIG_QUOTA
......
...@@ -289,6 +289,8 @@ int jbd2_journal_recover(journal_t *journal) ...@@ -289,6 +289,8 @@ int jbd2_journal_recover(journal_t *journal)
journal_superblock_t * sb; journal_superblock_t * sb;
struct recovery_info info; struct recovery_info info;
errseq_t wb_err;
struct address_space *mapping;
memset(&info, 0, sizeof(info)); memset(&info, 0, sizeof(info));
sb = journal->j_superblock; sb = journal->j_superblock;
...@@ -306,6 +308,9 @@ int jbd2_journal_recover(journal_t *journal) ...@@ -306,6 +308,9 @@ int jbd2_journal_recover(journal_t *journal)
return 0; return 0;
} }
wb_err = 0;
mapping = journal->j_fs_dev->bd_inode->i_mapping;
errseq_check_and_advance(&mapping->wb_err, &wb_err);
err = do_one_pass(journal, &info, PASS_SCAN); err = do_one_pass(journal, &info, PASS_SCAN);
if (!err) if (!err)
err = do_one_pass(journal, &info, PASS_REVOKE); err = do_one_pass(journal, &info, PASS_REVOKE);
...@@ -327,6 +332,9 @@ int jbd2_journal_recover(journal_t *journal) ...@@ -327,6 +332,9 @@ int jbd2_journal_recover(journal_t *journal)
jbd2_journal_clear_revoke(journal); jbd2_journal_clear_revoke(journal);
err2 = sync_blockdev(journal->j_fs_dev); err2 = sync_blockdev(journal->j_fs_dev);
if (!err)
err = err2;
err2 = errseq_check_and_advance(&mapping->wb_err, &wb_err);
if (!err) if (!err)
err = err2; err = err2;
/* Make sure all replayed data is on permanent storage */ /* Make sure all replayed data is on permanent storage */
...@@ -632,7 +640,7 @@ static int do_one_pass(journal_t *journal, ...@@ -632,7 +640,7 @@ static int do_one_pass(journal_t *journal,
success = err; success = err;
printk(KERN_ERR printk(KERN_ERR
"JBD2: IO error %d recovering " "JBD2: IO error %d recovering "
"block %ld in log\n", "block %lu in log\n",
err, io_block); err, io_block);
} else { } else {
unsigned long long blocknr; unsigned long long blocknr;
...@@ -661,7 +669,8 @@ static int do_one_pass(journal_t *journal, ...@@ -661,7 +669,8 @@ static int do_one_pass(journal_t *journal,
printk(KERN_ERR "JBD2: Invalid " printk(KERN_ERR "JBD2: Invalid "
"checksum recovering " "checksum recovering "
"data block %llu in " "data block %llu in "
"log\n", blocknr); "journal block %lu\n",
blocknr, io_block);
block_error = 1; block_error = 1;
goto skip_write; goto skip_write;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment