diff options
Diffstat (limited to 'fs/nfs/nfs4proc.c')
-rw-r--r-- | fs/nfs/nfs4proc.c | 193 |
1 files changed, 130 insertions, 63 deletions
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 4a5cc8402110..aed8701c1a36 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -195,6 +195,7 @@ static void update_changeattr(struct inode *inode, struct nfs4_change_info *cinf } struct nfs4_opendata { + atomic_t count; struct nfs_openargs o_arg; struct nfs_openres o_res; struct nfs_fattr f_attr; @@ -203,6 +204,8 @@ struct nfs4_opendata { struct dentry *dir; struct nfs4_state_owner *owner; struct iattr attrs; + int rpc_status; + int cancelled; }; static struct nfs4_opendata *nfs4_opendata_alloc(struct dentry *dentry, @@ -220,6 +223,7 @@ static struct nfs4_opendata *nfs4_opendata_alloc(struct dentry *dentry, p->o_arg.seqid = nfs_alloc_seqid(&sp->so_seqid); if (p->o_arg.seqid == NULL) goto err_free; + atomic_set(&p->count, 1); p->dentry = dget(dentry); p->dir = parent; p->owner = sp; @@ -255,7 +259,7 @@ err: static void nfs4_opendata_free(struct nfs4_opendata *p) { - if (p != NULL) { + if (p != NULL && atomic_dec_and_test(&p->count)) { nfs_free_seqid(p->o_arg.seqid); nfs4_put_state_owner(p->owner); dput(p->dir); @@ -305,6 +309,26 @@ static void update_open_stateid(struct nfs4_state *state, nfs4_stateid *stateid, spin_unlock(&state->owner->so_lock); } +static struct nfs4_state *nfs4_opendata_to_nfs4_state(struct nfs4_opendata *data) +{ + struct inode *inode; + struct nfs4_state *state = NULL; + + if (!(data->f_attr.valid & NFS_ATTR_FATTR)) + goto out; + inode = nfs_fhget(data->dir->d_sb, &data->o_res.fh, &data->f_attr); + if (inode == NULL) + goto out; + state = nfs4_get_open_state(inode, data->owner); + if (state == NULL) + goto put_inode; + update_open_stateid(state, &data->o_res.stateid, data->o_arg.open_flags); +put_inode: + iput(inode); +out: + return state; +} + /* * OPEN_RECLAIM: * reclaim state on the server after a reboot. @@ -473,41 +497,105 @@ static int _nfs4_proc_open_confirm(struct rpc_clnt *clnt, const struct nfs_fh *f return status; } -static int _nfs4_proc_open(struct inode *dir, struct nfs4_state_owner *sp, struct nfs_openargs *o_arg, struct nfs_openres *o_res) +static void nfs4_open_prepare(struct rpc_task *task, void *calldata) { - struct nfs_server *server = NFS_SERVER(dir); + struct nfs4_opendata *data = calldata; + struct nfs4_state_owner *sp = data->owner; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN], - .rpc_argp = o_arg, - .rpc_resp = o_res, + .rpc_argp = &data->o_arg, + .rpc_resp = &data->o_res, .rpc_cred = sp->so_cred, }; - int status; + + if (nfs_wait_on_sequence(data->o_arg.seqid, task) != 0) + return; + /* Update sequence id. */ + data->o_arg.id = sp->so_id; + data->o_arg.clientid = sp->so_client->cl_clientid; + rpc_call_setup(task, &msg, 0); +} - /* Update sequence id. The caller must serialize! */ - o_arg->id = sp->so_id; - o_arg->clientid = sp->so_client->cl_clientid; +static void nfs4_open_done(struct rpc_task *task, void *calldata) +{ + struct nfs4_opendata *data = calldata; - status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR); - if (status == 0) { - /* OPEN on anything except a regular file is disallowed in NFSv4 */ - switch (o_res->f_attr->mode & S_IFMT) { + data->rpc_status = task->tk_status; + if (RPC_ASSASSINATED(task)) + return; + if (task->tk_status == 0) { + switch (data->o_res.f_attr->mode & S_IFMT) { case S_IFREG: break; case S_IFLNK: - status = -ELOOP; + data->rpc_status = -ELOOP; break; case S_IFDIR: - status = -EISDIR; + data->rpc_status = -EISDIR; break; default: - status = -ENOTDIR; + data->rpc_status = -ENOTDIR; } } + nfs_increment_open_seqid(data->rpc_status, data->o_arg.seqid); +} + +static void nfs4_open_release(void *calldata) +{ + struct nfs4_opendata *data = calldata; + struct nfs4_state *state = NULL; + + /* If this request hasn't been cancelled, do nothing */ + if (data->cancelled == 0) + goto out_free; + /* In case of error, no cleanup! */ + if (data->rpc_status != 0) + goto out_free; + /* In case we need an open_confirm, no cleanup! */ + if (data->o_res.rflags & NFS4_OPEN_RESULT_CONFIRM) + goto out_free; + nfs_confirm_seqid(&data->owner->so_seqid, 0); + state = nfs4_opendata_to_nfs4_state(data); + if (state != NULL) + nfs4_close_state(state, data->o_arg.open_flags); +out_free: + nfs4_opendata_free(data); +} + +static const struct rpc_call_ops nfs4_open_ops = { + .rpc_call_prepare = nfs4_open_prepare, + .rpc_call_done = nfs4_open_done, + .rpc_release = nfs4_open_release, +}; - nfs_increment_open_seqid(status, o_arg->seqid); +/* + * Note: On error, nfs4_proc_open will free the struct nfs4_opendata + */ +static int _nfs4_proc_open(struct nfs4_opendata *data) +{ + struct inode *dir = data->dir->d_inode; + struct nfs_server *server = NFS_SERVER(dir); + struct nfs_openargs *o_arg = &data->o_arg; + struct nfs_openres *o_res = &data->o_res; + struct rpc_task *task; + int status; + + atomic_inc(&data->count); + task = rpc_run_task(server->client, RPC_TASK_ASYNC, &nfs4_open_ops, data); + if (IS_ERR(task)) { + nfs4_opendata_free(data); + return PTR_ERR(task); + } + status = nfs4_wait_for_completion_rpc_task(task); + if (status != 0) { + data->cancelled = 1; + smp_wmb(); + } else + status = data->rpc_status; + rpc_release_task(task); if (status != 0) - goto out; + return status; + if (o_arg->open_flags & O_CREAT) { update_changeattr(dir, &o_res->cinfo); nfs_post_op_update_inode(dir, o_res->dir_attr); @@ -515,15 +603,14 @@ static int _nfs4_proc_open(struct inode *dir, struct nfs4_state_owner *sp, stru nfs_refresh_inode(dir, o_res->dir_attr); if(o_res->rflags & NFS4_OPEN_RESULT_CONFIRM) { status = _nfs4_proc_open_confirm(server->client, &o_res->fh, - sp, &o_res->stateid, o_arg->seqid); + data->owner, &o_res->stateid, o_arg->seqid); if (status != 0) - goto out; + return status; } - nfs_confirm_seqid(&sp->so_seqid, 0); + nfs_confirm_seqid(&data->owner->so_seqid, 0); if (!(o_res->f_attr->valid & NFS_ATTR_FATTR)) - status = server->rpc_ops->getattr(server, &o_res->fh, o_res->f_attr); -out: - return status; + return server->rpc_ops->getattr(server, &o_res->fh, o_res->f_attr); + return 0; } static int _nfs4_do_access(struct inode *inode, struct rpc_cred *cred, int openflags) @@ -562,14 +649,15 @@ out: static int _nfs4_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *state, struct dentry *dentry) { struct dentry *parent = dget_parent(dentry); - struct inode *dir = parent->d_inode; struct inode *inode = state->inode; struct nfs_delegation *delegation = NFS_I(inode)->delegation; struct nfs4_opendata *opendata; + struct nfs4_state *newstate; + int openflags = state->state & (FMODE_READ|FMODE_WRITE); int status = 0; if (delegation != NULL && !(delegation->flags & NFS_DELEGATION_NEED_RECLAIM)) { - status = _nfs4_do_access(inode, sp->so_cred, state->state); + status = _nfs4_do_access(inode, sp->so_cred, openflags); if (status < 0) goto out; memcpy(&state->stateid, &delegation->stateid, sizeof(state->stateid)); @@ -577,27 +665,15 @@ static int _nfs4_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *st goto out; } status = -ENOMEM; - opendata = nfs4_opendata_alloc(dentry, sp, state->state, NULL); + opendata = nfs4_opendata_alloc(dentry, sp, openflags, NULL); if (opendata == NULL) goto out; - status = _nfs4_proc_open(dir, sp, &opendata->o_arg, &opendata->o_res); + status = _nfs4_proc_open(opendata); if (status != 0) goto out_nodeleg; - /* Check if files differ */ - if ((opendata->f_attr.mode & S_IFMT) != (inode->i_mode & S_IFMT)) + newstate = nfs4_opendata_to_nfs4_state(opendata); + if (newstate != state) goto out_stale; - /* Has the file handle changed? */ - if (nfs_compare_fh(&opendata->o_res.fh, NFS_FH(inode)) != 0) { - /* Verify if the change attributes are the same */ - if (opendata->f_attr.change_attr != NFS_I(inode)->change_attr) - goto out_stale; - if (nfs_size_to_loff_t(opendata->f_attr.size) != inode->i_size) - goto out_stale; - /* Lets just pretend that this is the same file */ - nfs_copy_fh(NFS_FH(inode), &opendata->o_res.fh); - NFS_I(inode)->fileid = opendata->f_attr.fileid; - } - memcpy(&state->stateid, &opendata->o_res.stateid, sizeof(state->stateid)); if (opendata->o_res.delegation_type != 0) { if (!(delegation->flags & NFS_DELEGATION_NEED_RECLAIM)) nfs_inode_set_delegation(inode, sp->so_cred, @@ -606,6 +682,8 @@ static int _nfs4_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *st nfs_inode_reclaim_delegation(inode, sp->so_cred, &opendata->o_res); } +out_close_state: + nfs4_close_state(newstate, openflags); out_nodeleg: nfs4_opendata_free(opendata); clear_bit(NFS_DELEGATED_STATE, &state->flags); @@ -618,7 +696,7 @@ out_stale: nfs4_drop_state_owner(sp); d_drop(dentry); /* Should we be trying to close that stateid? */ - goto out_nodeleg; + goto out_close_state; } static inline int nfs4_do_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *state, struct dentry *dentry) @@ -656,7 +734,7 @@ static int nfs4_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *sta } /* - * Returns an nfs4_state + an extra reference to the inode + * Returns a referenced nfs4_state if there is an open delegation on the file */ static int _nfs4_open_delegated(struct inode *inode, int flags, struct rpc_cred *cred, struct nfs4_state **res) { @@ -709,7 +787,6 @@ out_ok: nfs4_put_state_owner(sp); up_read(&nfsi->rwsem); up_read(&clp->cl_sem); - igrab(inode); *res = state; return 0; out_err: @@ -742,7 +819,7 @@ static struct nfs4_state *nfs4_open_delegated(struct inode *inode, int flags, st } /* - * Returns an nfs4_state + an referenced inode + * Returns a referenced nfs4_state */ static int _nfs4_do_open(struct inode *dir, struct dentry *dentry, int flags, struct iattr *sattr, struct rpc_cred *cred, struct nfs4_state **res) { @@ -750,7 +827,6 @@ static int _nfs4_do_open(struct inode *dir, struct dentry *dentry, int flags, st struct nfs4_state *state = NULL; struct nfs_server *server = NFS_SERVER(dir); struct nfs4_client *clp = server->nfs4_state; - struct inode *inode = NULL; struct nfs4_opendata *opendata; int status; @@ -765,20 +841,16 @@ static int _nfs4_do_open(struct inode *dir, struct dentry *dentry, int flags, st if (opendata == NULL) goto err_put_state_owner; - status = _nfs4_proc_open(dir, sp, &opendata->o_arg, &opendata->o_res); + status = _nfs4_proc_open(opendata); if (status != 0) goto err_opendata_free; status = -ENOMEM; - inode = nfs_fhget(dir->i_sb, &opendata->o_res.fh, &opendata->f_attr); - if (!inode) - goto err_opendata_free; - state = nfs4_get_open_state(inode, sp); - if (!state) + state = nfs4_opendata_to_nfs4_state(opendata); + if (state == NULL) goto err_opendata_free; - update_open_stateid(state, &opendata->o_res.stateid, flags); if (opendata->o_res.delegation_type != 0) - nfs_inode_set_delegation(inode, cred, &opendata->o_res); + nfs_inode_set_delegation(state->inode, cred, &opendata->o_res); nfs4_opendata_free(opendata); nfs4_put_state_owner(sp); up_read(&clp->cl_sem); @@ -791,8 +863,6 @@ err_put_state_owner: out_err: /* Note: clp->cl_sem must be released before nfs4_put_open_state()! */ up_read(&clp->cl_sem); - if (inode != NULL) - iput(inode); *res = NULL; return status; } @@ -1066,7 +1136,7 @@ nfs4_atomic_open(struct inode *dir, struct dentry *dentry, struct nameidata *nd) d_add(dentry, NULL); return (struct dentry *)state; } - res = d_add_unique(dentry, state->inode); + res = d_add_unique(dentry, igrab(state->inode)); if (res != NULL) dentry = res; nfs4_intent_set_file(nd, dentry, state); @@ -1078,7 +1148,6 @@ nfs4_open_revalidate(struct inode *dir, struct dentry *dentry, int openflags, st { struct rpc_cred *cred; struct nfs4_state *state; - struct inode *inode; cred = rpcauth_lookupcred(NFS_SERVER(dir)->client->cl_auth, 0); if (IS_ERR(cred)) @@ -1102,9 +1171,7 @@ nfs4_open_revalidate(struct inode *dir, struct dentry *dentry, int openflags, st } goto out_drop; } - inode = state->inode; - iput(inode); - if (inode == dentry->d_inode) { + if (state->inode == dentry->d_inode) { nfs4_intent_set_file(nd, dentry, state); return 1; } @@ -1633,7 +1700,7 @@ nfs4_proc_create(struct inode *dir, struct dentry *dentry, struct iattr *sattr, status = PTR_ERR(state); goto out; } - d_instantiate(dentry, state->inode); + d_instantiate(dentry, igrab(state->inode)); if (flags & O_EXCL) { struct nfs_fattr fattr; status = nfs4_do_setattr(NFS_SERVER(dir), &fattr, |