gfs2 fixes

- Make sure to initialize the filesystem work queues before registering
   the filesystem; this prevents them from being used uninitialized.
 
 - On filesystem withdraw: prevent a a double iput() and immediately
   reject pending locking requests that can no longer succeed.
 
 - Use TRY lock in gfs2_inode_lookup() to prevent a rare glock hang
   during evict.
 
 - During filesystem mount, explicitly make sure that the sb_bsize and
   sb_bsize_shift super block fields are consistent with each other.
   This prevents messy error messages during fuzz testing.
 
 - Switch from strlcpy to strscpy.
 -----BEGIN PGP SIGNATURE-----
 
 iQJIBAABCAAyFiEEJZs3krPW0xkhLMTc1b+f6wMTZToFAmNCoXgUHGFncnVlbmJh
 QHJlZGhhdC5jb20ACgkQ1b+f6wMTZTph4xAAg+SnmCkvh07ww1CLLGxPWFK41toZ
 zBYbwXb3h4qeFXQ4XSI4ZYnAjWTwIK0yjz0lp6pRnEoh0ih+IuEuIZLVls/ggEn6
 /BxRDKWHE6fJwkemsB/V8XAu3UTemG0BOo6kkoYYnkjd5qZuqcqMkUEvc4xwsIxu
 2tfzKfPoIgn95HVjiQboViwEEa/HzwaxxudWaVoa/Gn54wWxFfrQplGoYftS/8ah
 5d0ho1hOLPXJNNeKUXVnu8buKs3Oh59OpMXoI4gzvUfh2YWYqm5giP0Dwnv5qf6F
 NeMFqUxUKoMKL5nofkWI6FqRoWLbmattrL8nu0Fpn/tlzuMivlI25PB4k37R3tGx
 GILncsnLOMxzUx69aTnZLyn/8RWkgiHaQ3Nm4OJPTENd7/V7o/5O1BRInRzdeHdN
 LEfD6Fsi+muK5V6aIe1LIELSB0i/C7753pF0BWQ93KItE6hpufTct9YPXgmn+k/v
 gxHy+laPWhKXdPriBnO6RyeV7DJr/Ai+7ga3e35UxRqr9DvIpltNUvODqxyXPymS
 EIC45zQD6fVLOIFCxwLPrTbc2isrcvu+TYlcByaUVSc5weK3ZbdWz4Pm3Wd59K3n
 7oJRx6oWMemVcTgUsZQ7Lr7lPlM47O9kiUJy9RNCeefPChQ/5+OgxfKOcdqQZK8E
 z4a3efQ5APqp308=
 =h/LV
 -----END PGP SIGNATURE-----

Merge tag 'gfs2-v6.0-rc2-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2

Pull gfs2 updates from Andreas Gruenbacher:

 - Make sure to initialize the filesystem work queues before registering
   the filesystem; this prevents them from being used uninitialized.

 - On filesystem withdraw: prevent a a double iput() and immediately
   reject pending locking requests that can no longer succeed.

 - Use TRY lock in gfs2_inode_lookup() to prevent a rare glock hang
   during evict.

 - During filesystem mount, explicitly make sure that the sb_bsize and
   sb_bsize_shift super block fields are consistent with each other.
   This prevents messy error messages during fuzz testing.

 - Switch from strlcpy to strscpy.

* tag 'gfs2-v6.0-rc2-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2:
  gfs2: Register fs after creating workqueues
  gfs2: Check sb_bsize_shift after reading superblock
  gfs2: Switch from strlcpy to strscpy
  gfs2: Clear flags when withdraw prevents xmote
  gfs2: Dequeue waiters when withdrawn
  gfs2: Prevent double iput for journal on error
  gfs2: Use TRY lock in gfs2_inode_lookup for UNLINKED inodes
This commit is contained in:
Linus Torvalds 2022-10-10 20:08:52 -07:00
commit 4875d2ffb0
6 changed files with 77 additions and 25 deletions

View File

@ -59,6 +59,8 @@ typedef void (*glock_examiner) (struct gfs2_glock * gl);
static void do_xmote(struct gfs2_glock *gl, struct gfs2_holder *gh, unsigned int target);
static void __gfs2_glock_dq(struct gfs2_holder *gh);
static void handle_callback(struct gfs2_glock *gl, unsigned int state,
unsigned long delay, bool remote);
static struct dentry *gfs2_root;
static struct workqueue_struct *glock_workqueue;
@ -730,7 +732,8 @@ static bool is_system_glock(struct gfs2_glock *gl)
*
*/
static void do_xmote(struct gfs2_glock *gl, struct gfs2_holder *gh, unsigned int target)
static void do_xmote(struct gfs2_glock *gl, struct gfs2_holder *gh,
unsigned int target)
__releases(&gl->gl_lockref.lock)
__acquires(&gl->gl_lockref.lock)
{
@ -741,7 +744,8 @@ __acquires(&gl->gl_lockref.lock)
if (target != LM_ST_UNLOCKED && glock_blocked_by_withdraw(gl) &&
gh && !(gh->gh_flags & LM_FLAG_NOEXP))
return;
goto skip_inval;
lck_flags &= (LM_FLAG_TRY | LM_FLAG_TRY_1CB | LM_FLAG_NOEXP |
LM_FLAG_PRIORITY);
GLOCK_BUG_ON(gl, gl->gl_state == target);
@ -826,6 +830,20 @@ skip_inval:
(target != LM_ST_UNLOCKED ||
test_bit(SDF_WITHDRAW_RECOVERY, &sdp->sd_flags))) {
if (!is_system_glock(gl)) {
handle_callback(gl, LM_ST_UNLOCKED, 0, false); /* sets demote */
/*
* Ordinarily, we would call dlm and its callback would call
* finish_xmote, which would call state_change() to the new state.
* Since we withdrew, we won't call dlm, so call state_change
* manually, but to the UNLOCKED state we desire.
*/
state_change(gl, LM_ST_UNLOCKED);
/*
* We skip telling dlm to do the locking, so we won't get a
* reply that would otherwise clear GLF_LOCK. So we clear it here.
*/
clear_bit(GLF_LOCK, &gl->gl_flags);
clear_bit(GLF_DEMOTE_IN_PROGRESS, &gl->gl_flags);
gfs2_glock_queue_work(gl, GL_GLOCK_DFT_HOLD);
goto out;
} else {
@ -1018,16 +1036,18 @@ static void delete_work_func(struct work_struct *work)
if (gfs2_queue_delete_work(gl, 5 * HZ))
return;
}
goto out;
}
inode = gfs2_lookup_by_inum(sdp, no_addr, gl->gl_no_formal_ino,
GFS2_BLKST_UNLINKED);
if (!IS_ERR_OR_NULL(inode)) {
if (IS_ERR(inode)) {
if (PTR_ERR(inode) == -EAGAIN &&
(gfs2_queue_delete_work(gl, 5 * HZ)))
return;
} else {
d_prune_aliases(inode);
iput(inode);
}
out:
gfs2_glock_put(gl);
}
@ -2194,6 +2214,20 @@ static void dump_glock_func(struct gfs2_glock *gl)
dump_glock(NULL, gl, true);
}
static void withdraw_dq(struct gfs2_glock *gl)
{
spin_lock(&gl->gl_lockref.lock);
if (!__lockref_is_dead(&gl->gl_lockref) &&
glock_blocked_by_withdraw(gl))
do_error(gl, LM_OUT_ERROR); /* remove pending waiters */
spin_unlock(&gl->gl_lockref.lock);
}
void gfs2_gl_dq_holders(struct gfs2_sbd *sdp)
{
glock_hash_walk(withdraw_dq, sdp);
}
/**
* gfs2_gl_hash_clear - Empty out the glock hash table
* @sdp: the filesystem

View File

@ -274,6 +274,7 @@ extern void gfs2_cancel_delete_work(struct gfs2_glock *gl);
extern bool gfs2_delete_work_queued(const struct gfs2_glock *gl);
extern void gfs2_flush_delete_work(struct gfs2_sbd *sdp);
extern void gfs2_gl_hash_clear(struct gfs2_sbd *sdp);
extern void gfs2_gl_dq_holders(struct gfs2_sbd *sdp);
extern void gfs2_glock_thaw(struct gfs2_sbd *sdp);
extern void gfs2_glock_add_to_lru(struct gfs2_glock *gl);
extern void gfs2_glock_free(struct gfs2_glock *gl);

View File

@ -130,6 +130,7 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type,
if (inode->i_state & I_NEW) {
struct gfs2_sbd *sdp = GFS2_SB(inode);
struct gfs2_glock *io_gl;
int extra_flags = 0;
error = gfs2_glock_get(sdp, no_addr, &gfs2_inode_glops, CREATE,
&ip->i_gl);
@ -141,9 +142,12 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type,
if (unlikely(error))
goto fail;
if (blktype != GFS2_BLKST_UNLINKED)
if (blktype == GFS2_BLKST_UNLINKED)
extra_flags |= LM_FLAG_TRY;
else
gfs2_cancel_delete_work(io_gl);
error = gfs2_glock_nq_init(io_gl, LM_ST_SHARED, GL_EXACT,
error = gfs2_glock_nq_init(io_gl, LM_ST_SHARED,
GL_EXACT | extra_flags,
&ip->i_iopen_gh);
gfs2_glock_put(io_gl);
if (unlikely(error))
@ -210,6 +214,8 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type,
return inode;
fail:
if (error == GLR_TRYFAILED)
error = -EAGAIN;
if (gfs2_holder_initialized(&ip->i_iopen_gh))
gfs2_glock_dq_uninit(&ip->i_iopen_gh);
if (gfs2_holder_initialized(&i_gh))

View File

@ -151,14 +151,6 @@ static int __init init_gfs2_fs(void)
if (error)
goto fail_shrinker;
error = register_filesystem(&gfs2_fs_type);
if (error)
goto fail_fs1;
error = register_filesystem(&gfs2meta_fs_type);
if (error)
goto fail_fs2;
error = -ENOMEM;
gfs_recovery_wq = alloc_workqueue("gfs_recovery",
WQ_MEM_RECLAIM | WQ_FREEZABLE, 0);
@ -180,11 +172,23 @@ static int __init init_gfs2_fs(void)
goto fail_mempool;
gfs2_register_debugfs();
error = register_filesystem(&gfs2_fs_type);
if (error)
goto fail_fs1;
error = register_filesystem(&gfs2meta_fs_type);
if (error)
goto fail_fs2;
pr_info("GFS2 installed\n");
return 0;
fail_fs2:
unregister_filesystem(&gfs2_fs_type);
fail_fs1:
mempool_destroy(gfs2_page_pool);
fail_mempool:
destroy_workqueue(gfs2_freeze_wq);
fail_wq3:
@ -192,10 +196,6 @@ fail_wq3:
fail_wq2:
destroy_workqueue(gfs_recovery_wq);
fail_wq1:
unregister_filesystem(&gfs2meta_fs_type);
fail_fs2:
unregister_filesystem(&gfs2_fs_type);
fail_fs1:
unregister_shrinker(&gfs2_qd_shrinker);
fail_shrinker:
kmem_cache_destroy(gfs2_trans_cachep);

View File

@ -178,7 +178,10 @@ static int gfs2_check_sb(struct gfs2_sbd *sdp, int silent)
pr_warn("Invalid block size\n");
return -EINVAL;
}
if (sb->sb_bsize_shift != ffs(sb->sb_bsize) - 1) {
pr_warn("Invalid block size shift\n");
return -EINVAL;
}
return 0;
}
@ -381,8 +384,10 @@ static int init_names(struct gfs2_sbd *sdp, int silent)
if (!table[0])
table = sdp->sd_vfs->s_id;
strlcpy(sdp->sd_proto_name, proto, GFS2_FSNAME_LEN);
strlcpy(sdp->sd_table_name, table, GFS2_FSNAME_LEN);
BUILD_BUG_ON(GFS2_LOCKNAME_LEN > GFS2_FSNAME_LEN);
strscpy(sdp->sd_proto_name, proto, GFS2_LOCKNAME_LEN);
strscpy(sdp->sd_table_name, table, GFS2_LOCKNAME_LEN);
table = sdp->sd_table_name;
while ((table = strchr(table, '/')))
@ -1439,13 +1444,13 @@ static int gfs2_parse_param(struct fs_context *fc, struct fs_parameter *param)
switch (o) {
case Opt_lockproto:
strlcpy(args->ar_lockproto, param->string, GFS2_LOCKNAME_LEN);
strscpy(args->ar_lockproto, param->string, GFS2_LOCKNAME_LEN);
break;
case Opt_locktable:
strlcpy(args->ar_locktable, param->string, GFS2_LOCKNAME_LEN);
strscpy(args->ar_locktable, param->string, GFS2_LOCKNAME_LEN);
break;
case Opt_hostdata:
strlcpy(args->ar_hostdata, param->string, GFS2_LOCKNAME_LEN);
strscpy(args->ar_hostdata, param->string, GFS2_LOCKNAME_LEN);
break;
case Opt_spectator:
args->ar_spectator = 1;

View File

@ -164,6 +164,11 @@ static void signal_our_withdraw(struct gfs2_sbd *sdp)
}
if (!ret)
gfs2_make_fs_ro(sdp);
/*
* Dequeue any pending non-system glock holders that can no
* longer be granted because the file system is withdrawn.
*/
gfs2_gl_dq_holders(sdp);
gfs2_freeze_unlock(&freeze_gh);
}
@ -204,6 +209,7 @@ static void signal_our_withdraw(struct gfs2_sbd *sdp)
* exception code in glock_dq.
*/
iput(inode);
sdp->sd_jdesc->jd_inode = NULL;
/*
* Wait until the journal inode's glock is freed. This allows try locks
* on other nodes to be successful, otherwise we remain the owner of