Skip to content

Commit

Permalink
NFSv4: Fix a potential CLOSE race
Browse files Browse the repository at this point in the history
 Once the state_owner and lock_owner semaphores get removed, it will be
 possible for other OPEN requests to reopen the same file if they have
 lower sequence ids than our CLOSE call.
 This patch ensures that we recheck the file state once
 nfs_wait_on_sequence() has completed waiting.

 Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
  • Loading branch information
Trond Myklebust authored and Trond Myklebust committed Oct 18, 2005
1 parent cee54fc commit 9512135
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 45 deletions.
110 changes: 77 additions & 33 deletions fs/nfs/nfs4proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,21 @@ static void update_changeattr(struct inode *inode, struct nfs4_change_info *cinf
nfsi->change_attr = cinfo->after;
}

/* Helper for asynchronous RPC calls */
static int nfs4_call_async(struct rpc_clnt *clnt, rpc_action tk_begin,
rpc_action tk_exit, void *calldata)
{
struct rpc_task *task;

if (!(task = rpc_new_task(clnt, tk_exit, RPC_TASK_ASYNC)))
return -ENOMEM;

task->tk_calldata = calldata;
task->tk_action = tk_begin;
rpc_execute(task);
return 0;
}

static void update_open_stateid(struct nfs4_state *state, nfs4_stateid *stateid, int open_flags)
{
struct inode *inode = state->inode;
Expand Down Expand Up @@ -810,11 +825,24 @@ struct nfs4_closedata {
struct nfs_closeres res;
};

static void nfs4_free_closedata(struct nfs4_closedata *calldata)
{
struct nfs4_state *state = calldata->state;
struct nfs4_state_owner *sp = state->owner;
struct nfs_server *server = NFS_SERVER(calldata->inode);

nfs4_put_open_state(calldata->state);
nfs_free_seqid(calldata->arg.seqid);
up(&sp->so_sema);
nfs4_put_state_owner(sp);
up_read(&server->nfs4_state->cl_sem);
kfree(calldata);
}

static void nfs4_close_done(struct rpc_task *task)
{
struct nfs4_closedata *calldata = (struct nfs4_closedata *)task->tk_calldata;
struct nfs4_state *state = calldata->state;
struct nfs4_state_owner *sp = state->owner;
struct nfs_server *server = NFS_SERVER(calldata->inode);

/* hmm. we are done with the inode, and in the process of freeing
Expand All @@ -838,25 +866,46 @@ static void nfs4_close_done(struct rpc_task *task)
}
}
state->state = calldata->arg.open_flags;
nfs4_put_open_state(state);
nfs_free_seqid(calldata->arg.seqid);
up(&sp->so_sema);
nfs4_put_state_owner(sp);
up_read(&server->nfs4_state->cl_sem);
kfree(calldata);
nfs4_free_closedata(calldata);
}

static inline int nfs4_close_call(struct rpc_clnt *clnt, struct nfs4_closedata *calldata)
static void nfs4_close_begin(struct rpc_task *task)
{
struct nfs4_closedata *calldata = (struct nfs4_closedata *)task->tk_calldata;
struct nfs4_state *state = calldata->state;
struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CLOSE],
.rpc_argp = &calldata->arg,
.rpc_resp = &calldata->res,
.rpc_cred = calldata->state->owner->so_cred,
.rpc_cred = state->owner->so_cred,
};
if (calldata->arg.open_flags != 0)
int mode = 0;
int status;

status = nfs_wait_on_sequence(calldata->arg.seqid, task);
if (status != 0)
return;
/* Don't reorder reads */
smp_rmb();
/* Recalculate the new open mode in case someone reopened the file
* while we were waiting in line to be scheduled.
*/
if (state->nreaders != 0)
mode |= FMODE_READ;
if (state->nwriters != 0)
mode |= FMODE_WRITE;
if (test_bit(NFS_DELEGATED_STATE, &state->flags))
state->state = mode;
if (mode == state->state) {
nfs4_free_closedata(calldata);
task->tk_exit = NULL;
rpc_exit(task, 0);
return;
}
if (mode != 0)
msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_DOWNGRADE];
return rpc_call_async(clnt, &msg, 0, nfs4_close_done, calldata);
calldata->arg.open_flags = mode;
rpc_call_setup(task, &msg, 0);
}

/*
Expand All @@ -873,35 +922,30 @@ static inline int nfs4_close_call(struct rpc_clnt *clnt, struct nfs4_closedata *
int nfs4_do_close(struct inode *inode, struct nfs4_state *state, mode_t mode)
{
struct nfs4_closedata *calldata;
int status;
int status = -ENOMEM;

/* Tell caller we're done */
if (test_bit(NFS_DELEGATED_STATE, &state->flags)) {
state->state = mode;
return 0;
}
calldata = (struct nfs4_closedata *)kmalloc(sizeof(*calldata), GFP_KERNEL);
calldata = kmalloc(sizeof(*calldata), GFP_KERNEL);
if (calldata == NULL)
return -ENOMEM;
goto out;
calldata->inode = inode;
calldata->state = state;
calldata->arg.fh = NFS_FH(inode);
calldata->arg.stateid = &state->stateid;
/* Serialization for the sequence id */
calldata->arg.seqid = nfs_alloc_seqid(&state->owner->so_seqid);
if (calldata->arg.seqid == NULL) {
kfree(calldata);
return -ENOMEM;
}
calldata->arg.open_flags = mode;
memcpy(&calldata->arg.stateid, &state->stateid,
sizeof(calldata->arg.stateid));
status = nfs4_close_call(NFS_SERVER(inode)->client, calldata);
/*
* Return -EINPROGRESS on success in order to indicate to the
* caller that an asynchronous RPC call has been launched, and
* that it will release the semaphores on completion.
*/
return (status == 0) ? -EINPROGRESS : status;
if (calldata->arg.seqid == NULL)
goto out_free_calldata;

status = nfs4_call_async(NFS_SERVER(inode)->client, nfs4_close_begin,
nfs4_close_done, calldata);
if (status == 0)
goto out;

nfs_free_seqid(calldata->arg.seqid);
out_free_calldata:
kfree(calldata);
out:
return status;
}

struct inode *
Expand Down
6 changes: 5 additions & 1 deletion fs/nfs/nfs4state.c
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,11 @@ void nfs4_close_state(struct nfs4_state *state, mode_t mode)
newstate |= FMODE_WRITE;
if (state->state == newstate)
goto out;
if (nfs4_do_close(inode, state, newstate) == -EINPROGRESS)
if (test_bit(NFS_DELEGATED_STATE, &state->flags)) {
state->state = newstate;
goto out;
}
if (nfs4_do_close(inode, state, newstate) == 0)
return;
}
out:
Expand Down
14 changes: 4 additions & 10 deletions fs/nfs/nfs4xdr.c
Original file line number Diff line number Diff line change
Expand Up @@ -602,10 +602,10 @@ static int encode_close(struct xdr_stream *xdr, const struct nfs_closeargs *arg)
{
uint32_t *p;

RESERVE_SPACE(8+sizeof(arg->stateid.data));
RESERVE_SPACE(8+sizeof(arg->stateid->data));
WRITE32(OP_CLOSE);
WRITE32(arg->seqid->sequence->counter);
WRITEMEM(arg->stateid.data, sizeof(arg->stateid.data));
WRITEMEM(arg->stateid->data, sizeof(arg->stateid->data));

return 0;
}
Expand Down Expand Up @@ -950,9 +950,9 @@ static int encode_open_downgrade(struct xdr_stream *xdr, const struct nfs_closea
{
uint32_t *p;

RESERVE_SPACE(8+sizeof(arg->stateid.data));
RESERVE_SPACE(8+sizeof(arg->stateid->data));
WRITE32(OP_OPEN_DOWNGRADE);
WRITEMEM(arg->stateid.data, sizeof(arg->stateid.data));
WRITEMEM(arg->stateid->data, sizeof(arg->stateid->data));
WRITE32(arg->seqid->sequence->counter);
encode_share_access(xdr, arg->open_flags);
return 0;
Expand Down Expand Up @@ -1416,9 +1416,6 @@ static int nfs4_xdr_enc_close(struct rpc_rqst *req, uint32_t *p, struct nfs_clos
};
int status;

status = nfs_wait_on_sequence(args->seqid, req->rq_task);
if (status != 0)
goto out;
xdr_init_encode(&xdr, &req->rq_snd_buf, p);
encode_compound_hdr(&xdr, &hdr);
status = encode_putfh(&xdr, args->fh);
Expand Down Expand Up @@ -1518,9 +1515,6 @@ static int nfs4_xdr_enc_open_downgrade(struct rpc_rqst *req, uint32_t *p, struct
};
int status;

status = nfs_wait_on_sequence(args->seqid, req->rq_task);
if (status != 0)
goto out;
xdr_init_encode(&xdr, &req->rq_snd_buf, p);
encode_compound_hdr(&xdr, &hdr);
status = encode_putfh(&xdr, args->fh);
Expand Down
2 changes: 1 addition & 1 deletion include/linux/nfs_xdr.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ struct nfs_open_confirmres {
*/
struct nfs_closeargs {
struct nfs_fh * fh;
nfs4_stateid stateid;
nfs4_stateid * stateid;
struct nfs_seqid * seqid;
int open_flags;
};
Expand Down

0 comments on commit 9512135

Please sign in to comment.