X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=fs%2Fnfs%2Fnfs4proc.c;fp=fs%2Fnfs%2Fnfs4proc.c;h=d86c0db7b1e82a03bd003bc54aca52385e2c1bb7;hb=43bc926fffd92024b46cafaf7350d669ba9ca884;hp=1d5cb3e80c3e9b1cbe38b5a9a8a16ed5807345ca;hpb=cee37fe97739d85991964371c1f3a745c00dd236;p=linux-2.6.git diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 1d5cb3e80..d86c0db7b 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -47,23 +47,27 @@ #include #include #include +#include +#include "nfs4_fs.h" #include "delegation.h" +#include "iostat.h" #define NFSDBG_FACILITY NFSDBG_PROC #define NFS4_POLL_RETRY_MIN (1*HZ) #define NFS4_POLL_RETRY_MAX (15*HZ) +struct nfs4_opendata; +static int _nfs4_proc_open(struct nfs4_opendata *data); static int nfs4_do_fsinfo(struct nfs_server *, struct nfs_fh *, struct nfs_fsinfo *); -static int nfs4_async_handle_error(struct rpc_task *, struct nfs_server *); +static int nfs4_async_handle_error(struct rpc_task *, const struct nfs_server *); static int _nfs4_proc_access(struct inode *inode, struct nfs_access_entry *entry); -static int nfs4_handle_exception(struct nfs_server *server, int errorcode, struct nfs4_exception *exception); +static int nfs4_handle_exception(const struct nfs_server *server, int errorcode, struct nfs4_exception *exception); +static int nfs4_wait_clnt_recover(struct rpc_clnt *clnt, struct nfs4_client *clp); extern u32 *nfs4_decode_dirent(u32 *p, struct nfs_entry *entry, int plus); extern struct rpc_procinfo nfs4_procedures[]; -extern nfs4_stateid zero_stateid; - /* Prevent leaks of NFSv4 errors into userland */ int nfs4_map_errors(int err) { @@ -104,7 +108,7 @@ const u32 nfs4_statfs_bitmap[2] = { | FATTR4_WORD1_SPACE_TOTAL }; -u32 nfs4_pathconf_bitmap[2] = { +const u32 nfs4_pathconf_bitmap[2] = { FATTR4_WORD0_MAXLINK | FATTR4_WORD0_MAXNAME, 0 @@ -124,7 +128,7 @@ static void nfs4_setup_readdir(u64 cookie, u32 *verifier, struct dentry *dentry, BUG_ON(readdir->count < 80); if (cookie > 2) { - readdir->cookie = (cookie > 2) ? cookie : 0; + readdir->cookie = cookie; memcpy(&readdir->verifier, verifier, sizeof(readdir->verifier)); return; } @@ -172,8 +176,7 @@ static void nfs4_setup_readdir(u64 cookie, u32 *verifier, struct dentry *dentry, kunmap_atomic(start, KM_USER0); } -static void -renew_lease(struct nfs_server *server, unsigned long timestamp) +static void renew_lease(const struct nfs_server *server, unsigned long timestamp) { struct nfs4_client *clp = server->nfs4_state; spin_lock(&clp->cl_lock); @@ -186,8 +189,128 @@ static void update_changeattr(struct inode *inode, struct nfs4_change_info *cinf { struct nfs_inode *nfsi = NFS_I(inode); + spin_lock(&inode->i_lock); + nfsi->cache_validity |= NFS_INO_INVALID_ATTR; if (cinfo->before == nfsi->change_attr && cinfo->atomic) nfsi->change_attr = cinfo->after; + spin_unlock(&inode->i_lock); +} + +struct nfs4_opendata { + atomic_t count; + struct nfs_openargs o_arg; + struct nfs_openres o_res; + struct nfs_open_confirmargs c_arg; + struct nfs_open_confirmres c_res; + struct nfs_fattr f_attr; + struct nfs_fattr dir_attr; + struct dentry *dentry; + struct dentry *dir; + struct nfs4_state_owner *owner; + struct iattr attrs; + unsigned long timestamp; + int rpc_status; + int cancelled; +}; + +static struct nfs4_opendata *nfs4_opendata_alloc(struct dentry *dentry, + struct nfs4_state_owner *sp, int flags, + const struct iattr *attrs) +{ + struct dentry *parent = dget_parent(dentry); + struct inode *dir = parent->d_inode; + struct nfs_server *server = NFS_SERVER(dir); + struct nfs4_opendata *p; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) + goto err; + 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; + atomic_inc(&sp->so_count); + p->o_arg.fh = NFS_FH(dir); + p->o_arg.open_flags = flags, + p->o_arg.clientid = server->nfs4_state->cl_clientid; + p->o_arg.id = sp->so_id; + p->o_arg.name = &dentry->d_name; + p->o_arg.server = server; + p->o_arg.bitmask = server->attr_bitmask; + p->o_arg.claim = NFS4_OPEN_CLAIM_NULL; + p->o_res.f_attr = &p->f_attr; + p->o_res.dir_attr = &p->dir_attr; + p->o_res.server = server; + nfs_fattr_init(&p->f_attr); + nfs_fattr_init(&p->dir_attr); + if (flags & O_EXCL) { + u32 *s = (u32 *) p->o_arg.u.verifier.data; + s[0] = jiffies; + s[1] = current->pid; + } else if (flags & O_CREAT) { + p->o_arg.u.attrs = &p->attrs; + memcpy(&p->attrs, attrs, sizeof(p->attrs)); + } + p->c_arg.fh = &p->o_res.fh; + p->c_arg.stateid = &p->o_res.stateid; + p->c_arg.seqid = p->o_arg.seqid; + return p; +err_free: + kfree(p); +err: + dput(parent); + return NULL; +} + +static void nfs4_opendata_free(struct nfs4_opendata *p) +{ + 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); + dput(p->dentry); + kfree(p); + } +} + +/* Helper for asynchronous RPC calls */ +static int nfs4_call_async(struct rpc_clnt *clnt, + const struct rpc_call_ops *tk_ops, void *calldata) +{ + struct rpc_task *task; + + if (!(task = rpc_new_task(clnt, RPC_TASK_ASYNC, tk_ops, calldata))) + return -ENOMEM; + rpc_execute(task); + return 0; +} + +static int nfs4_wait_for_completion_rpc_task(struct rpc_task *task) +{ + sigset_t oldset; + int ret; + + rpc_clnt_sigmask(task->tk_client, &oldset); + ret = rpc_wait_for_completion_task(task); + rpc_clnt_sigunmask(task->tk_client, &oldset); + return ret; +} + +static inline void update_open_stateflags(struct nfs4_state *state, mode_t open_flags) +{ + switch (open_flags) { + case FMODE_WRITE: + state->n_wronly++; + break; + case FMODE_READ: + state->n_rdonly++; + break; + case FMODE_READ|FMODE_WRITE: + state->n_rdwr++; + } } static void update_open_stateid(struct nfs4_state *state, nfs4_stateid *stateid, int open_flags) @@ -195,46 +318,138 @@ static void update_open_stateid(struct nfs4_state *state, nfs4_stateid *stateid, struct inode *inode = state->inode; open_flags &= (FMODE_READ|FMODE_WRITE); - /* Protect against nfs4_find_state() */ + /* Protect against nfs4_find_state_byowner() */ + spin_lock(&state->owner->so_lock); spin_lock(&inode->i_lock); - state->state |= open_flags; - /* NB! List reordering - see the reclaim code for why. */ - if ((open_flags & FMODE_WRITE) && 0 == state->nwriters++) - list_move(&state->open_states, &state->owner->so_states); - if (open_flags & FMODE_READ) - state->nreaders++; memcpy(&state->stateid, stateid, sizeof(state->stateid)); + update_open_stateflags(state, open_flags); + nfs4_state_set_mode_locked(state, state->state | open_flags); spin_unlock(&inode->i_lock); + 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 (IS_ERR(inode)) + 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; +} + +static struct nfs_open_context *nfs4_state_find_open_context(struct nfs4_state *state) +{ + struct nfs_inode *nfsi = NFS_I(state->inode); + struct nfs_open_context *ctx; + + spin_lock(&state->inode->i_lock); + list_for_each_entry(ctx, &nfsi->open_files, list) { + if (ctx->state != state) + continue; + get_nfs_open_context(ctx); + spin_unlock(&state->inode->i_lock); + return ctx; + } + spin_unlock(&state->inode->i_lock); + return ERR_PTR(-ENOENT); +} + +static int nfs4_open_recover_helper(struct nfs4_opendata *opendata, mode_t openflags, nfs4_stateid *stateid) +{ + int ret; + + opendata->o_arg.open_flags = openflags; + ret = _nfs4_proc_open(opendata); + if (ret != 0) + return ret; + memcpy(stateid->data, opendata->o_res.stateid.data, + sizeof(stateid->data)); + return 0; +} + +static int nfs4_open_recover(struct nfs4_opendata *opendata, struct nfs4_state *state) +{ + nfs4_stateid stateid; + struct nfs4_state *newstate; + int mode = 0; + int delegation = 0; + int ret; + + /* memory barrier prior to reading state->n_* */ + smp_rmb(); + if (state->n_rdwr != 0) { + ret = nfs4_open_recover_helper(opendata, FMODE_READ|FMODE_WRITE, &stateid); + if (ret != 0) + return ret; + mode |= FMODE_READ|FMODE_WRITE; + if (opendata->o_res.delegation_type != 0) + delegation = opendata->o_res.delegation_type; + smp_rmb(); + } + if (state->n_wronly != 0) { + ret = nfs4_open_recover_helper(opendata, FMODE_WRITE, &stateid); + if (ret != 0) + return ret; + mode |= FMODE_WRITE; + if (opendata->o_res.delegation_type != 0) + delegation = opendata->o_res.delegation_type; + smp_rmb(); + } + if (state->n_rdonly != 0) { + ret = nfs4_open_recover_helper(opendata, FMODE_READ, &stateid); + if (ret != 0) + return ret; + mode |= FMODE_READ; + } + clear_bit(NFS_DELEGATED_STATE, &state->flags); + if (mode == 0) + return 0; + if (opendata->o_res.delegation_type == 0) + opendata->o_res.delegation_type = delegation; + opendata->o_arg.open_flags |= mode; + newstate = nfs4_opendata_to_nfs4_state(opendata); + if (newstate != NULL) { + if (opendata->o_res.delegation_type != 0) { + struct nfs_inode *nfsi = NFS_I(newstate->inode); + int delegation_flags = 0; + if (nfsi->delegation) + delegation_flags = nfsi->delegation->flags; + if (!(delegation_flags & NFS_DELEGATION_NEED_RECLAIM)) + nfs_inode_set_delegation(newstate->inode, + opendata->owner->so_cred, + &opendata->o_res); + else + nfs_inode_reclaim_delegation(newstate->inode, + opendata->owner->so_cred, + &opendata->o_res); + } + nfs4_close_state(newstate, opendata->o_arg.open_flags); + } + if (newstate != state) + return -ESTALE; + return 0; } /* * OPEN_RECLAIM: * reclaim state on the server after a reboot. - * Assumes caller is holding the sp->so_sem */ -static int _nfs4_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state) +static int _nfs4_do_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state, struct dentry *dentry) { - struct inode *inode = state->inode; - struct nfs_server *server = NFS_SERVER(inode); - struct nfs_delegation *delegation = NFS_I(inode)->delegation; - struct nfs_openargs o_arg = { - .fh = NFS_FH(inode), - .seqid = sp->so_seqid, - .id = sp->so_id, - .open_flags = state->state, - .clientid = server->nfs4_state->cl_clientid, - .claim = NFS4_OPEN_CLAIM_PREVIOUS, - .bitmask = server->attr_bitmask, - }; - struct nfs_openres o_res = { - .server = server, /* Grrr */ - }; - struct rpc_message msg = { - .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_NOATTR], - .rpc_argp = &o_arg, - .rpc_resp = &o_res, - .rpc_cred = sp->so_cred, - }; + struct nfs_delegation *delegation = NFS_I(state->inode)->delegation; + struct nfs4_opendata *opendata; + int delegation_type = 0; int status; if (delegation != NULL) { @@ -244,89 +459,64 @@ static int _nfs4_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *st set_bit(NFS_DELEGATED_STATE, &state->flags); return 0; } - o_arg.u.delegation_type = delegation->type; + delegation_type = delegation->type; } - status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR); - nfs4_increment_seqid(status, sp); - if (status == 0) { - memcpy(&state->stateid, &o_res.stateid, sizeof(state->stateid)); - if (o_res.delegation_type != 0) { - nfs_inode_reclaim_delegation(inode, sp->so_cred, &o_res); - /* Did the server issue an immediate delegation recall? */ - if (o_res.do_recall) - nfs_async_inode_return_delegation(inode, &o_res.stateid); - } - } - clear_bit(NFS_DELEGATED_STATE, &state->flags); - /* Ensure we update the inode attributes */ - NFS_CACHEINV(inode); + opendata = nfs4_opendata_alloc(dentry, sp, 0, NULL); + if (opendata == NULL) + return -ENOMEM; + opendata->o_arg.claim = NFS4_OPEN_CLAIM_PREVIOUS; + opendata->o_arg.fh = NFS_FH(state->inode); + nfs_copy_fh(&opendata->o_res.fh, opendata->o_arg.fh); + opendata->o_arg.u.delegation_type = delegation_type; + status = nfs4_open_recover(opendata, state); + nfs4_opendata_free(opendata); return status; } -static int nfs4_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state) +static int nfs4_do_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state, struct dentry *dentry) { struct nfs_server *server = NFS_SERVER(state->inode); struct nfs4_exception exception = { }; int err; do { - err = _nfs4_open_reclaim(sp, state); - switch (err) { - case 0: - case -NFS4ERR_STALE_CLIENTID: - case -NFS4ERR_STALE_STATEID: - case -NFS4ERR_EXPIRED: - return err; - } - err = nfs4_handle_exception(server, err, &exception); + err = _nfs4_do_open_reclaim(sp, state, dentry); + if (err != -NFS4ERR_DELAY) + break; + nfs4_handle_exception(server, err, &exception); } while (exception.retry); return err; } +static int nfs4_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state) +{ + struct nfs_open_context *ctx; + int ret; + + ctx = nfs4_state_find_open_context(state); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + ret = nfs4_do_open_reclaim(sp, state, ctx->dentry); + put_nfs_open_context(ctx); + return ret; +} + static int _nfs4_open_delegation_recall(struct dentry *dentry, struct nfs4_state *state) { struct nfs4_state_owner *sp = state->owner; - struct inode *inode = dentry->d_inode; - struct nfs_server *server = NFS_SERVER(inode); - struct dentry *parent = dget_parent(dentry); - struct nfs_openargs arg = { - .fh = NFS_FH(parent->d_inode), - .clientid = server->nfs4_state->cl_clientid, - .name = &dentry->d_name, - .id = sp->so_id, - .server = server, - .bitmask = server->attr_bitmask, - .claim = NFS4_OPEN_CLAIM_DELEGATE_CUR, - }; - struct nfs_openres res = { - .server = server, - }; - struct rpc_message msg = { - .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_NOATTR], - .rpc_argp = &arg, - .rpc_resp = &res, - .rpc_cred = sp->so_cred, - }; - int status = 0; + struct nfs4_opendata *opendata; + int ret; - down(&sp->so_sema); if (!test_bit(NFS_DELEGATED_STATE, &state->flags)) - goto out; - if (state->state == 0) - goto out; - arg.seqid = sp->so_seqid; - arg.open_flags = state->state; - memcpy(arg.u.delegation.data, state->stateid.data, sizeof(arg.u.delegation.data)); - status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR); - nfs4_increment_seqid(status, sp); - if (status >= 0) { - memcpy(state->stateid.data, res.stateid.data, - sizeof(state->stateid.data)); - clear_bit(NFS_DELEGATED_STATE, &state->flags); - } -out: - up(&sp->so_sema); - dput(parent); - return status; + return 0; + opendata = nfs4_opendata_alloc(dentry, sp, 0, NULL); + if (opendata == NULL) + return -ENOMEM; + opendata->o_arg.claim = NFS4_OPEN_CLAIM_DELEGATE_CUR; + memcpy(opendata->o_arg.u.delegation.data, state->stateid.data, + sizeof(opendata->o_arg.u.delegation.data)); + ret = nfs4_open_recover(opendata, state); + nfs4_opendata_free(opendata); + return ret; } int nfs4_open_delegation_recall(struct dentry *dentry, struct nfs4_state *state) @@ -351,60 +541,208 @@ int nfs4_open_delegation_recall(struct dentry *dentry, struct nfs4_state *state) return err; } -static inline int _nfs4_proc_open_confirm(struct rpc_clnt *clnt, const struct nfs_fh *fh, struct nfs4_state_owner *sp, nfs4_stateid *stateid) +static void nfs4_open_confirm_prepare(struct rpc_task *task, void *calldata) { - struct nfs_open_confirmargs arg = { - .fh = fh, - .seqid = sp->so_seqid, - .stateid = *stateid, - }; - struct nfs_open_confirmres res; - struct rpc_message msg = { - .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_CONFIRM], - .rpc_argp = &arg, - .rpc_resp = &res, - .rpc_cred = sp->so_cred, + struct nfs4_opendata *data = calldata; + struct rpc_message msg = { + .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_CONFIRM], + .rpc_argp = &data->c_arg, + .rpc_resp = &data->c_res, + .rpc_cred = data->owner->so_cred, }; + data->timestamp = jiffies; + rpc_call_setup(task, &msg, 0); +} + +static void nfs4_open_confirm_done(struct rpc_task *task, void *calldata) +{ + struct nfs4_opendata *data = calldata; + + data->rpc_status = task->tk_status; + if (RPC_ASSASSINATED(task)) + return; + if (data->rpc_status == 0) { + memcpy(data->o_res.stateid.data, data->c_res.stateid.data, + sizeof(data->o_res.stateid.data)); + renew_lease(data->o_res.server, data->timestamp); + } + nfs_increment_open_seqid(data->rpc_status, data->c_arg.seqid); + nfs_confirm_seqid(&data->owner->so_seqid, data->rpc_status); +} + +static void nfs4_open_confirm_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; + 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_confirm_ops = { + .rpc_call_prepare = nfs4_open_confirm_prepare, + .rpc_call_done = nfs4_open_confirm_done, + .rpc_release = nfs4_open_confirm_release, +}; + +/* + * Note: On error, nfs4_proc_open_confirm will free the struct nfs4_opendata + */ +static int _nfs4_proc_open_confirm(struct nfs4_opendata *data) +{ + struct nfs_server *server = NFS_SERVER(data->dir->d_inode); + struct rpc_task *task; int status; - status = rpc_call_sync(clnt, &msg, RPC_TASK_NOINTR); - nfs4_increment_seqid(status, sp); - if (status >= 0) - memcpy(stateid, &res.stateid, sizeof(*stateid)); + atomic_inc(&data->count); + /* + * If rpc_run_task() ends up calling ->rpc_release(), we + * want to ensure that it takes the 'error' code path. + */ + data->rpc_status = -ENOMEM; + task = rpc_run_task(server->client, RPC_TASK_ASYNC, &nfs4_open_confirm_ops, data); + if (IS_ERR(task)) + 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); 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; + if (data->o_arg.claim == NFS4_OPEN_CLAIM_PREVIOUS) + msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_NOATTR]; + data->timestamp = jiffies; + rpc_call_setup(task, &msg, 0); +} - /* Update sequence id. The caller must serialize! */ - o_arg->seqid = sp->so_seqid; - 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; + + 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: + data->rpc_status = -ELOOP; + break; + case S_IFDIR: + data->rpc_status = -EISDIR; + break; + default: + data->rpc_status = -ENOTDIR; + } + renew_lease(data->o_res.server, data->timestamp); + } + 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, +}; + +/* + * 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; - status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR); - nfs4_increment_seqid(status, sp); + atomic_inc(&data->count); + /* + * If rpc_run_task() ends up calling ->rpc_release(), we + * want to ensure that it takes the 'error' code path. + */ + data->rpc_status = -ENOMEM; + task = rpc_run_task(server->client, RPC_TASK_ASYNC, &nfs4_open_ops, data); + if (IS_ERR(task)) + 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; - update_changeattr(dir, &o_res->cinfo); + 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); + } else + 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); + status = _nfs4_proc_open_confirm(data); if (status != 0) - goto out; + return status; } + 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) @@ -435,6 +773,15 @@ out: return -EACCES; } +int nfs4_recover_expired_lease(struct nfs_server *server) +{ + struct nfs4_client *clp = server->nfs4_state; + + if (test_and_clear_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state)) + nfs4_schedule_state_recovery(clp); + return nfs4_wait_clnt_recover(server->client, clp); +} + /* * OPEN_EXPIRED: * reclaim state on the server after a network partition. @@ -442,95 +789,62 @@ 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_server *server = NFS_SERVER(dir); struct nfs_delegation *delegation = NFS_I(inode)->delegation; - struct nfs_fattr f_attr = { - .valid = 0, - }; - struct nfs_openargs o_arg = { - .fh = NFS_FH(dir), - .open_flags = state->state, - .name = &dentry->d_name, - .bitmask = server->attr_bitmask, - .claim = NFS4_OPEN_CLAIM_NULL, - }; - struct nfs_openres o_res = { - .f_attr = &f_attr, - .server = server, - }; - int status = 0; + struct nfs4_opendata *opendata; + int openflags = state->state & (FMODE_READ|FMODE_WRITE); + int ret; if (delegation != NULL && !(delegation->flags & NFS_DELEGATION_NEED_RECLAIM)) { - status = _nfs4_do_access(inode, sp->so_cred, state->state); - if (status < 0) - goto out; + ret = _nfs4_do_access(inode, sp->so_cred, openflags); + if (ret < 0) + return ret; memcpy(&state->stateid, &delegation->stateid, sizeof(state->stateid)); set_bit(NFS_DELEGATED_STATE, &state->flags); - goto out; + return 0; } - status = _nfs4_proc_open(dir, sp, &o_arg, &o_res); - if (status != 0) - goto out_nodeleg; - /* Check if files differ */ - if ((f_attr.mode & S_IFMT) != (inode->i_mode & S_IFMT)) - goto out_stale; - /* Has the file handle changed? */ - if (nfs_compare_fh(&o_res.fh, NFS_FH(inode)) != 0) { - /* Verify if the change attributes are the same */ - if (f_attr.change_attr != NFS_I(inode)->change_attr) - goto out_stale; - if (nfs_size_to_loff_t(f_attr.size) != inode->i_size) - goto out_stale; - /* Lets just pretend that this is the same file */ - nfs_copy_fh(NFS_FH(inode), &o_res.fh); - NFS_I(inode)->fileid = f_attr.fileid; - } - memcpy(&state->stateid, &o_res.stateid, sizeof(state->stateid)); - if (o_res.delegation_type != 0) { - if (!(delegation->flags & NFS_DELEGATION_NEED_RECLAIM)) - nfs_inode_set_delegation(inode, sp->so_cred, &o_res); - else - nfs_inode_reclaim_delegation(inode, sp->so_cred, &o_res); + opendata = nfs4_opendata_alloc(dentry, sp, openflags, NULL); + if (opendata == NULL) + return -ENOMEM; + ret = nfs4_open_recover(opendata, state); + if (ret == -ESTALE) { + /* Invalidate the state owner so we don't ever use it again */ + nfs4_drop_state_owner(sp); + d_drop(dentry); } -out_nodeleg: - clear_bit(NFS_DELEGATED_STATE, &state->flags); -out: - dput(parent); - return status; -out_stale: - status = -ESTALE; - /* Invalidate the state owner so we don't ever use it again */ - nfs4_drop_state_owner(sp); - d_drop(dentry); - /* Should we be trying to close that stateid? */ - goto out_nodeleg; + nfs4_opendata_free(opendata); + return ret; +} + +static inline int nfs4_do_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *state, struct dentry *dentry) +{ + struct nfs_server *server = NFS_SERVER(dentry->d_inode); + struct nfs4_exception exception = { }; + int err; + + do { + err = _nfs4_open_expired(sp, state, dentry); + if (err == -NFS4ERR_DELAY) + nfs4_handle_exception(server, err, &exception); + } while (exception.retry); + return err; } static int nfs4_open_expired(struct nfs4_state_owner *sp, struct nfs4_state *state) { - struct nfs_inode *nfsi = NFS_I(state->inode); struct nfs_open_context *ctx; - int status; + int ret; - spin_lock(&state->inode->i_lock); - list_for_each_entry(ctx, &nfsi->open_files, list) { - if (ctx->state != state) - continue; - get_nfs_open_context(ctx); - spin_unlock(&state->inode->i_lock); - status = _nfs4_open_expired(sp, state, ctx->dentry); - put_nfs_open_context(ctx); - return status; - } - spin_unlock(&state->inode->i_lock); - return -ENOENT; + ctx = nfs4_state_find_open_context(state); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + ret = nfs4_do_open_expired(sp, state, ctx->dentry); + put_nfs_open_context(ctx); + return ret; } /* - * 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) { @@ -543,6 +857,14 @@ static int _nfs4_open_delegated(struct inode *inode, int flags, struct rpc_cred int open_flags = flags & (FMODE_READ|FMODE_WRITE); int err; + err = -ENOMEM; + if (!(sp = nfs4_get_state_owner(server, cred))) { + dprintk("%s: nfs4_get_state_owner failed!\n", __FUNCTION__); + return err; + } + err = nfs4_recover_expired_lease(server); + if (err != 0) + goto out_put_state_owner; /* Protect against reboot recovery - NOTE ORDER! */ down_read(&clp->cl_sem); /* Protect against delegation recall */ @@ -552,11 +874,6 @@ static int _nfs4_open_delegated(struct inode *inode, int flags, struct rpc_cred if (delegation == NULL || (delegation->type & open_flags) != open_flags) goto out_err; err = -ENOMEM; - if (!(sp = nfs4_get_state_owner(server, cred))) { - dprintk("%s: nfs4_get_state_owner failed!\n", __FUNCTION__); - goto out_err; - } - down(&sp->so_sema); state = nfs4_get_open_state(inode, sp); if (state == NULL) goto out_err; @@ -564,46 +881,41 @@ static int _nfs4_open_delegated(struct inode *inode, int flags, struct rpc_cred err = -ENOENT; if ((state->state & open_flags) == open_flags) { spin_lock(&inode->i_lock); - if (open_flags & FMODE_READ) - state->nreaders++; - if (open_flags & FMODE_WRITE) - state->nwriters++; + update_open_stateflags(state, open_flags); spin_unlock(&inode->i_lock); goto out_ok; } else if (state->state != 0) - goto out_err; + goto out_put_open_state; lock_kernel(); err = _nfs4_do_access(inode, cred, open_flags); unlock_kernel(); if (err != 0) - goto out_err; + goto out_put_open_state; set_bit(NFS_DELEGATED_STATE, &state->flags); update_open_stateid(state, &delegation->stateid, open_flags); out_ok: - up(&sp->so_sema); nfs4_put_state_owner(sp); up_read(&nfsi->rwsem); up_read(&clp->cl_sem); - igrab(inode); *res = state; - return 0; -out_err: - if (sp != NULL) { - if (state != NULL) - nfs4_put_open_state(state); - up(&sp->so_sema); - nfs4_put_state_owner(sp); - } + return 0; +out_put_open_state: + nfs4_put_open_state(state); +out_err: up_read(&nfsi->rwsem); up_read(&clp->cl_sem); + if (err != -EACCES) + nfs_inode_return_delegation(inode); +out_put_state_owner: + nfs4_put_state_owner(sp); return err; } static struct nfs4_state *nfs4_open_delegated(struct inode *inode, int flags, struct rpc_cred *cred) { struct nfs4_exception exception = { }; - struct nfs4_state *res; + struct nfs4_state *res = ERR_PTR(-EIO); int err; do { @@ -617,7 +929,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) { @@ -625,70 +937,46 @@ 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; - struct nfs_fattr f_attr = { - .valid = 0, - }; - struct nfs_openargs o_arg = { - .fh = NFS_FH(dir), - .open_flags = flags, - .name = &dentry->d_name, - .server = server, - .bitmask = server->attr_bitmask, - .claim = NFS4_OPEN_CLAIM_NULL, - }; - struct nfs_openres o_res = { - .f_attr = &f_attr, - .server = server, - }; /* Protect against reboot recovery conflicts */ - down_read(&clp->cl_sem); status = -ENOMEM; if (!(sp = nfs4_get_state_owner(server, cred))) { dprintk("nfs4_do_open: nfs4_get_state_owner failed!\n"); goto out_err; } - if (flags & O_EXCL) { - u32 *p = (u32 *) o_arg.u.verifier.data; - p[0] = jiffies; - p[1] = current->pid; - } else - o_arg.u.attrs = sattr; - /* Serialization for the sequence id */ - down(&sp->so_sema); + status = nfs4_recover_expired_lease(server); + if (status != 0) + goto err_put_state_owner; + down_read(&clp->cl_sem); + status = -ENOMEM; + opendata = nfs4_opendata_alloc(dentry, sp, flags, sattr); + if (opendata == NULL) + goto err_put_state_owner; - status = _nfs4_proc_open(dir, sp, &o_arg, &o_res); + status = _nfs4_proc_open(opendata); if (status != 0) - goto out_err; + goto err_opendata_free; status = -ENOMEM; - inode = nfs_fhget(dir->i_sb, &o_res.fh, &f_attr); - if (!inode) - goto out_err; - state = nfs4_get_open_state(inode, sp); - if (!state) - goto out_err; - update_open_stateid(state, &o_res.stateid, flags); - if (o_res.delegation_type != 0) - nfs_inode_set_delegation(inode, cred, &o_res); - up(&sp->so_sema); + state = nfs4_opendata_to_nfs4_state(opendata); + if (state == NULL) + goto err_opendata_free; + if (opendata->o_res.delegation_type != 0) + nfs_inode_set_delegation(state->inode, cred, &opendata->o_res); + nfs4_opendata_free(opendata); nfs4_put_state_owner(sp); up_read(&clp->cl_sem); *res = state; return 0; +err_opendata_free: + nfs4_opendata_free(opendata); +err_put_state_owner: + nfs4_put_state_owner(sp); out_err: - if (sp != NULL) { - if (state != NULL) - nfs4_put_open_state(state); - up(&sp->so_sema); - nfs4_put_state_owner(sp); - } /* 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; } @@ -710,7 +998,7 @@ static struct nfs4_state *nfs4_do_open(struct inode *dir, struct dentry *dentry, * It is actually a sign of a bug on the client or on the server. * * If we receive a BAD_SEQID error in the particular case of - * doing an OPEN, we assume that nfs4_increment_seqid() will + * doing an OPEN, we assume that nfs_increment_open_seqid() will * have unhashed the old state_owner for us, and that we can * therefore safely retry using a new one. We should still warn * the user though... @@ -720,18 +1008,28 @@ static struct nfs4_state *nfs4_do_open(struct inode *dir, struct dentry *dentry, exception.retry = 1; continue; } + /* + * BAD_STATEID on OPEN means that the server cancelled our + * state before it received the OPEN_CONFIRM. + * Recover by retrying the request as per the discussion + * on Page 181 of RFC3530. + */ + if (status == -NFS4ERR_BAD_STATEID) { + exception.retry = 1; + continue; + } res = ERR_PTR(nfs4_handle_exception(NFS_SERVER(dir), status, &exception)); } while (exception.retry); return res; } -static int _nfs4_do_setattr(struct nfs_server *server, struct nfs_fattr *fattr, - struct nfs_fh *fhandle, struct iattr *sattr, - struct nfs4_state *state) +static int _nfs4_do_setattr(struct inode *inode, struct nfs_fattr *fattr, + struct iattr *sattr, struct nfs4_state *state) { + struct nfs_server *server = NFS_SERVER(inode); struct nfs_setattrargs arg = { - .fh = fhandle, + .fh = NFS_FH(inode), .iap = sattr, .server = server, .bitmask = server->attr_bitmask, @@ -745,29 +1043,34 @@ static int _nfs4_do_setattr(struct nfs_server *server, struct nfs_fattr *fattr, .rpc_argp = &arg, .rpc_resp = &res, }; + unsigned long timestamp = jiffies; + int status; - fattr->valid = 0; + nfs_fattr_init(fattr); - if (state != NULL) + if (nfs4_copy_delegation_stateid(&arg.stateid, inode)) { + /* Use that stateid */ + } else if (state != NULL) { msg.rpc_cred = state->owner->so_cred; - if (sattr->ia_valid & ATTR_SIZE) - nfs4_copy_stateid(&arg.stateid, state, NULL); - else + nfs4_copy_stateid(&arg.stateid, state, current->files); + } else memcpy(&arg.stateid, &zero_stateid, sizeof(arg.stateid)); - return rpc_call_sync(server->client, &msg, 0); + status = rpc_call_sync(server->client, &msg, 0); + if (status == 0 && state != NULL) + renew_lease(server, timestamp); + return status; } -static int nfs4_do_setattr(struct nfs_server *server, struct nfs_fattr *fattr, - struct nfs_fh *fhandle, struct iattr *sattr, - struct nfs4_state *state) +static int nfs4_do_setattr(struct inode *inode, struct nfs_fattr *fattr, + struct iattr *sattr, struct nfs4_state *state) { + struct nfs_server *server = NFS_SERVER(inode); struct nfs4_exception exception = { }; int err; do { err = nfs4_handle_exception(server, - _nfs4_do_setattr(server, fattr, fhandle, sattr, - state), + _nfs4_do_setattr(inode, fattr, sattr, state), &exception); } while (exception.retry); return err; @@ -778,27 +1081,41 @@ struct nfs4_closedata { struct nfs4_state *state; struct nfs_closeargs arg; struct nfs_closeres res; + struct nfs_fattr fattr; + unsigned long timestamp; }; -static void nfs4_close_done(struct rpc_task *task) +static void nfs4_free_closedata(void *data) +{ + struct nfs4_closedata *calldata = data; + struct nfs4_state_owner *sp = calldata->state->owner; + + nfs4_put_open_state(calldata->state); + nfs_free_seqid(calldata->arg.seqid); + nfs4_put_state_owner(sp); + kfree(calldata); +} + +static void nfs4_close_done(struct rpc_task *task, void *data) { - struct nfs4_closedata *calldata = (struct nfs4_closedata *)task->tk_calldata; + struct nfs4_closedata *calldata = data; struct nfs4_state *state = calldata->state; - struct nfs4_state_owner *sp = state->owner; struct nfs_server *server = NFS_SERVER(calldata->inode); + if (RPC_ASSASSINATED(task)) + return; /* hmm. we are done with the inode, and in the process of freeing * the state_owner. we keep this around to process errors */ - nfs4_increment_seqid(task->tk_status, sp); + nfs_increment_open_seqid(task->tk_status, calldata->arg.seqid); switch (task->tk_status) { case 0: memcpy(&state->stateid, &calldata->res.stateid, sizeof(state->stateid)); + renew_lease(server, calldata->timestamp); break; case -NFS4ERR_STALE_STATEID: case -NFS4ERR_EXPIRED: - state->state = calldata->arg.open_flags; nfs4_schedule_state_recovery(server->nfs4_state); break; default: @@ -807,27 +1124,57 @@ static void nfs4_close_done(struct rpc_task *task) return; } } - state->state = calldata->arg.open_flags; - nfs4_put_open_state(state); - up(&sp->so_sema); - nfs4_put_state_owner(sp); - up_read(&server->nfs4_state->cl_sem); - kfree(calldata); + nfs_refresh_inode(calldata->inode, calldata->res.fattr); } -static inline int nfs4_close_call(struct rpc_clnt *clnt, struct nfs4_closedata *calldata) +static void nfs4_close_prepare(struct rpc_task *task, void *data) { + struct nfs4_closedata *calldata = data; + 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, old_mode; + + if (nfs_wait_on_sequence(calldata->arg.seqid, task) != 0) + return; + /* Recalculate the new open mode in case someone reopened the file + * while we were waiting in line to be scheduled. + */ + spin_lock(&state->owner->so_lock); + spin_lock(&calldata->inode->i_lock); + mode = old_mode = state->state; + if (state->n_rdwr == 0) { + if (state->n_rdonly == 0) + mode &= ~FMODE_READ; + if (state->n_wronly == 0) + mode &= ~FMODE_WRITE; + } + nfs4_state_set_mode_locked(state, mode); + spin_unlock(&calldata->inode->i_lock); + spin_unlock(&state->owner->so_lock); + if (mode == old_mode || test_bit(NFS_DELEGATED_STATE, &state->flags)) { + /* Note: exit _without_ calling nfs4_close_done */ + task->tk_action = NULL; + return; + } + nfs_fattr_init(calldata->res.fattr); + 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; + calldata->timestamp = jiffies; + rpc_call_setup(task, &msg, 0); } +static const struct rpc_call_ops nfs4_close_ops = { + .rpc_call_prepare = nfs4_close_prepare, + .rpc_call_done = nfs4_close_done, + .rpc_release = nfs4_free_closedata, +}; + /* * It is possible for data to be read/written from a mem-mapped file * after the sys_close call (which hits the vfs layer as a flush). @@ -839,42 +1186,60 @@ static inline int nfs4_close_call(struct rpc_clnt *clnt, struct nfs4_closedata * * * NOTE: Caller must be holding the sp->so_owner semaphore! */ -int nfs4_do_close(struct inode *inode, struct nfs4_state *state, mode_t mode) +int nfs4_do_close(struct inode *inode, struct nfs4_state *state) { + struct nfs_server *server = NFS_SERVER(inode); 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 = state->owner->so_seqid; - 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; + calldata->arg.seqid = nfs_alloc_seqid(&state->owner->so_seqid); + if (calldata->arg.seqid == NULL) + goto out_free_calldata; + calldata->arg.bitmask = server->attr_bitmask; + calldata->res.fattr = &calldata->fattr; + calldata->res.server = server; + + status = nfs4_call_async(server->client, &nfs4_close_ops, calldata); + if (status == 0) + goto out; + + nfs_free_seqid(calldata->arg.seqid); +out_free_calldata: + kfree(calldata); +out: + return status; +} + +static int nfs4_intent_set_file(struct nameidata *nd, struct dentry *dentry, struct nfs4_state *state) +{ + struct file *filp; + + filp = lookup_instantiate_filp(nd, dentry, NULL); + if (!IS_ERR(filp)) { + struct nfs_open_context *ctx; + ctx = (struct nfs_open_context *)filp->private_data; + ctx->state = state; + return 0; + } + nfs4_close_state(state, nd->intent.open.flags); + return PTR_ERR(filp); } -struct inode * +struct dentry * nfs4_atomic_open(struct inode *dir, struct dentry *dentry, struct nameidata *nd) { struct iattr attr; struct rpc_cred *cred; struct nfs4_state *state; + struct dentry *res; if (nd->flags & LOOKUP_CREATE) { attr.ia_mode = nd->intent.open.create_mode; @@ -888,20 +1253,26 @@ nfs4_atomic_open(struct inode *dir, struct dentry *dentry, struct nameidata *nd) cred = rpcauth_lookupcred(NFS_SERVER(dir)->client->cl_auth, 0); if (IS_ERR(cred)) - return (struct inode *)cred; + return (struct dentry *)cred; state = nfs4_do_open(dir, dentry, nd->intent.open.flags, &attr, cred); put_rpccred(cred); - if (IS_ERR(state)) - return (struct inode *)state; - return state->inode; + if (IS_ERR(state)) { + if (PTR_ERR(state) == -ENOENT) + d_add(dentry, NULL); + return (struct dentry *)state; + } + res = d_add_unique(dentry, igrab(state->inode)); + if (res != NULL) + dentry = res; + nfs4_intent_set_file(nd, dentry, state); + return res; } int -nfs4_open_revalidate(struct inode *dir, struct dentry *dentry, int openflags) +nfs4_open_revalidate(struct inode *dir, struct dentry *dentry, int openflags, struct nameidata *nd) { 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)) @@ -910,18 +1281,28 @@ nfs4_open_revalidate(struct inode *dir, struct dentry *dentry, int openflags) if (IS_ERR(state)) state = nfs4_do_open(dir, dentry, openflags, NULL, cred); put_rpccred(cred); - if (state == ERR_PTR(-ENOENT) && dentry->d_inode == 0) - return 1; - if (IS_ERR(state)) - return 0; - inode = state->inode; - if (inode == dentry->d_inode) { - iput(inode); + if (IS_ERR(state)) { + switch (PTR_ERR(state)) { + case -EPERM: + case -EACCES: + case -EDQUOT: + case -ENOSPC: + case -EROFS: + lookup_instantiate_filp(nd, (struct dentry *)state, NULL); + return 1; + case -ENOENT: + if (dentry->d_inode == NULL) + return 1; + } + goto out_drop; + } + if (state->inode == dentry->d_inode) { + nfs4_intent_set_file(nd, dentry, state); return 1; } - d_drop(dentry); nfs4_close_state(state, openflags); - iput(inode); +out_drop: + d_drop(dentry); return 0; } @@ -965,13 +1346,12 @@ static int nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *fh static int _nfs4_lookup_root(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fsinfo *info) { - struct nfs_fattr * fattr = info->fattr; struct nfs4_lookup_root_arg args = { .bitmask = nfs4_fattr_bitmap, }; struct nfs4_lookup_res res = { .server = server, - .fattr = fattr, + .fattr = info->fattr, .fh = fhandle, }; struct rpc_message msg = { @@ -979,7 +1359,7 @@ static int _nfs4_lookup_root(struct nfs_server *server, struct nfs_fh *fhandle, .rpc_argp = &args, .rpc_resp = &res, }; - fattr->valid = 0; + nfs_fattr_init(info->fattr); return rpc_call_sync(server->client, &msg, 0); } @@ -1042,7 +1422,7 @@ static int nfs4_proc_get_root(struct nfs_server *server, struct nfs_fh *fhandle, q.len = p - q.name; do { - fattr->valid = 0; + nfs_fattr_init(fattr); status = nfs4_handle_exception(server, rpc_call_sync(server->client, &msg, 0), &exception); @@ -1060,7 +1440,7 @@ static int nfs4_proc_get_root(struct nfs_server *server, struct nfs_fh *fhandle, if (status == 0) status = nfs4_do_fsinfo(server, fhandle, info); out: - return status; + return nfs4_map_errors(status); } static int _nfs4_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fattr *fattr) @@ -1079,7 +1459,7 @@ static int _nfs4_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle, .rpc_resp = &res, }; - fattr->valid = 0; + nfs_fattr_init(fattr); return rpc_call_sync(server->client, &msg, 0); } @@ -1116,47 +1496,29 @@ static int nfs4_proc_setattr(struct dentry *dentry, struct nfs_fattr *fattr, struct iattr *sattr) { - struct inode * inode = dentry->d_inode; - int size_change = sattr->ia_valid & ATTR_SIZE; - struct nfs4_state *state = NULL; - int need_iput = 0; + struct rpc_cred *cred; + struct inode *inode = dentry->d_inode; + struct nfs_open_context *ctx; + struct nfs4_state *state = NULL; int status; - fattr->valid = 0; + nfs_fattr_init(fattr); - if (size_change) { - struct rpc_cred *cred = rpcauth_lookupcred(NFS_SERVER(inode)->client->cl_auth, 0); - if (IS_ERR(cred)) - return PTR_ERR(cred); - state = nfs4_find_state(inode, cred, FMODE_WRITE); - if (state == NULL) { - state = nfs4_open_delegated(dentry->d_inode, - FMODE_WRITE, cred); - if (IS_ERR(state)) - state = nfs4_do_open(dentry->d_parent->d_inode, - dentry, FMODE_WRITE, - NULL, cred); - need_iput = 1; - } - put_rpccred(cred); - if (IS_ERR(state)) - return PTR_ERR(state); + cred = rpcauth_lookupcred(NFS_SERVER(inode)->client->cl_auth, 0); + if (IS_ERR(cred)) + return PTR_ERR(cred); - if (state->inode != inode) { - printk(KERN_WARNING "nfs: raced in setattr (%p != %p), returning -EIO\n", inode, state->inode); - status = -EIO; - goto out; - } - } - status = nfs4_do_setattr(NFS_SERVER(inode), fattr, - NFS_FH(inode), sattr, state); -out: - if (state) { - inode = state->inode; - nfs4_close_state(state, FMODE_WRITE); - if (need_iput) - iput(inode); - } + /* Search for an existing open(O_WRITE) file */ + ctx = nfs_find_open_context(inode, cred, FMODE_WRITE); + if (ctx != NULL) + state = ctx->state; + + status = nfs4_do_setattr(inode, fattr, sattr, state); + if (status == 0) + nfs_setattr_update_inode(inode, sattr); + if (ctx != NULL) + put_nfs_open_context(ctx); + put_rpccred(cred); return status; } @@ -1181,7 +1543,7 @@ static int _nfs4_proc_lookup(struct inode *dir, struct qstr *name, .rpc_resp = &res, }; - fattr->valid = 0; + nfs_fattr_init(fattr); dprintk("NFS call lookup %s\n", name->name); status = rpc_call_sync(NFS_CLIENT(dir), &msg, 0); @@ -1330,7 +1692,7 @@ static int _nfs4_proc_read(struct nfs_read_data *rdata) dprintk("NFS call read %d @ %Ld\n", rdata->args.count, (long long) rdata->args.offset); - fattr->valid = 0; + nfs_fattr_init(fattr); status = rpc_call_sync(server->client, &msg, flags); if (!status) renew_lease(server, timestamp); @@ -1367,10 +1729,17 @@ static int _nfs4_proc_write(struct nfs_write_data *wdata) dprintk("NFS call write %d @ %Ld\n", wdata->args.count, (long long) wdata->args.offset); - fattr->valid = 0; + wdata->args.bitmask = server->attr_bitmask; + wdata->res.server = server; + wdata->timestamp = jiffies; + nfs_fattr_init(fattr); status = rpc_call_sync(server->client, &msg, rpcflags); dprintk("NFS reply write: %d\n", status); - return status; + if (status < 0) + return status; + renew_lease(server, wdata->timestamp); + nfs_post_op_update_inode(inode, fattr); + return wdata->res.count; } static int nfs4_proc_write(struct nfs_write_data *wdata) @@ -1401,9 +1770,16 @@ static int _nfs4_proc_commit(struct nfs_write_data *cdata) dprintk("NFS call commit %d @ %Ld\n", cdata->args.count, (long long) cdata->args.offset); - fattr->valid = 0; + cdata->args.bitmask = server->attr_bitmask; + cdata->res.server = server; + cdata->timestamp = jiffies; + nfs_fattr_init(fattr); status = rpc_call_sync(server->client, &msg, 0); + if (status >= 0) + renew_lease(server, cdata->timestamp); dprintk("NFS reply commit: %d\n", status); + if (status >= 0) + nfs_post_op_update_inode(inode, fattr); return status; } @@ -1436,7 +1812,7 @@ static int nfs4_proc_commit(struct nfs_write_data *cdata) static int nfs4_proc_create(struct inode *dir, struct dentry *dentry, struct iattr *sattr, - int flags) + int flags, struct nameidata *nd) { struct nfs4_state *state; struct rpc_cred *cred; @@ -1453,27 +1829,34 @@ 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, - NFS_FH(state->inode), sattr, state); + status = nfs4_do_setattr(state->inode, &fattr, sattr, state); if (status == 0) - goto out; - } else if (flags != 0) - goto out; - nfs4_close_state(state, flags); + nfs_setattr_update_inode(state->inode, sattr); + } + if (status == 0 && nd != NULL && (nd->flags & LOOKUP_OPEN)) + status = nfs4_intent_set_file(nd, dentry, state); + else + nfs4_close_state(state, flags); out: return status; } static int _nfs4_proc_remove(struct inode *dir, struct qstr *name) { + struct nfs_server *server = NFS_SERVER(dir); struct nfs4_remove_arg args = { .fh = NFS_FH(dir), .name = name, + .bitmask = server->attr_bitmask, + }; + struct nfs_fattr dir_attr; + struct nfs4_remove_res res = { + .server = server, + .dir_attr = &dir_attr, }; - struct nfs4_change_info res; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_REMOVE], .rpc_argp = &args, @@ -1481,9 +1864,12 @@ static int _nfs4_proc_remove(struct inode *dir, struct qstr *name) }; int status; - status = rpc_call_sync(NFS_CLIENT(dir), &msg, 0); - if (status == 0) - update_changeattr(dir, &res); + nfs_fattr_init(res.dir_attr); + status = rpc_call_sync(server->client, &msg, 0); + if (status == 0) { + update_changeattr(dir, &res.cinfo); + nfs_post_op_update_inode(dir, res.dir_attr); + } return status; } @@ -1501,12 +1887,14 @@ static int nfs4_proc_remove(struct inode *dir, struct qstr *name) struct unlink_desc { struct nfs4_remove_arg args; - struct nfs4_change_info res; + struct nfs4_remove_res res; + struct nfs_fattr dir_attr; }; static int nfs4_proc_unlink_setup(struct rpc_message *msg, struct dentry *dir, struct qstr *name) { + struct nfs_server *server = NFS_SERVER(dir->d_inode); struct unlink_desc *up; up = (struct unlink_desc *) kmalloc(sizeof(*up), GFP_KERNEL); @@ -1515,6 +1903,9 @@ static int nfs4_proc_unlink_setup(struct rpc_message *msg, struct dentry *dir, up->args.fh = NFS_FH(dir->d_inode); up->args.name = name; + up->args.bitmask = server->attr_bitmask; + up->res.server = server; + up->res.dir_attr = &up->dir_attr; msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_REMOVE]; msg->rpc_argp = &up->args; @@ -1529,7 +1920,8 @@ static int nfs4_proc_unlink_done(struct dentry *dir, struct rpc_task *task) if (msg->rpc_resp != NULL) { up = container_of(msg->rpc_resp, struct unlink_desc, res); - update_changeattr(dir->d_inode, &up->res); + update_changeattr(dir->d_inode, &up->res.cinfo); + nfs_post_op_update_inode(dir->d_inode, up->res.dir_attr); kfree(up); msg->rpc_resp = NULL; msg->rpc_argp = NULL; @@ -1540,13 +1932,20 @@ static int nfs4_proc_unlink_done(struct dentry *dir, struct rpc_task *task) static int _nfs4_proc_rename(struct inode *old_dir, struct qstr *old_name, struct inode *new_dir, struct qstr *new_name) { + struct nfs_server *server = NFS_SERVER(old_dir); struct nfs4_rename_arg arg = { .old_dir = NFS_FH(old_dir), .new_dir = NFS_FH(new_dir), .old_name = old_name, .new_name = new_name, + .bitmask = server->attr_bitmask, + }; + struct nfs_fattr old_fattr, new_fattr; + struct nfs4_rename_res res = { + .server = server, + .old_fattr = &old_fattr, + .new_fattr = &new_fattr, }; - struct nfs4_rename_res res = { }; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_RENAME], .rpc_argp = &arg, @@ -1554,11 +1953,15 @@ static int _nfs4_proc_rename(struct inode *old_dir, struct qstr *old_name, }; int status; - status = rpc_call_sync(NFS_CLIENT(old_dir), &msg, 0); + nfs_fattr_init(res.old_fattr); + nfs_fattr_init(res.new_fattr); + status = rpc_call_sync(server->client, &msg, 0); if (!status) { update_changeattr(old_dir, &res.old_cinfo); + nfs_post_op_update_inode(old_dir, res.old_fattr); update_changeattr(new_dir, &res.new_cinfo); + nfs_post_op_update_inode(new_dir, res.new_fattr); } return status; } @@ -1579,22 +1982,34 @@ static int nfs4_proc_rename(struct inode *old_dir, struct qstr *old_name, static int _nfs4_proc_link(struct inode *inode, struct inode *dir, struct qstr *name) { + struct nfs_server *server = NFS_SERVER(inode); struct nfs4_link_arg arg = { .fh = NFS_FH(inode), .dir_fh = NFS_FH(dir), .name = name, + .bitmask = server->attr_bitmask, + }; + struct nfs_fattr fattr, dir_attr; + struct nfs4_link_res res = { + .server = server, + .fattr = &fattr, + .dir_attr = &dir_attr, }; - struct nfs4_change_info cinfo = { }; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_LINK], .rpc_argp = &arg, - .rpc_resp = &cinfo, + .rpc_resp = &res, }; int status; - status = rpc_call_sync(NFS_CLIENT(inode), &msg, 0); - if (!status) - update_changeattr(dir, &cinfo); + nfs_fattr_init(res.fattr); + nfs_fattr_init(res.dir_attr); + status = rpc_call_sync(server->client, &msg, 0); + if (!status) { + update_changeattr(dir, &res.cinfo); + nfs_post_op_update_inode(dir, res.dir_attr); + nfs_refresh_inode(inode, res.fattr); + } return status; } @@ -1616,6 +2031,7 @@ static int _nfs4_proc_symlink(struct inode *dir, struct qstr *name, struct nfs_fattr *fattr) { struct nfs_server *server = NFS_SERVER(dir); + struct nfs_fattr dir_fattr; struct nfs4_create_arg arg = { .dir_fh = NFS_FH(dir), .server = server, @@ -1628,6 +2044,7 @@ static int _nfs4_proc_symlink(struct inode *dir, struct qstr *name, .server = server, .fh = fhandle, .fattr = fattr, + .dir_fattr = &dir_fattr, }; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SYMLINK], @@ -1639,11 +2056,13 @@ static int _nfs4_proc_symlink(struct inode *dir, struct qstr *name, if (path->len > NFS4_MAXPATHLEN) return -ENAMETOOLONG; arg.u.symlink = path; - fattr->valid = 0; + nfs_fattr_init(fattr); + nfs_fattr_init(&dir_fattr); status = rpc_call_sync(NFS_CLIENT(dir), &msg, 0); if (!status) update_changeattr(dir, &res.dir_cinfo); + nfs_post_op_update_inode(dir, res.dir_fattr); return status; } @@ -1667,7 +2086,7 @@ static int _nfs4_proc_mkdir(struct inode *dir, struct dentry *dentry, { struct nfs_server *server = NFS_SERVER(dir); struct nfs_fh fhandle; - struct nfs_fattr fattr; + struct nfs_fattr fattr, dir_fattr; struct nfs4_create_arg arg = { .dir_fh = NFS_FH(dir), .server = server, @@ -1680,6 +2099,7 @@ static int _nfs4_proc_mkdir(struct inode *dir, struct dentry *dentry, .server = server, .fh = &fhandle, .fattr = &fattr, + .dir_fattr = &dir_fattr, }; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CREATE], @@ -1688,11 +2108,13 @@ static int _nfs4_proc_mkdir(struct inode *dir, struct dentry *dentry, }; int status; - fattr.valid = 0; + nfs_fattr_init(&fattr); + nfs_fattr_init(&dir_fattr); status = rpc_call_sync(NFS_CLIENT(dir), &msg, 0); if (!status) { update_changeattr(dir, &res.dir_cinfo); + nfs_post_op_update_inode(dir, res.dir_fattr); status = nfs_instantiate(dentry, &fhandle, &fattr); } return status; @@ -1731,6 +2153,10 @@ static int _nfs4_proc_readdir(struct dentry *dentry, struct rpc_cred *cred, }; int status; + dprintk("%s: dentry = %s/%s, cookie = %Lu\n", __FUNCTION__, + dentry->d_parent->d_name.name, + dentry->d_name.name, + (unsigned long long)cookie); lock_kernel(); nfs4_setup_readdir(cookie, NFS_COOKIEVERF(dir), dentry, &args); res.pgbase = args.pgbase; @@ -1738,6 +2164,7 @@ static int _nfs4_proc_readdir(struct dentry *dentry, struct rpc_cred *cred, if (status == 0) memcpy(NFS_COOKIEVERF(dir), res.verifier.data, NFS4_VERIFIER_SIZE); unlock_kernel(); + dprintk("%s: returns %d\n", __FUNCTION__, status); return status; } @@ -1760,7 +2187,7 @@ static int _nfs4_proc_mknod(struct inode *dir, struct dentry *dentry, { struct nfs_server *server = NFS_SERVER(dir); struct nfs_fh fh; - struct nfs_fattr fattr; + struct nfs_fattr fattr, dir_fattr; struct nfs4_create_arg arg = { .dir_fh = NFS_FH(dir), .server = server, @@ -1772,6 +2199,7 @@ static int _nfs4_proc_mknod(struct inode *dir, struct dentry *dentry, .server = server, .fh = &fh, .fattr = &fattr, + .dir_fattr = &dir_fattr, }; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CREATE], @@ -1781,7 +2209,8 @@ static int _nfs4_proc_mknod(struct inode *dir, struct dentry *dentry, int status; int mode = sattr->ia_mode; - fattr.valid = 0; + nfs_fattr_init(&fattr); + nfs_fattr_init(&dir_fattr); BUG_ON(!(sattr->ia_valid & ATTR_MODE)); BUG_ON(!S_ISFIFO(mode) && !S_ISBLK(mode) && !S_ISCHR(mode) && !S_ISSOCK(mode)); @@ -1803,6 +2232,7 @@ static int _nfs4_proc_mknod(struct inode *dir, struct dentry *dentry, status = rpc_call_sync(NFS_CLIENT(dir), &msg, 0); if (status == 0) { update_changeattr(dir, &res.dir_cinfo); + nfs_post_op_update_inode(dir, res.dir_fattr); status = nfs_instantiate(dentry, &fh, &fattr); } return status; @@ -1834,7 +2264,7 @@ static int _nfs4_proc_statfs(struct nfs_server *server, struct nfs_fh *fhandle, .rpc_resp = fsstat, }; - fsstat->fattr->valid = 0; + nfs_fattr_init(fsstat->fattr); return rpc_call_sync(server->client, &msg, 0); } @@ -1881,7 +2311,7 @@ static int nfs4_do_fsinfo(struct nfs_server *server, struct nfs_fh *fhandle, str static int nfs4_proc_fsinfo(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fsinfo *fsinfo) { - fsinfo->fattr->valid = 0; + nfs_fattr_init(fsinfo->fattr); return nfs4_do_fsinfo(server, fhandle, fsinfo); } @@ -1904,7 +2334,7 @@ static int _nfs4_proc_pathconf(struct nfs_server *server, struct nfs_fh *fhandle return 0; } - pathconf->fattr->valid = 0; + nfs_fattr_init(pathconf->fattr); return rpc_call_sync(server->client, &msg, 0); } @@ -1922,65 +2352,50 @@ static int nfs4_proc_pathconf(struct nfs_server *server, struct nfs_fh *fhandle, return err; } -static void -nfs4_read_done(struct rpc_task *task) +static int nfs4_read_done(struct rpc_task *task, struct nfs_read_data *data) { - struct nfs_read_data *data = (struct nfs_read_data *) task->tk_calldata; - struct inode *inode = data->inode; + struct nfs_server *server = NFS_SERVER(data->inode); - if (nfs4_async_handle_error(task, NFS_SERVER(inode)) == -EAGAIN) { + if (nfs4_async_handle_error(task, server) == -EAGAIN) { rpc_restart_call(task); - return; + return -EAGAIN; } if (task->tk_status > 0) - renew_lease(NFS_SERVER(inode), data->timestamp); - /* Call back common NFS readpage processing */ - nfs_readpage_result(task); + renew_lease(server, data->timestamp); + return 0; } -static void -nfs4_proc_read_setup(struct nfs_read_data *data) +static void nfs4_proc_read_setup(struct nfs_read_data *data) { - struct rpc_task *task = &data->task; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_READ], .rpc_argp = &data->args, .rpc_resp = &data->res, .rpc_cred = data->cred, }; - struct inode *inode = data->inode; - int flags; data->timestamp = jiffies; - /* N.B. Do we need to test? Never called for swapfile inode */ - flags = RPC_TASK_ASYNC | (IS_SWAPFILE(inode)? NFS_RPC_SWAPFLAGS : 0); - - /* Finalize the task. */ - rpc_init_task(task, NFS_CLIENT(inode), nfs4_read_done, flags); - rpc_call_setup(task, &msg, 0); + rpc_call_setup(&data->task, &msg, 0); } -static void -nfs4_write_done(struct rpc_task *task) +static int nfs4_write_done(struct rpc_task *task, struct nfs_write_data *data) { - struct nfs_write_data *data = (struct nfs_write_data *) task->tk_calldata; struct inode *inode = data->inode; if (nfs4_async_handle_error(task, NFS_SERVER(inode)) == -EAGAIN) { rpc_restart_call(task); - return; + return -EAGAIN; } - if (task->tk_status >= 0) + if (task->tk_status >= 0) { renew_lease(NFS_SERVER(inode), data->timestamp); - /* Call back common NFS writeback processing */ - nfs_writeback_done(task); + nfs_post_op_update_inode(inode, data->res.fattr); + } + return 0; } -static void -nfs4_proc_write_setup(struct nfs_write_data *data, int how) +static void nfs4_proc_write_setup(struct nfs_write_data *data, int how) { - struct rpc_task *task = &data->task; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_WRITE], .rpc_argp = &data->args, @@ -1988,8 +2403,8 @@ nfs4_proc_write_setup(struct nfs_write_data *data, int how) .rpc_cred = data->cred, }; struct inode *inode = data->inode; + struct nfs_server *server = NFS_SERVER(inode); int stable; - int flags; if (how & FLUSH_STABLE) { if (!NFS_I(inode)->ncommit) @@ -1999,61 +2414,52 @@ nfs4_proc_write_setup(struct nfs_write_data *data, int how) } else stable = NFS_UNSTABLE; data->args.stable = stable; + data->args.bitmask = server->attr_bitmask; + data->res.server = server; data->timestamp = jiffies; - /* Set the initial flags for the task. */ - flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC; - /* Finalize the task. */ - rpc_init_task(task, NFS_CLIENT(inode), nfs4_write_done, flags); - rpc_call_setup(task, &msg, 0); + rpc_call_setup(&data->task, &msg, 0); } -static void -nfs4_commit_done(struct rpc_task *task) +static int nfs4_commit_done(struct rpc_task *task, struct nfs_write_data *data) { - struct nfs_write_data *data = (struct nfs_write_data *) task->tk_calldata; struct inode *inode = data->inode; if (nfs4_async_handle_error(task, NFS_SERVER(inode)) == -EAGAIN) { rpc_restart_call(task); - return; + return -EAGAIN; } - /* Call back common NFS writeback processing */ - nfs_commit_done(task); + if (task->tk_status >= 0) + nfs_post_op_update_inode(inode, data->res.fattr); + return 0; } -static void -nfs4_proc_commit_setup(struct nfs_write_data *data, int how) +static void nfs4_proc_commit_setup(struct nfs_write_data *data, int how) { - struct rpc_task *task = &data->task; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_COMMIT], .rpc_argp = &data->args, .rpc_resp = &data->res, .rpc_cred = data->cred, }; - struct inode *inode = data->inode; - int flags; + struct nfs_server *server = NFS_SERVER(data->inode); - /* Set the initial flags for the task. */ - flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC; + data->args.bitmask = server->attr_bitmask; + data->res.server = server; - /* Finalize the task. */ - rpc_init_task(task, NFS_CLIENT(inode), nfs4_commit_done, flags); - rpc_call_setup(task, &msg, 0); + rpc_call_setup(&data->task, &msg, 0); } /* * nfs4_proc_async_renew(): This is not one of the nfs_rpc_ops; it is a special * standalone procedure for queueing an asynchronous RENEW. */ -static void -renew_done(struct rpc_task *task) +static void nfs4_renew_done(struct rpc_task *task, void *data) { struct nfs4_client *clp = (struct nfs4_client *)task->tk_msg.rpc_argp; - unsigned long timestamp = (unsigned long)task->tk_calldata; + unsigned long timestamp = (unsigned long)data; if (task->tk_status < 0) { switch (task->tk_status) { @@ -2070,26 +2476,28 @@ renew_done(struct rpc_task *task) spin_unlock(&clp->cl_lock); } -int -nfs4_proc_async_renew(struct nfs4_client *clp) +static const struct rpc_call_ops nfs4_renew_ops = { + .rpc_call_done = nfs4_renew_done, +}; + +int nfs4_proc_async_renew(struct nfs4_client *clp, struct rpc_cred *cred) { struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_RENEW], .rpc_argp = clp, - .rpc_cred = clp->cl_cred, + .rpc_cred = cred, }; return rpc_call_async(clp->cl_rpcclient, &msg, RPC_TASK_SOFT, - renew_done, (void *)jiffies); + &nfs4_renew_ops, (void *)jiffies); } -int -nfs4_proc_renew(struct nfs4_client *clp) +int nfs4_proc_renew(struct nfs4_client *clp, struct rpc_cred *cred) { struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_RENEW], .rpc_argp = clp, - .rpc_cred = clp->cl_cred, + .rpc_cred = cred, }; unsigned long now = jiffies; int status; @@ -2104,67 +2512,196 @@ nfs4_proc_renew(struct nfs4_client *clp) return 0; } -/* - * We will need to arrange for the VFS layer to provide an atomic open. - * Until then, this open method is prone to inefficiency and race conditions - * due to the lookup, potential create, and open VFS calls from sys_open() - * placed on the wire. +static inline int nfs4_server_supports_acls(struct nfs_server *server) +{ + return (server->caps & NFS_CAP_ACLS) + && (server->acl_bitmask & ACL4_SUPPORT_ALLOW_ACL) + && (server->acl_bitmask & ACL4_SUPPORT_DENY_ACL); +} + +/* Assuming that XATTR_SIZE_MAX is a multiple of PAGE_CACHE_SIZE, and that + * it's OK to put sizeof(void) * (XATTR_SIZE_MAX/PAGE_CACHE_SIZE) bytes on + * the stack. */ -static int -nfs4_proc_file_open(struct inode *inode, struct file *filp) +#define NFS4ACL_MAXPAGES (XATTR_SIZE_MAX >> PAGE_CACHE_SHIFT) + +static void buf_to_pages(const void *buf, size_t buflen, + struct page **pages, unsigned int *pgbase) { - struct dentry *dentry = filp->f_dentry; - struct nfs_open_context *ctx; - struct nfs4_state *state = NULL; - struct rpc_cred *cred; - int status = -ENOMEM; + const void *p = buf; + + *pgbase = offset_in_page(buf); + p -= *pgbase; + while (p < buf + buflen) { + *(pages++) = virt_to_page(p); + p += PAGE_CACHE_SIZE; + } +} - dprintk("nfs4_proc_file_open: starting on (%.*s/%.*s)\n", - (int)dentry->d_parent->d_name.len, - dentry->d_parent->d_name.name, - (int)dentry->d_name.len, dentry->d_name.name); +struct nfs4_cached_acl { + int cached; + size_t len; + char data[0]; +}; +static void nfs4_set_cached_acl(struct inode *inode, struct nfs4_cached_acl *acl) +{ + struct nfs_inode *nfsi = NFS_I(inode); - /* Find our open stateid */ - cred = rpcauth_lookupcred(NFS_SERVER(inode)->client->cl_auth, 0); - if (IS_ERR(cred)) - return PTR_ERR(cred); - ctx = alloc_nfs_open_context(dentry, cred); - put_rpccred(cred); - if (unlikely(ctx == NULL)) - return -ENOMEM; - status = -EIO; /* ERACE actually */ - state = nfs4_find_state(inode, cred, filp->f_mode); - if (unlikely(state == NULL)) - goto no_state; - ctx->state = state; - nfs4_close_state(state, filp->f_mode); - ctx->mode = filp->f_mode; - nfs_file_set_open_context(filp, ctx); - put_nfs_open_context(ctx); - if (filp->f_mode & FMODE_WRITE) - nfs_begin_data_update(inode); - return 0; -no_state: - printk(KERN_WARNING "NFS: v4 raced in function %s\n", __FUNCTION__); - put_nfs_open_context(ctx); - return status; + spin_lock(&inode->i_lock); + kfree(nfsi->nfs4_acl); + nfsi->nfs4_acl = acl; + spin_unlock(&inode->i_lock); } -/* - * Release our state - */ -static int -nfs4_proc_file_release(struct inode *inode, struct file *filp) +static void nfs4_zap_acl_attr(struct inode *inode) { - if (filp->f_mode & FMODE_WRITE) - nfs_end_data_update(inode); - nfs_file_clear_open_context(filp); - return 0; + nfs4_set_cached_acl(inode, NULL); +} + +static inline ssize_t nfs4_read_cached_acl(struct inode *inode, char *buf, size_t buflen) +{ + struct nfs_inode *nfsi = NFS_I(inode); + struct nfs4_cached_acl *acl; + int ret = -ENOENT; + + spin_lock(&inode->i_lock); + acl = nfsi->nfs4_acl; + if (acl == NULL) + goto out; + if (buf == NULL) /* user is just asking for length */ + goto out_len; + if (acl->cached == 0) + goto out; + ret = -ERANGE; /* see getxattr(2) man page */ + if (acl->len > buflen) + goto out; + memcpy(buf, acl->data, acl->len); +out_len: + ret = acl->len; +out: + spin_unlock(&inode->i_lock); + return ret; +} + +static void nfs4_write_cached_acl(struct inode *inode, const char *buf, size_t acl_len) +{ + struct nfs4_cached_acl *acl; + + if (buf && acl_len <= PAGE_SIZE) { + acl = kmalloc(sizeof(*acl) + acl_len, GFP_KERNEL); + if (acl == NULL) + goto out; + acl->cached = 1; + memcpy(acl->data, buf, acl_len); + } else { + acl = kmalloc(sizeof(*acl), GFP_KERNEL); + if (acl == NULL) + goto out; + acl->cached = 0; + } + acl->len = acl_len; +out: + nfs4_set_cached_acl(inode, acl); +} + +static inline ssize_t nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen) +{ + struct page *pages[NFS4ACL_MAXPAGES]; + struct nfs_getaclargs args = { + .fh = NFS_FH(inode), + .acl_pages = pages, + .acl_len = buflen, + }; + size_t resp_len = buflen; + void *resp_buf; + struct rpc_message msg = { + .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETACL], + .rpc_argp = &args, + .rpc_resp = &resp_len, + }; + struct page *localpage = NULL; + int ret; + + if (buflen < PAGE_SIZE) { + /* As long as we're doing a round trip to the server anyway, + * let's be prepared for a page of acl data. */ + localpage = alloc_page(GFP_KERNEL); + resp_buf = page_address(localpage); + if (localpage == NULL) + return -ENOMEM; + args.acl_pages[0] = localpage; + args.acl_pgbase = 0; + resp_len = args.acl_len = PAGE_SIZE; + } else { + resp_buf = buf; + buf_to_pages(buf, buflen, args.acl_pages, &args.acl_pgbase); + } + ret = rpc_call_sync(NFS_CLIENT(inode), &msg, 0); + if (ret) + goto out_free; + if (resp_len > args.acl_len) + nfs4_write_cached_acl(inode, NULL, resp_len); + else + nfs4_write_cached_acl(inode, resp_buf, resp_len); + if (buf) { + ret = -ERANGE; + if (resp_len > buflen) + goto out_free; + if (localpage) + memcpy(buf, resp_buf, resp_len); + } + ret = resp_len; +out_free: + if (localpage) + __free_page(localpage); + return ret; +} + +static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen) +{ + struct nfs_server *server = NFS_SERVER(inode); + int ret; + + if (!nfs4_server_supports_acls(server)) + return -EOPNOTSUPP; + ret = nfs_revalidate_inode(server, inode); + if (ret < 0) + return ret; + ret = nfs4_read_cached_acl(inode, buf, buflen); + if (ret != -ENOENT) + return ret; + return nfs4_get_acl_uncached(inode, buf, buflen); +} + +static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen) +{ + struct nfs_server *server = NFS_SERVER(inode); + struct page *pages[NFS4ACL_MAXPAGES]; + struct nfs_setaclargs arg = { + .fh = NFS_FH(inode), + .acl_pages = pages, + .acl_len = buflen, + }; + struct rpc_message msg = { + .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SETACL], + .rpc_argp = &arg, + .rpc_resp = NULL, + }; + int ret; + + if (!nfs4_server_supports_acls(server)) + return -EOPNOTSUPP; + nfs_inode_return_delegation(inode); + buf_to_pages(buf, buflen, arg.acl_pages, &arg.acl_pgbase); + ret = rpc_call_sync(NFS_SERVER(inode)->client, &msg, 0); + if (ret == 0) + nfs4_write_cached_acl(inode, buf, buflen); + return ret; } static int -nfs4_async_handle_error(struct rpc_task *task, struct nfs_server *server) +nfs4_async_handle_error(struct rpc_task *task, const struct nfs_server *server) { struct nfs4_client *clp = server->nfs4_state; @@ -2176,12 +2713,14 @@ nfs4_async_handle_error(struct rpc_task *task, struct nfs_server *server) case -NFS4ERR_EXPIRED: rpc_sleep_on(&clp->cl_rpcwaitq, task, NULL, NULL); nfs4_schedule_state_recovery(clp); - if (test_bit(NFS4CLNT_OK, &clp->cl_state)) + if (test_bit(NFS4CLNT_STATE_RECOVER, &clp->cl_state) == 0) rpc_wake_up_task(task); task->tk_status = 0; return -EAGAIN; - case -NFS4ERR_GRACE: case -NFS4ERR_DELAY: + nfs_inc_server_stats((struct nfs_server *) server, + NFSIOS_DELAY); + case -NFS4ERR_GRACE: rpc_delay(task, NFS4_POLL_RETRY_MAX); task->tk_status = 0; return -EAGAIN; @@ -2193,25 +2732,25 @@ nfs4_async_handle_error(struct rpc_task *task, struct nfs_server *server) return 0; } +static int nfs4_wait_bit_interruptible(void *word) +{ + if (signal_pending(current)) + return -ERESTARTSYS; + schedule(); + return 0; +} + static int nfs4_wait_clnt_recover(struct rpc_clnt *clnt, struct nfs4_client *clp) { - DEFINE_WAIT(wait); sigset_t oldset; - int interruptible, res = 0; + int res; might_sleep(); rpc_clnt_sigmask(clnt, &oldset); - interruptible = TASK_UNINTERRUPTIBLE; - if (clnt->cl_intr) - interruptible = TASK_INTERRUPTIBLE; - prepare_to_wait(&clp->cl_waitq, &wait, interruptible); - nfs4_schedule_state_recovery(clp); - if (clnt->cl_intr && signalled()) - res = -ERESTARTSYS; - else if (!test_bit(NFS4CLNT_OK, &clp->cl_state)) - schedule(); - finish_wait(&clp->cl_waitq, &wait); + res = wait_on_bit(&clp->cl_state, NFS4CLNT_STATE_RECOVER, + nfs4_wait_bit_interruptible, + TASK_INTERRUPTIBLE); rpc_clnt_sigunmask(clnt, &oldset); return res; } @@ -2229,14 +2768,11 @@ static int nfs4_delay(struct rpc_clnt *clnt, long *timeout) *timeout = NFS4_POLL_RETRY_MAX; rpc_clnt_sigmask(clnt, &oldset); if (clnt->cl_intr) { - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(*timeout); + schedule_timeout_interruptible(*timeout); if (signalled()) res = -ERESTARTSYS; - } else { - set_current_state(TASK_UNINTERRUPTIBLE); - schedule_timeout(*timeout); - } + } else + schedule_timeout_uninterruptible(*timeout); rpc_clnt_sigunmask(clnt, &oldset); *timeout <<= 1; return res; @@ -2245,7 +2781,7 @@ static int nfs4_delay(struct rpc_clnt *clnt, long *timeout) /* This is the error handling routine for processes that are allowed * to sleep. */ -int nfs4_handle_exception(struct nfs_server *server, int errorcode, struct nfs4_exception *exception) +int nfs4_handle_exception(const struct nfs_server *server, int errorcode, struct nfs4_exception *exception) { struct nfs4_client *clp = server->nfs4_state; int ret = errorcode; @@ -2257,6 +2793,7 @@ int nfs4_handle_exception(struct nfs_server *server, int errorcode, struct nfs4_ case -NFS4ERR_STALE_CLIENTID: case -NFS4ERR_STALE_STATEID: case -NFS4ERR_EXPIRED: + nfs4_schedule_state_recovery(clp); ret = nfs4_wait_clnt_recover(server->client, clp); if (ret == 0) exception->retry = 1; @@ -2264,18 +2801,16 @@ int nfs4_handle_exception(struct nfs_server *server, int errorcode, struct nfs4_ case -NFS4ERR_GRACE: case -NFS4ERR_DELAY: ret = nfs4_delay(server->client, &exception->timeout); - if (ret == 0) - exception->retry = 1; - break; + if (ret != 0) + break; case -NFS4ERR_OLD_STATEID: - if (ret == 0) - exception->retry = 1; + exception->retry = 1; } /* We failed to handle the error */ return nfs4_map_errors(ret); } -int nfs4_proc_setclientid(struct nfs4_client *clp, u32 program, unsigned short port) +int nfs4_proc_setclientid(struct nfs4_client *clp, u32 program, unsigned short port, struct rpc_cred *cred) { nfs4_verifier sc_verifier; struct nfs4_setclientid setclientid = { @@ -2286,7 +2821,7 @@ int nfs4_proc_setclientid(struct nfs4_client *clp, u32 program, unsigned short p .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SETCLIENTID], .rpc_argp = &setclientid, .rpc_resp = clp, - .rpc_cred = clp->cl_cred, + .rpc_cred = cred, }; u32 *p; int loop = 0; @@ -2300,7 +2835,7 @@ int nfs4_proc_setclientid(struct nfs4_client *clp, u32 program, unsigned short p setclientid.sc_name_len = scnprintf(setclientid.sc_name, sizeof(setclientid.sc_name), "%s/%u.%u.%u.%u %s %u", clp->cl_ipaddr, NIPQUAD(clp->cl_addr.s_addr), - clp->cl_cred->cr_ops->cr_name, + cred->cr_ops->cr_name, clp->cl_id_uniquifier); setclientid.sc_netid_len = scnprintf(setclientid.sc_netid, sizeof(setclientid.sc_netid), "tcp"); @@ -2322,15 +2857,14 @@ int nfs4_proc_setclientid(struct nfs4_client *clp, u32 program, unsigned short p return status; } -int -nfs4_proc_setclientid_confirm(struct nfs4_client *clp) +static int _nfs4_proc_setclientid_confirm(struct nfs4_client *clp, struct rpc_cred *cred) { struct nfs_fsinfo fsinfo; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SETCLIENTID_CONFIRM], .rpc_argp = clp, .rpc_resp = &fsinfo, - .rpc_cred = clp->cl_cred, + .rpc_cred = cred, }; unsigned long now; int status; @@ -2341,24 +2875,108 @@ nfs4_proc_setclientid_confirm(struct nfs4_client *clp) spin_lock(&clp->cl_lock); clp->cl_lease_time = fsinfo.lease_time * HZ; clp->cl_last_renewal = now; + clear_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state); spin_unlock(&clp->cl_lock); } return status; } -static int _nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid) +int nfs4_proc_setclientid_confirm(struct nfs4_client *clp, struct rpc_cred *cred) { - struct nfs4_delegreturnargs args = { - .fhandle = NFS_FH(inode), - .stateid = stateid, - }; + long timeout; + int err; + do { + err = _nfs4_proc_setclientid_confirm(clp, cred); + switch (err) { + case 0: + return err; + case -NFS4ERR_RESOURCE: + /* The IBM lawyers misread another document! */ + case -NFS4ERR_DELAY: + err = nfs4_delay(clp->cl_rpcclient, &timeout); + } + } while (err == 0); + return err; +} + +struct nfs4_delegreturndata { + struct nfs4_delegreturnargs args; + struct nfs4_delegreturnres res; + struct nfs_fh fh; + nfs4_stateid stateid; + struct rpc_cred *cred; + unsigned long timestamp; + struct nfs_fattr fattr; + int rpc_status; +}; + +static void nfs4_delegreturn_prepare(struct rpc_task *task, void *calldata) +{ + struct nfs4_delegreturndata *data = calldata; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_DELEGRETURN], - .rpc_argp = &args, - .rpc_cred = cred, + .rpc_argp = &data->args, + .rpc_resp = &data->res, + .rpc_cred = data->cred, }; + nfs_fattr_init(data->res.fattr); + rpc_call_setup(task, &msg, 0); +} - return rpc_call_sync(NFS_CLIENT(inode), &msg, 0); +static void nfs4_delegreturn_done(struct rpc_task *task, void *calldata) +{ + struct nfs4_delegreturndata *data = calldata; + data->rpc_status = task->tk_status; + if (data->rpc_status == 0) + renew_lease(data->res.server, data->timestamp); +} + +static void nfs4_delegreturn_release(void *calldata) +{ + struct nfs4_delegreturndata *data = calldata; + + put_rpccred(data->cred); + kfree(calldata); +} + +static const struct rpc_call_ops nfs4_delegreturn_ops = { + .rpc_call_prepare = nfs4_delegreturn_prepare, + .rpc_call_done = nfs4_delegreturn_done, + .rpc_release = nfs4_delegreturn_release, +}; + +static int _nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid) +{ + struct nfs4_delegreturndata *data; + struct nfs_server *server = NFS_SERVER(inode); + struct rpc_task *task; + int status; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + data->args.fhandle = &data->fh; + data->args.stateid = &data->stateid; + data->args.bitmask = server->attr_bitmask; + nfs_copy_fh(&data->fh, NFS_FH(inode)); + memcpy(&data->stateid, stateid, sizeof(data->stateid)); + data->res.fattr = &data->fattr; + data->res.server = server; + data->cred = get_rpccred(cred); + data->timestamp = jiffies; + data->rpc_status = 0; + + task = rpc_run_task(NFS_CLIENT(inode), RPC_TASK_ASYNC, &nfs4_delegreturn_ops, data); + if (IS_ERR(task)) + return PTR_ERR(task); + status = nfs4_wait_for_completion_rpc_task(task); + if (status == 0) { + status = data->rpc_status; + if (status == 0) + nfs_post_op_update_inode(inode, &data->fattr); + } + rpc_release_task(task); + return status; } int nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid) @@ -2389,51 +3007,24 @@ int nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4 static unsigned long nfs4_set_lock_task_retry(unsigned long timeout) { - current->state = TASK_INTERRUPTIBLE; - schedule_timeout(timeout); + schedule_timeout_interruptible(timeout); timeout <<= 1; if (timeout > NFS4_LOCK_MAXTIMEOUT) return NFS4_LOCK_MAXTIMEOUT; return timeout; } -static inline int -nfs4_lck_type(int cmd, struct file_lock *request) -{ - /* set lock type */ - switch (request->fl_type) { - case F_RDLCK: - return IS_SETLKW(cmd) ? NFS4_READW_LT : NFS4_READ_LT; - case F_WRLCK: - return IS_SETLKW(cmd) ? NFS4_WRITEW_LT : NFS4_WRITE_LT; - case F_UNLCK: - return NFS4_WRITE_LT; - } - BUG(); - return 0; -} - -static inline uint64_t -nfs4_lck_length(struct file_lock *request) -{ - if (request->fl_end == OFFSET_MAX) - return ~(uint64_t)0; - return request->fl_end - request->fl_start + 1; -} - static int _nfs4_proc_getlk(struct nfs4_state *state, int cmd, struct file_lock *request) { struct inode *inode = state->inode; struct nfs_server *server = NFS_SERVER(inode); struct nfs4_client *clp = server->nfs4_state; - struct nfs_lockargs arg = { + struct nfs_lockt_args arg = { .fh = NFS_FH(inode), - .type = nfs4_lck_type(cmd, request), - .offset = request->fl_start, - .length = nfs4_lck_length(request), + .fl = request, }; - struct nfs_lockres res = { - .server = server, + struct nfs_lockt_res res = { + .denied = request, }; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_LOCKT], @@ -2441,44 +3032,25 @@ static int _nfs4_proc_getlk(struct nfs4_state *state, int cmd, struct file_lock .rpc_resp = &res, .rpc_cred = state->owner->so_cred, }; - struct nfs_lowner nlo; struct nfs4_lock_state *lsp; int status; down_read(&clp->cl_sem); - nlo.clientid = clp->cl_clientid; - down(&state->lock_sema); - lsp = nfs4_find_lock_state(state, request->fl_owner); - if (lsp) - nlo.id = lsp->ls_id; - else { - spin_lock(&clp->cl_lock); - nlo.id = nfs4_alloc_lockowner_id(clp); - spin_unlock(&clp->cl_lock); - } - arg.u.lockt = &nlo; + arg.lock_owner.clientid = clp->cl_clientid; + status = nfs4_set_lock_state(state, request); + if (status != 0) + goto out; + lsp = request->fl_u.nfs4_fl.owner; + arg.lock_owner.id = lsp->ls_id; status = rpc_call_sync(server->client, &msg, 0); - if (!status) { - request->fl_type = F_UNLCK; - } else if (status == -NFS4ERR_DENIED) { - int64_t len, start, end; - start = res.u.denied.offset; - len = res.u.denied.length; - end = start + len - 1; - if (end < 0 || len == 0) - request->fl_end = OFFSET_MAX; - else - request->fl_end = (loff_t)end; - request->fl_start = (loff_t)start; - request->fl_type = F_WRLCK; - if (res.u.denied.type & 1) - request->fl_type = F_RDLCK; - request->fl_pid = 0; - status = 0; - } - if (lsp) - nfs4_put_lock_state(lsp); - up(&state->lock_sema); + switch (status) { + case 0: + request->fl_type = F_UNLCK; + break; + case -NFS4ERR_DENIED: + status = 0; + } +out: up_read(&clp->cl_sem); return status; } @@ -2515,153 +3087,345 @@ static int do_vfs_lock(struct file *file, struct file_lock *fl) return res; } -static int _nfs4_proc_unlck(struct nfs4_state *state, int cmd, struct file_lock *request) +struct nfs4_unlockdata { + struct nfs_locku_args arg; + struct nfs_locku_res res; + struct nfs4_lock_state *lsp; + struct nfs_open_context *ctx; + struct file_lock fl; + const struct nfs_server *server; + unsigned long timestamp; +}; + +static struct nfs4_unlockdata *nfs4_alloc_unlockdata(struct file_lock *fl, + struct nfs_open_context *ctx, + struct nfs4_lock_state *lsp, + struct nfs_seqid *seqid) +{ + struct nfs4_unlockdata *p; + struct inode *inode = lsp->ls_state->inode; + + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) + return NULL; + p->arg.fh = NFS_FH(inode); + p->arg.fl = &p->fl; + p->arg.seqid = seqid; + p->arg.stateid = &lsp->ls_stateid; + p->lsp = lsp; + atomic_inc(&lsp->ls_count); + /* Ensure we don't close file until we're done freeing locks! */ + p->ctx = get_nfs_open_context(ctx); + memcpy(&p->fl, fl, sizeof(p->fl)); + p->server = NFS_SERVER(inode); + return p; +} + +static void nfs4_locku_release_calldata(void *data) +{ + struct nfs4_unlockdata *calldata = data; + nfs_free_seqid(calldata->arg.seqid); + nfs4_put_lock_state(calldata->lsp); + put_nfs_open_context(calldata->ctx); + kfree(calldata); +} + +static void nfs4_locku_done(struct rpc_task *task, void *data) { - struct inode *inode = state->inode; - struct nfs_server *server = NFS_SERVER(inode); - struct nfs4_client *clp = server->nfs4_state; - struct nfs_lockargs arg = { - .fh = NFS_FH(inode), - .type = nfs4_lck_type(cmd, request), - .offset = request->fl_start, - .length = nfs4_lck_length(request), - }; - struct nfs_lockres res = { - .server = server, - }; + struct nfs4_unlockdata *calldata = data; + + if (RPC_ASSASSINATED(task)) + return; + nfs_increment_lock_seqid(task->tk_status, calldata->arg.seqid); + switch (task->tk_status) { + case 0: + memcpy(calldata->lsp->ls_stateid.data, + calldata->res.stateid.data, + sizeof(calldata->lsp->ls_stateid.data)); + renew_lease(calldata->server, calldata->timestamp); + break; + case -NFS4ERR_STALE_STATEID: + case -NFS4ERR_EXPIRED: + nfs4_schedule_state_recovery(calldata->server->nfs4_state); + break; + default: + if (nfs4_async_handle_error(task, calldata->server) == -EAGAIN) { + rpc_restart_call(task); + } + } +} + +static void nfs4_locku_prepare(struct rpc_task *task, void *data) +{ + struct nfs4_unlockdata *calldata = data; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_LOCKU], - .rpc_argp = &arg, - .rpc_resp = &res, - .rpc_cred = state->owner->so_cred, + .rpc_argp = &calldata->arg, + .rpc_resp = &calldata->res, + .rpc_cred = calldata->lsp->ls_state->owner->so_cred, }; - struct nfs4_lock_state *lsp; - struct nfs_locku_opargs luargs; - int status = 0; - - down_read(&clp->cl_sem); - down(&state->lock_sema); - lsp = nfs4_find_lock_state(state, request->fl_owner); - if (!lsp) - goto out; - /* We might have lost the locks! */ - if ((lsp->ls_flags & NFS_LOCK_INITIALIZED) != 0) { - luargs.seqid = lsp->ls_seqid; - memcpy(&luargs.stateid, &lsp->ls_stateid, sizeof(luargs.stateid)); - arg.u.locku = &luargs; - status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR); - nfs4_increment_lock_seqid(status, lsp); + + if (nfs_wait_on_sequence(calldata->arg.seqid, task) != 0) + return; + if ((calldata->lsp->ls_flags & NFS_LOCK_INITIALIZED) == 0) { + /* Note: exit _without_ running nfs4_locku_done */ + task->tk_action = NULL; + return; } + calldata->timestamp = jiffies; + rpc_call_setup(task, &msg, 0); +} - if (status == 0) { - memcpy(&lsp->ls_stateid, &res.u.stateid, - sizeof(lsp->ls_stateid)); - nfs4_notify_unlck(state, request, lsp); +static const struct rpc_call_ops nfs4_locku_ops = { + .rpc_call_prepare = nfs4_locku_prepare, + .rpc_call_done = nfs4_locku_done, + .rpc_release = nfs4_locku_release_calldata, +}; + +static struct rpc_task *nfs4_do_unlck(struct file_lock *fl, + struct nfs_open_context *ctx, + struct nfs4_lock_state *lsp, + struct nfs_seqid *seqid) +{ + struct nfs4_unlockdata *data; + + data = nfs4_alloc_unlockdata(fl, ctx, lsp, seqid); + if (data == NULL) { + nfs_free_seqid(seqid); + return ERR_PTR(-ENOMEM); } - nfs4_put_lock_state(lsp); -out: - up(&state->lock_sema); - if (status == 0) - do_vfs_lock(request->fl_file, request); - up_read(&clp->cl_sem); - return status; + + /* Unlock _before_ we do the RPC call */ + do_vfs_lock(fl->fl_file, fl); + return rpc_run_task(NFS_CLIENT(lsp->ls_state->inode), RPC_TASK_ASYNC, &nfs4_locku_ops, data); } static int nfs4_proc_unlck(struct nfs4_state *state, int cmd, struct file_lock *request) { - struct nfs4_exception exception = { }; - int err; + struct nfs_seqid *seqid; + struct nfs4_lock_state *lsp; + struct rpc_task *task; + int status = 0; - do { - err = nfs4_handle_exception(NFS_SERVER(state->inode), - _nfs4_proc_unlck(state, cmd, request), - &exception); - } while (exception.retry); - return err; + /* Is this a delegated lock? */ + if (test_bit(NFS_DELEGATED_STATE, &state->flags)) + goto out_unlock; + /* Is this open_owner holding any locks on the server? */ + if (test_bit(LK_STATE_IN_USE, &state->flags) == 0) + goto out_unlock; + + status = nfs4_set_lock_state(state, request); + if (status != 0) + goto out_unlock; + lsp = request->fl_u.nfs4_fl.owner; + status = -ENOMEM; + seqid = nfs_alloc_seqid(&lsp->ls_seqid); + if (seqid == NULL) + goto out_unlock; + task = nfs4_do_unlck(request, request->fl_file->private_data, lsp, seqid); + status = PTR_ERR(task); + if (IS_ERR(task)) + goto out_unlock; + status = nfs4_wait_for_completion_rpc_task(task); + rpc_release_task(task); + return status; +out_unlock: + do_vfs_lock(request->fl_file, request); + return status; } -static int _nfs4_do_setlk(struct nfs4_state *state, int cmd, struct file_lock *request, int reclaim) +struct nfs4_lockdata { + struct nfs_lock_args arg; + struct nfs_lock_res res; + struct nfs4_lock_state *lsp; + struct nfs_open_context *ctx; + struct file_lock fl; + unsigned long timestamp; + int rpc_status; + int cancelled; +}; + +static struct nfs4_lockdata *nfs4_alloc_lockdata(struct file_lock *fl, + struct nfs_open_context *ctx, struct nfs4_lock_state *lsp) { - struct inode *inode = state->inode; + struct nfs4_lockdata *p; + struct inode *inode = lsp->ls_state->inode; struct nfs_server *server = NFS_SERVER(inode); - struct nfs4_lock_state *lsp; - struct nfs_lockargs arg = { - .fh = NFS_FH(inode), - .type = nfs4_lck_type(cmd, request), - .offset = request->fl_start, - .length = nfs4_lck_length(request), - }; - struct nfs_lockres res = { - .server = server, - }; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) + return NULL; + + p->arg.fh = NFS_FH(inode); + p->arg.fl = &p->fl; + p->arg.lock_seqid = nfs_alloc_seqid(&lsp->ls_seqid); + if (p->arg.lock_seqid == NULL) + goto out_free; + p->arg.lock_stateid = &lsp->ls_stateid; + p->arg.lock_owner.clientid = server->nfs4_state->cl_clientid; + p->arg.lock_owner.id = lsp->ls_id; + p->lsp = lsp; + atomic_inc(&lsp->ls_count); + p->ctx = get_nfs_open_context(ctx); + memcpy(&p->fl, fl, sizeof(p->fl)); + return p; +out_free: + kfree(p); + return NULL; +} + +static void nfs4_lock_prepare(struct rpc_task *task, void *calldata) +{ + struct nfs4_lockdata *data = calldata; + struct nfs4_state *state = data->lsp->ls_state; + struct nfs4_state_owner *sp = state->owner; struct rpc_message msg = { - .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_LOCK], - .rpc_argp = &arg, - .rpc_resp = &res, - .rpc_cred = state->owner->so_cred, - }; - struct nfs_lock_opargs largs = { - .reclaim = reclaim, - .new_lock_owner = 0, + .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_LOCK], + .rpc_argp = &data->arg, + .rpc_resp = &data->res, + .rpc_cred = sp->so_cred, }; - int status; - lsp = nfs4_get_lock_state(state, request->fl_owner); - if (lsp == NULL) + if (nfs_wait_on_sequence(data->arg.lock_seqid, task) != 0) + return; + dprintk("%s: begin!\n", __FUNCTION__); + /* Do we need to do an open_to_lock_owner? */ + if (!(data->arg.lock_seqid->sequence->flags & NFS_SEQID_CONFIRMED)) { + data->arg.open_seqid = nfs_alloc_seqid(&sp->so_seqid); + if (data->arg.open_seqid == NULL) { + data->rpc_status = -ENOMEM; + task->tk_action = NULL; + goto out; + } + data->arg.open_stateid = &state->stateid; + data->arg.new_lock_owner = 1; + } + data->timestamp = jiffies; + rpc_call_setup(task, &msg, 0); +out: + dprintk("%s: done!, ret = %d\n", __FUNCTION__, data->rpc_status); +} + +static void nfs4_lock_done(struct rpc_task *task, void *calldata) +{ + struct nfs4_lockdata *data = calldata; + + dprintk("%s: begin!\n", __FUNCTION__); + + data->rpc_status = task->tk_status; + if (RPC_ASSASSINATED(task)) + goto out; + if (data->arg.new_lock_owner != 0) { + nfs_increment_open_seqid(data->rpc_status, data->arg.open_seqid); + if (data->rpc_status == 0) + nfs_confirm_seqid(&data->lsp->ls_seqid, 0); + else + goto out; + } + if (data->rpc_status == 0) { + memcpy(data->lsp->ls_stateid.data, data->res.stateid.data, + sizeof(data->lsp->ls_stateid.data)); + data->lsp->ls_flags |= NFS_LOCK_INITIALIZED; + renew_lease(NFS_SERVER(data->ctx->dentry->d_inode), data->timestamp); + } + nfs_increment_lock_seqid(data->rpc_status, data->arg.lock_seqid); +out: + dprintk("%s: done, ret = %d!\n", __FUNCTION__, data->rpc_status); +} + +static void nfs4_lock_release(void *calldata) +{ + struct nfs4_lockdata *data = calldata; + + dprintk("%s: begin!\n", __FUNCTION__); + if (data->arg.open_seqid != NULL) + nfs_free_seqid(data->arg.open_seqid); + if (data->cancelled != 0) { + struct rpc_task *task; + task = nfs4_do_unlck(&data->fl, data->ctx, data->lsp, + data->arg.lock_seqid); + if (!IS_ERR(task)) + rpc_release_task(task); + dprintk("%s: cancelling lock!\n", __FUNCTION__); + } else + nfs_free_seqid(data->arg.lock_seqid); + nfs4_put_lock_state(data->lsp); + put_nfs_open_context(data->ctx); + kfree(data); + dprintk("%s: done!\n", __FUNCTION__); +} + +static const struct rpc_call_ops nfs4_lock_ops = { + .rpc_call_prepare = nfs4_lock_prepare, + .rpc_call_done = nfs4_lock_done, + .rpc_release = nfs4_lock_release, +}; + +static int _nfs4_do_setlk(struct nfs4_state *state, int cmd, struct file_lock *fl, int reclaim) +{ + struct nfs4_lockdata *data; + struct rpc_task *task; + int ret; + + dprintk("%s: begin!\n", __FUNCTION__); + data = nfs4_alloc_lockdata(fl, fl->fl_file->private_data, + fl->fl_u.nfs4_fl.owner); + if (data == NULL) return -ENOMEM; - if (!(lsp->ls_flags & NFS_LOCK_INITIALIZED)) { - struct nfs4_state_owner *owner = state->owner; - struct nfs_open_to_lock otl = { - .lock_owner = { - .clientid = server->nfs4_state->cl_clientid, - }, - }; - - otl.lock_seqid = lsp->ls_seqid; - otl.lock_owner.id = lsp->ls_id; - memcpy(&otl.open_stateid, &state->stateid, sizeof(otl.open_stateid)); - largs.u.open_lock = &otl; - largs.new_lock_owner = 1; - arg.u.lock = &largs; - down(&owner->so_sema); - otl.open_seqid = owner->so_seqid; - status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR); - /* increment open_owner seqid on success, and - * seqid mutating errors */ - nfs4_increment_seqid(status, owner); - up(&owner->so_sema); - } else { - struct nfs_exist_lock el = { - .seqid = lsp->ls_seqid, - }; - memcpy(&el.stateid, &lsp->ls_stateid, sizeof(el.stateid)); - largs.u.exist_lock = ⪙ - largs.new_lock_owner = 0; - arg.u.lock = &largs; - status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR); - } - /* increment seqid on success, and * seqid mutating errors*/ - nfs4_increment_lock_seqid(status, lsp); - /* save the returned stateid. */ - if (status == 0) { - memcpy(&lsp->ls_stateid, &res.u.stateid, sizeof(nfs4_stateid)); - lsp->ls_flags |= NFS_LOCK_INITIALIZED; - if (!reclaim) - nfs4_notify_setlk(state, request, lsp); - } else if (status == -NFS4ERR_DENIED) - status = -EAGAIN; - nfs4_put_lock_state(lsp); - return status; + if (IS_SETLKW(cmd)) + data->arg.block = 1; + if (reclaim != 0) + data->arg.reclaim = 1; + task = rpc_run_task(NFS_CLIENT(state->inode), RPC_TASK_ASYNC, + &nfs4_lock_ops, data); + if (IS_ERR(task)) + return PTR_ERR(task); + ret = nfs4_wait_for_completion_rpc_task(task); + if (ret == 0) { + ret = data->rpc_status; + if (ret == -NFS4ERR_DENIED) + ret = -EAGAIN; + } else + data->cancelled = 1; + rpc_release_task(task); + dprintk("%s: done, ret = %d!\n", __FUNCTION__, ret); + return ret; } static int nfs4_lock_reclaim(struct nfs4_state *state, struct file_lock *request) { - return _nfs4_do_setlk(state, F_SETLK, request, 1); + struct nfs_server *server = NFS_SERVER(state->inode); + struct nfs4_exception exception = { }; + int err; + + /* Cache the lock if possible... */ + if (test_bit(NFS_DELEGATED_STATE, &state->flags)) + return 0; + do { + err = _nfs4_do_setlk(state, F_SETLK, request, 1); + if (err != -NFS4ERR_DELAY) + break; + nfs4_handle_exception(server, err, &exception); + } while (exception.retry); + return err; } static int nfs4_lock_expired(struct nfs4_state *state, struct file_lock *request) { - return _nfs4_do_setlk(state, F_SETLK, request, 0); + struct nfs_server *server = NFS_SERVER(state->inode); + struct nfs4_exception exception = { }; + int err; + + err = nfs4_set_lock_state(state, request); + if (err != 0) + return err; + do { + err = _nfs4_do_setlk(state, F_SETLK, request, 0); + if (err != -NFS4ERR_DELAY) + break; + nfs4_handle_exception(server, err, &exception); + } while (exception.retry); + return err; } static int _nfs4_proc_setlk(struct nfs4_state *state, int cmd, struct file_lock *request) @@ -2669,16 +3433,26 @@ static int _nfs4_proc_setlk(struct nfs4_state *state, int cmd, struct file_lock struct nfs4_client *clp = state->owner->so_client; int status; + /* Is this a delegated open? */ + if (NFS_I(state->inode)->delegation_state != 0) { + /* Yes: cache locks! */ + status = do_vfs_lock(request->fl_file, request); + /* ...but avoid races with delegation recall... */ + if (status < 0 || test_bit(NFS_DELEGATED_STATE, &state->flags)) + return status; + } down_read(&clp->cl_sem); - down(&state->lock_sema); + status = nfs4_set_lock_state(state, request); + if (status != 0) + goto out; status = _nfs4_do_setlk(state, cmd, request, 0); - up(&state->lock_sema); - if (status == 0) { - /* Note: we always want to sleep here! */ - request->fl_flags |= FL_SLEEP; - if (do_vfs_lock(request->fl_file, request) < 0) - printk(KERN_WARNING "%s: VFS is out of sync with lock manager!\n", __FUNCTION__); - } + if (status != 0) + goto out; + /* Note: we always want to sleep here! */ + request->fl_flags |= FL_SLEEP; + if (do_vfs_lock(request->fl_file, request) < 0) + printk(KERN_WARNING "%s: VFS is out of sync with lock manager!\n", __FUNCTION__); +out: up_read(&clp->cl_sem); return status; } @@ -2729,10 +3503,73 @@ nfs4_proc_lock(struct file *filp, int cmd, struct file_lock *request) if (signalled()) break; } while(status < 0); - return status; } +int nfs4_lock_delegation_recall(struct nfs4_state *state, struct file_lock *fl) +{ + struct nfs_server *server = NFS_SERVER(state->inode); + struct nfs4_exception exception = { }; + int err; + + err = nfs4_set_lock_state(state, fl); + if (err != 0) + goto out; + do { + err = _nfs4_do_setlk(state, F_SETLK, fl, 0); + if (err != -NFS4ERR_DELAY) + break; + err = nfs4_handle_exception(server, err, &exception); + } while (exception.retry); +out: + return err; +} + +#define XATTR_NAME_NFSV4_ACL "system.nfs4_acl" + +int nfs4_setxattr(struct dentry *dentry, const char *key, const void *buf, + size_t buflen, int flags) +{ + struct inode *inode = dentry->d_inode; + + if (strcmp(key, XATTR_NAME_NFSV4_ACL) != 0) + return -EOPNOTSUPP; + + if (!S_ISREG(inode->i_mode) && + (!S_ISDIR(inode->i_mode) || inode->i_mode & S_ISVTX)) + return -EPERM; + + return nfs4_proc_set_acl(inode, buf, buflen); +} + +/* The getxattr man page suggests returning -ENODATA for unknown attributes, + * and that's what we'll do for e.g. user attributes that haven't been set. + * But we'll follow ext2/ext3's lead by returning -EOPNOTSUPP for unsupported + * attributes in kernel-managed attribute namespaces. */ +ssize_t nfs4_getxattr(struct dentry *dentry, const char *key, void *buf, + size_t buflen) +{ + struct inode *inode = dentry->d_inode; + + if (strcmp(key, XATTR_NAME_NFSV4_ACL) != 0) + return -EOPNOTSUPP; + + return nfs4_proc_get_acl(inode, buf, buflen); +} + +ssize_t nfs4_listxattr(struct dentry *dentry, char *buf, size_t buflen) +{ + size_t len = strlen(XATTR_NAME_NFSV4_ACL) + 1; + + if (!nfs4_server_supports_acls(NFS_SERVER(dentry->d_inode))) + return 0; + if (buf && buflen < len) + return -ERANGE; + if (buf) + memcpy(buf, XATTR_NAME_NFSV4_ACL, len); + return len; +} + struct nfs4_state_recovery_ops nfs4_reboot_recovery_ops = { .recover_open = nfs4_open_reclaim, .recover_lock = nfs4_lock_reclaim, @@ -2743,10 +3580,20 @@ struct nfs4_state_recovery_ops nfs4_network_partition_recovery_ops = { .recover_lock = nfs4_lock_expired, }; +static struct inode_operations nfs4_file_inode_operations = { + .permission = nfs_permission, + .getattr = nfs_getattr, + .setattr = nfs_setattr, + .getxattr = nfs4_getxattr, + .setxattr = nfs4_setxattr, + .listxattr = nfs4_listxattr, +}; + struct nfs_rpc_ops nfs_v4_clientops = { .version = 4, /* protocol version */ .dentry_ops = &nfs4_dentry_operations, .dir_inode_ops = &nfs4_dir_inode_operations, + .file_inode_ops = &nfs4_file_inode_operations, .getroot = nfs4_proc_get_root, .getattr = nfs4_proc_getattr, .setattr = nfs4_proc_setattr, @@ -2772,11 +3619,15 @@ struct nfs_rpc_ops nfs_v4_clientops = { .pathconf = nfs4_proc_pathconf, .decode_dirent = nfs4_decode_dirent, .read_setup = nfs4_proc_read_setup, + .read_done = nfs4_read_done, .write_setup = nfs4_proc_write_setup, + .write_done = nfs4_write_done, .commit_setup = nfs4_proc_commit_setup, - .file_open = nfs4_proc_file_open, - .file_release = nfs4_proc_file_release, + .commit_done = nfs4_commit_done, + .file_open = nfs_open, + .file_release = nfs_release, .lock = nfs4_proc_lock, + .clear_acl_cache = nfs4_zap_acl_attr, }; /*