Skip to content

Commit

Permalink
GFS2: Fix inode deallocation race
Browse files Browse the repository at this point in the history
This area of the code has always been a bit delicate due to the
subtleties of lock ordering. The problem is that for "normal"
alloc/dealloc, we always grab the inode locks first and the rgrp lock
later.

In order to ensure no races in looking up the unlinked, but still
allocated inodes, we need to hold the rgrp lock when we do the lookup,
which means that we can't take the inode glock.

The solution is to borrow the technique already used by NFS to solve
what is essentially the same problem (given an inode number, look up
the inode carefully, checking that it really is in the expected
state).

We cannot do that directly from the allocation code (lock ordering
again) so we give the job to the pre-existing delete workqueue and
carry on with the allocation as normal.

If we find there is no space, we do a journal flush (required anyway
if space from a deallocation is to be released) which should block
against the pending deallocations, so we should always get the space
back.

Signed-off-by: Steven Whitehouse <swhiteho@redhat.com>
  • Loading branch information
Steven Whitehouse committed Nov 15, 2010
1 parent 0143832 commit 044b941
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 216 deletions.
46 changes: 4 additions & 42 deletions fs/gfs2/export.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,8 @@ static struct dentry *gfs2_get_dentry(struct super_block *sb,
struct gfs2_inum_host *inum)
{
struct gfs2_sbd *sdp = sb->s_fs_info;
struct gfs2_holder i_gh;
struct inode *inode;
struct dentry *dentry;
int error;

inode = gfs2_ilookup(sb, inum->no_addr);
if (inode) {
Expand All @@ -152,52 +150,16 @@ static struct dentry *gfs2_get_dentry(struct super_block *sb,
goto out_inode;
}

error = gfs2_glock_nq_num(sdp, inum->no_addr, &gfs2_inode_glops,
LM_ST_SHARED, LM_FLAG_ANY, &i_gh);
if (error)
return ERR_PTR(error);

error = gfs2_check_blk_type(sdp, inum->no_addr, GFS2_BLKST_DINODE);
if (error)
goto fail;

inode = gfs2_inode_lookup(sb, DT_UNKNOWN, inum->no_addr, 0);
if (IS_ERR(inode)) {
error = PTR_ERR(inode);
goto fail;
}

error = gfs2_inode_refresh(GFS2_I(inode));
if (error) {
iput(inode);
goto fail;
}

/* Pick up the works we bypass in gfs2_inode_lookup */
if (inode->i_state & I_NEW)
gfs2_set_iop(inode);

if (GFS2_I(inode)->i_no_formal_ino != inum->no_formal_ino) {
iput(inode);
goto fail;
}

error = -EIO;
if (GFS2_I(inode)->i_diskflags & GFS2_DIF_SYSTEM) {
iput(inode);
goto fail;
}

gfs2_glock_dq_uninit(&i_gh);
inode = gfs2_lookup_by_inum(sdp, inum->no_addr, &inum->no_formal_ino,
GFS2_BLKST_DINODE);
if (IS_ERR(inode))
return ERR_CAST(inode);

out_inode:
dentry = d_obtain_alias(inode);
if (!IS_ERR(dentry))
dentry->d_op = &gfs2_dops;
return dentry;
fail:
gfs2_glock_dq_uninit(&i_gh);
return ERR_PTR(error);
}

static struct dentry *gfs2_fh_to_dentry(struct super_block *sb, struct fid *fid,
Expand Down
21 changes: 10 additions & 11 deletions fs/gfs2/glock.c
Original file line number Diff line number Diff line change
Expand Up @@ -686,21 +686,20 @@ static void delete_work_func(struct work_struct *work)
{
struct gfs2_glock *gl = container_of(work, struct gfs2_glock, gl_delete);
struct gfs2_sbd *sdp = gl->gl_sbd;
struct gfs2_inode *ip = NULL;
struct gfs2_inode *ip;
struct inode *inode;
u64 no_addr = 0;
u64 no_addr = gl->gl_name.ln_number;

ip = gl->gl_object;
/* Note: Unsafe to dereference ip as we don't hold right refs/locks */

spin_lock(&gl->gl_spin);
ip = (struct gfs2_inode *)gl->gl_object;
if (ip)
no_addr = ip->i_no_addr;
spin_unlock(&gl->gl_spin);
if (ip) {
inode = gfs2_ilookup(sdp->sd_vfs, no_addr);
if (inode) {
d_prune_aliases(inode);
iput(inode);
}
else
inode = gfs2_lookup_by_inum(sdp, no_addr, NULL, GFS2_BLKST_UNLINKED);
if (inode && !IS_ERR(inode)) {
d_prune_aliases(inode);
iput(inode);
}
gfs2_glock_put(gl);
}
Expand Down
152 changes: 35 additions & 117 deletions fs/gfs2/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,49 +73,6 @@ static struct inode *gfs2_iget(struct super_block *sb, u64 no_addr)
return iget5_locked(sb, hash, iget_test, iget_set, &no_addr);
}

struct gfs2_skip_data {
u64 no_addr;
int skipped;
};

static int iget_skip_test(struct inode *inode, void *opaque)
{
struct gfs2_inode *ip = GFS2_I(inode);
struct gfs2_skip_data *data = opaque;

if (ip->i_no_addr == data->no_addr) {
if (inode->i_state & (I_FREEING|I_WILL_FREE)){
data->skipped = 1;
return 0;
}
return 1;
}
return 0;
}

static int iget_skip_set(struct inode *inode, void *opaque)
{
struct gfs2_inode *ip = GFS2_I(inode);
struct gfs2_skip_data *data = opaque;

if (data->skipped)
return 1;
inode->i_ino = (unsigned long)(data->no_addr);
ip->i_no_addr = data->no_addr;
return 0;
}

static struct inode *gfs2_iget_skip(struct super_block *sb,
u64 no_addr)
{
struct gfs2_skip_data data;
unsigned long hash = (unsigned long)no_addr;

data.no_addr = no_addr;
data.skipped = 0;
return iget5_locked(sb, hash, iget_skip_test, iget_skip_set, &data);
}

/**
* GFS2 lookup code fills in vfs inode contents based on info obtained
* from directory entry inside gfs2_inode_lookup(). This has caused issues
Expand Down Expand Up @@ -243,93 +200,54 @@ struct inode *gfs2_inode_lookup(struct super_block *sb,
return ERR_PTR(error);
}

/**
* gfs2_process_unlinked_inode - Lookup an unlinked inode for reclamation
* and try to reclaim it by doing iput.
*
* This function assumes no rgrp locks are currently held.
*
* @sb: The super block
* no_addr: The inode number
*
*/

void gfs2_process_unlinked_inode(struct super_block *sb, u64 no_addr)
struct inode *gfs2_lookup_by_inum(struct gfs2_sbd *sdp, u64 no_addr,
u64 *no_formal_ino, unsigned int blktype)
{
struct gfs2_sbd *sdp;
struct gfs2_inode *ip;
struct gfs2_glock *io_gl = NULL;
int error;
struct gfs2_holder gh;
struct super_block *sb = sdp->sd_vfs;
struct gfs2_holder i_gh;
struct inode *inode;
int error;

inode = gfs2_iget_skip(sb, no_addr);

if (!inode)
return;

/* If it's not a new inode, someone's using it, so leave it alone. */
if (!(inode->i_state & I_NEW)) {
iput(inode);
return;
}

ip = GFS2_I(inode);
sdp = GFS2_SB(inode);
ip->i_no_formal_ino = -1;
error = gfs2_glock_nq_num(sdp, no_addr, &gfs2_inode_glops,
LM_ST_SHARED, LM_FLAG_ANY, &i_gh);
if (error)
return ERR_PTR(error);

error = gfs2_glock_get(sdp, no_addr, &gfs2_inode_glops, CREATE, &ip->i_gl);
if (unlikely(error))
error = gfs2_check_blk_type(sdp, no_addr, blktype);
if (error)
goto fail;
ip->i_gl->gl_object = ip;

error = gfs2_glock_get(sdp, no_addr, &gfs2_iopen_glops, CREATE, &io_gl);
if (unlikely(error))
goto fail_put;

set_bit(GIF_INVALID, &ip->i_flags);
error = gfs2_glock_nq_init(io_gl, LM_ST_SHARED, LM_FLAG_TRY | GL_EXACT,
&ip->i_iopen_gh);
if (unlikely(error))
goto fail_iopen;
inode = gfs2_inode_lookup(sb, DT_UNKNOWN, no_addr, 0);
if (IS_ERR(inode))
goto fail;

ip->i_iopen_gh.gh_gl->gl_object = ip;
gfs2_glock_put(io_gl);
io_gl = NULL;
error = gfs2_inode_refresh(GFS2_I(inode));
if (error)
goto fail_iput;

inode->i_mode = DT2IF(DT_UNKNOWN);
/* Pick up the works we bypass in gfs2_inode_lookup */
if (inode->i_state & I_NEW)
gfs2_set_iop(inode);

/*
* We must read the inode in order to work out its type in
* this case. Note that this doesn't happen often as we normally
* know the type beforehand. This code path only occurs during
* unlinked inode recovery (where it is safe to do this glock,
* which is not true in the general case).
*/
error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, LM_FLAG_TRY,
&gh);
if (unlikely(error))
goto fail_glock;
/* Two extra checks for NFS only */
if (no_formal_ino) {
error = -ESTALE;
if (GFS2_I(inode)->i_no_formal_ino != *no_formal_ino)
goto fail_iput;

/* Inode is now uptodate */
gfs2_glock_dq_uninit(&gh);
gfs2_set_iop(inode);
error = -EIO;
if (GFS2_I(inode)->i_diskflags & GFS2_DIF_SYSTEM)
goto fail_iput;

/* The iput will cause it to be deleted. */
iput(inode);
return;
error = 0;
}

fail_glock:
gfs2_glock_dq(&ip->i_iopen_gh);
fail_iopen:
if (io_gl)
gfs2_glock_put(io_gl);
fail_put:
ip->i_gl->gl_object = NULL;
gfs2_glock_put(ip->i_gl);
fail:
iget_failed(inode);
return;
gfs2_glock_dq_uninit(&i_gh);
return error ? ERR_PTR(error) : inode;
fail_iput:
iput(inode);
goto fail;
}

static int gfs2_dinode_in(struct gfs2_inode *ip, const void *buf)
Expand Down
4 changes: 3 additions & 1 deletion fs/gfs2/inode.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ static inline int gfs2_check_internal_file_size(struct inode *inode,
extern void gfs2_set_iop(struct inode *inode);
extern struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned type,
u64 no_addr, u64 no_formal_ino);
extern void gfs2_process_unlinked_inode(struct super_block *sb, u64 no_addr);
extern struct inode *gfs2_lookup_by_inum(struct gfs2_sbd *sdp, u64 no_addr,
u64 *no_formal_ino,
unsigned int blktype);
extern struct inode *gfs2_ilookup(struct super_block *sb, u64 no_addr);

extern int gfs2_inode_refresh(struct gfs2_inode *ip);
Expand Down
Loading

0 comments on commit 044b941

Please sign in to comment.