Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
* fs/nfs/nfs4proc.c
*
* Client-side procedure declarations for NFSv4.
*
* Copyright (c) 2002 The Regents of the University of Michigan.
* All rights reserved.
*
* Kendrick Smith <kmsmith@umich.edu>
* Andy Adamson <andros@umich.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/mm.h>
#include <linux/utsname.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfs.h>
#include <linux/nfs4.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_page.h>
#include <linux/smp_lock.h>
#include <linux/namei.h>
#include "delegation.h"
#define NFSDBG_FACILITY NFSDBG_PROC
#define NFS4_POLL_RETRY_MIN (1*HZ)
#define NFS4_POLL_RETRY_MAX (15*HZ)

Trond Myklebust
committed
static int _nfs4_proc_open_confirm(struct rpc_clnt *clnt, const struct nfs_fh *fh, struct nfs4_state_owner *sp, nfs4_stateid *stateid, struct nfs_seqid *seqid);
static int nfs4_do_fsinfo(struct nfs_server *, struct nfs_fh *, struct nfs_fsinfo *);
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(const struct nfs_server *server, int errorcode, struct nfs4_exception *exception);
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
extern u32 *nfs4_decode_dirent(u32 *p, struct nfs_entry *entry, int plus);
extern struct rpc_procinfo nfs4_procedures[];
/* Prevent leaks of NFSv4 errors into userland */
int nfs4_map_errors(int err)
{
if (err < -1000) {
dprintk("%s could not handle NFSv4 error %d\n",
__FUNCTION__, -err);
return -EIO;
}
return err;
}
/*
* This is our standard bitmap for GETATTR requests.
*/
const u32 nfs4_fattr_bitmap[2] = {
FATTR4_WORD0_TYPE
| FATTR4_WORD0_CHANGE
| FATTR4_WORD0_SIZE
| FATTR4_WORD0_FSID
| FATTR4_WORD0_FILEID,
FATTR4_WORD1_MODE
| FATTR4_WORD1_NUMLINKS
| FATTR4_WORD1_OWNER
| FATTR4_WORD1_OWNER_GROUP
| FATTR4_WORD1_RAWDEV
| FATTR4_WORD1_SPACE_USED
| FATTR4_WORD1_TIME_ACCESS
| FATTR4_WORD1_TIME_METADATA
| FATTR4_WORD1_TIME_MODIFY
};
const u32 nfs4_statfs_bitmap[2] = {
FATTR4_WORD0_FILES_AVAIL
| FATTR4_WORD0_FILES_FREE
| FATTR4_WORD0_FILES_TOTAL,
FATTR4_WORD1_SPACE_AVAIL
| FATTR4_WORD1_SPACE_FREE
| FATTR4_WORD1_SPACE_TOTAL
};
const u32 nfs4_pathconf_bitmap[2] = {
FATTR4_WORD0_MAXLINK
| FATTR4_WORD0_MAXNAME,
0
};
const u32 nfs4_fsinfo_bitmap[2] = { FATTR4_WORD0_MAXFILESIZE
| FATTR4_WORD0_MAXREAD
| FATTR4_WORD0_MAXWRITE
| FATTR4_WORD0_LEASE_TIME,
0
};
static void nfs4_setup_readdir(u64 cookie, u32 *verifier, struct dentry *dentry,
struct nfs4_readdir_arg *readdir)
{
u32 *start, *p;
BUG_ON(readdir->count < 80);
if (cookie > 2) {
readdir->cookie = cookie;
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
memcpy(&readdir->verifier, verifier, sizeof(readdir->verifier));
return;
}
readdir->cookie = 0;
memset(&readdir->verifier, 0, sizeof(readdir->verifier));
if (cookie == 2)
return;
/*
* NFSv4 servers do not return entries for '.' and '..'
* Therefore, we fake these entries here. We let '.'
* have cookie 0 and '..' have cookie 1. Note that
* when talking to the server, we always send cookie 0
* instead of 1 or 2.
*/
start = p = (u32 *)kmap_atomic(*readdir->pages, KM_USER0);
if (cookie == 0) {
*p++ = xdr_one; /* next */
*p++ = xdr_zero; /* cookie, first word */
*p++ = xdr_one; /* cookie, second word */
*p++ = xdr_one; /* entry len */
memcpy(p, ".\0\0\0", 4); /* entry */
p++;
*p++ = xdr_one; /* bitmap length */
*p++ = htonl(FATTR4_WORD0_FILEID); /* bitmap */
*p++ = htonl(8); /* attribute buffer length */
p = xdr_encode_hyper(p, dentry->d_inode->i_ino);
}
*p++ = xdr_one; /* next */
*p++ = xdr_zero; /* cookie, first word */
*p++ = xdr_two; /* cookie, second word */
*p++ = xdr_two; /* entry len */
memcpy(p, "..\0\0", 4); /* entry */
p++;
*p++ = xdr_one; /* bitmap length */
*p++ = htonl(FATTR4_WORD0_FILEID); /* bitmap */
*p++ = htonl(8); /* attribute buffer length */
p = xdr_encode_hyper(p, dentry->d_parent->d_inode->i_ino);
readdir->pgbase = (char *)p - (char *)start;
readdir->count -= readdir->pgbase;
kunmap_atomic(start, KM_USER0);
}
static void
renew_lease(struct nfs_server *server, unsigned long timestamp)
{
struct nfs4_client *clp = server->nfs4_state;
spin_lock(&clp->cl_lock);
if (time_before(clp->cl_last_renewal,timestamp))
clp->cl_last_renewal = timestamp;
spin_unlock(&clp->cl_lock);
}
static void update_changeattr(struct inode *inode, struct nfs4_change_info *cinfo)
{
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);
/* Helper for asynchronous RPC calls */
static int nfs4_call_async(struct rpc_clnt *clnt,
const struct rpc_call_ops *tk_ops, void *calldata)
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 void update_open_stateid(struct nfs4_state *state, nfs4_stateid *stateid, int open_flags)
{
struct inode *inode = state->inode;
open_flags &= (FMODE_READ|FMODE_WRITE);
/* Protect against nfs4_find_state_byowner() */
spin_lock(&state->owner->so_lock);
memcpy(&state->stateid, stateid, sizeof(state->stateid));
if ((open_flags & FMODE_WRITE))
state->nwriters++;
nfs4_state_set_mode_locked(state, state->state | open_flags);
spin_unlock(&state->owner->so_lock);
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
}
/*
* OPEN_RECLAIM:
* reclaim state on the server after a reboot.
*/
static int _nfs4_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state)
{
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),
.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,
};
int status;
if (delegation != NULL) {
if (!(delegation->flags & NFS_DELEGATION_NEED_RECLAIM)) {
memcpy(&state->stateid, &delegation->stateid,
sizeof(state->stateid));
set_bit(NFS_DELEGATED_STATE, &state->flags);
return 0;
}
o_arg.u.delegation_type = delegation->type;
}
o_arg.seqid = nfs_alloc_seqid(&sp->so_seqid);
if (o_arg.seqid == NULL)
return -ENOMEM;
status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR);
/* Confirm the sequence as being established */
nfs_confirm_seqid(&sp->so_seqid, status);
nfs_increment_open_seqid(status, o_arg.seqid);
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);
return status;
}
static int nfs4_open_reclaim(struct nfs4_state_owner *sp, struct nfs4_state *state)
{
struct nfs_server *server = NFS_SERVER(state->inode);
struct nfs4_exception exception = { };
int err;
do {
err = _nfs4_open_reclaim(sp, state);

Trond Myklebust
committed
if (err != -NFS4ERR_DELAY)
break;
nfs4_handle_exception(server, err, &exception);
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
} while (exception.retry);
return err;
}
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;
if (!test_bit(NFS_DELEGATED_STATE, &state->flags))
goto out;
if (state->state == 0)
goto out;
arg.seqid = nfs_alloc_seqid(&sp->so_seqid);
status = -ENOMEM;
if (arg.seqid == NULL)
goto out;
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);
nfs_increment_open_seqid(status, arg.seqid);

Trond Myklebust
committed
if (status != 0)
goto out_free;
if(res.rflags & NFS4_OPEN_RESULT_CONFIRM) {
status = _nfs4_proc_open_confirm(server->client, NFS_FH(inode),
sp, &res.stateid, arg.seqid);
if (status != 0)
goto out_free;
}
nfs_confirm_seqid(&sp->so_seqid, 0);
if (status >= 0) {
memcpy(state->stateid.data, res.stateid.data,
sizeof(state->stateid.data));
clear_bit(NFS_DELEGATED_STATE, &state->flags);
}

Trond Myklebust
committed
out_free:
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
out:
dput(parent);
return status;
}
int nfs4_open_delegation_recall(struct dentry *dentry, struct nfs4_state *state)
{
struct nfs4_exception exception = { };
struct nfs_server *server = NFS_SERVER(dentry->d_inode);
int err;
do {
err = _nfs4_open_delegation_recall(dentry, state);
switch (err) {
case 0:
return err;
case -NFS4ERR_STALE_CLIENTID:
case -NFS4ERR_STALE_STATEID:
case -NFS4ERR_EXPIRED:
/* Don't recall a delegation if it was lost */
nfs4_schedule_state_recovery(server->nfs4_state);
return err;
}
err = nfs4_handle_exception(server, err, &exception);
} while (exception.retry);
return err;
}

Trond Myklebust
committed
static int _nfs4_proc_open_confirm(struct rpc_clnt *clnt, const struct nfs_fh *fh, struct nfs4_state_owner *sp, nfs4_stateid *stateid, struct nfs_seqid *seqid)
{
struct nfs_open_confirmargs arg = {
.fh = fh,
.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,
};
int status;
status = rpc_call_sync(clnt, &msg, RPC_TASK_NOINTR);
/* Confirm the sequence as being established */
nfs_confirm_seqid(&sp->so_seqid, status);
nfs_increment_open_seqid(status, seqid);
if (status >= 0)
memcpy(stateid, &res.stateid, sizeof(*stateid));
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)
{
struct nfs_server *server = NFS_SERVER(dir);
struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN],
.rpc_argp = o_arg,
.rpc_resp = o_res,
.rpc_cred = sp->so_cred,
};
int status;
/* Update sequence id. The caller must serialize! */
o_arg->id = sp->so_id;
o_arg->clientid = sp->so_client->cl_clientid;
status = rpc_call_sync(server->client, &msg, RPC_TASK_NOINTR);

Trond Myklebust
committed
if (status == 0) {
/* OPEN on anything except a regular file is disallowed in NFSv4 */
switch (o_res->f_attr->mode & S_IFMT) {
case S_IFREG:
break;
case S_IFLNK:
status = -ELOOP;
break;
case S_IFDIR:
status = -EISDIR;
break;
default:
status = -ENOTDIR;
}
}
nfs_increment_open_seqid(status, o_arg->seqid);
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, o_arg->seqid);
nfs_confirm_seqid(&sp->so_seqid, 0);
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
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;
}
static int _nfs4_do_access(struct inode *inode, struct rpc_cred *cred, int openflags)
{
struct nfs_access_entry cache;
int mask = 0;
int status;
if (openflags & FMODE_READ)
mask |= MAY_READ;
if (openflags & FMODE_WRITE)
mask |= MAY_WRITE;
status = nfs_access_get_cached(inode, cred, &cache);
if (status == 0)
goto out;
/* Be clever: ask server to check for all possible rights */
cache.mask = MAY_EXEC | MAY_WRITE | MAY_READ;
cache.cred = cred;
cache.jiffies = jiffies;
status = _nfs4_proc_access(inode, &cache);
if (status != 0)
return status;
nfs_access_add_cache(inode, &cache);
out:
if ((cache.mask & mask) == mask)
return 0;
return -EACCES;
}
/*
* OPEN_EXPIRED:
* reclaim state on the server after a network partition.
* Assumes caller holds the appropriate lock
*/
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, dir_attr;
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,
.dir_attr = &dir_attr,
.server = server,
};
int status = 0;
if (delegation != NULL && !(delegation->flags & NFS_DELEGATION_NEED_RECLAIM)) {
status = _nfs4_do_access(inode, sp->so_cred, state->state);
if (status < 0)
goto out;
memcpy(&state->stateid, &delegation->stateid, sizeof(state->stateid));
set_bit(NFS_DELEGATED_STATE, &state->flags);
goto out;
}
o_arg.seqid = nfs_alloc_seqid(&sp->so_seqid);
status = -ENOMEM;
if (o_arg.seqid == NULL)
goto out;
nfs_fattr_init(&f_attr);
nfs_fattr_init(&dir_attr);
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);
}
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;
}

Trond Myklebust
committed
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;
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);

Trond Myklebust
committed
status = nfs4_do_open_expired(sp, state, ctx->dentry);
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
put_nfs_open_context(ctx);
return status;
}
spin_unlock(&state->inode->i_lock);
return -ENOENT;
}
/*
* Returns an nfs4_state + an extra reference to the inode
*/
static int _nfs4_open_delegated(struct inode *inode, int flags, struct rpc_cred *cred, struct nfs4_state **res)
{
struct nfs_delegation *delegation;
struct nfs_server *server = NFS_SERVER(inode);
struct nfs4_client *clp = server->nfs4_state;
struct nfs_inode *nfsi = NFS_I(inode);
struct nfs4_state_owner *sp = NULL;
struct nfs4_state *state = NULL;
int open_flags = flags & (FMODE_READ|FMODE_WRITE);
int err;
/* Protect against reboot recovery - NOTE ORDER! */
down_read(&clp->cl_sem);
/* Protect against delegation recall */
down_read(&nfsi->rwsem);
delegation = NFS_I(inode)->delegation;
err = -ENOENT;
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;
}
state = nfs4_get_open_state(inode, sp);
if (state == NULL)
goto out_err;
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++;
spin_unlock(&inode->i_lock);
goto out_ok;
} else if (state->state != 0)
goto out_err;
lock_kernel();
err = _nfs4_do_access(inode, cred, open_flags);
unlock_kernel();
if (err != 0)
goto out_err;
set_bit(NFS_DELEGATED_STATE, &state->flags);
update_open_stateid(state, &delegation->stateid, open_flags);
out_ok:
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);
nfs4_put_state_owner(sp);
}
up_read(&nfsi->rwsem);
up_read(&clp->cl_sem);
if (err != -EACCES)
nfs_inode_return_delegation(inode);
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
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;
int err;
do {
err = _nfs4_open_delegated(inode, flags, cred, &res);
if (err == 0)
break;
res = ERR_PTR(nfs4_handle_exception(NFS_SERVER(inode),
err, &exception));
} while (exception.retry);
return res;
}
/*
* Returns an nfs4_state + an referenced inode
*/
static int _nfs4_do_open(struct inode *dir, struct dentry *dentry, int flags, struct iattr *sattr, struct rpc_cred *cred, struct nfs4_state **res)
{
struct nfs4_state_owner *sp;
struct nfs4_state *state = NULL;
struct nfs_server *server = NFS_SERVER(dir);
struct nfs4_client *clp = server->nfs4_state;
struct inode *inode = NULL;
int status;
struct nfs_fattr f_attr, dir_attr;
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,
.dir_attr = &dir_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 */
o_arg.seqid = nfs_alloc_seqid(&sp->so_seqid);
if (o_arg.seqid == NULL)
return -ENOMEM;
nfs_fattr_init(&f_attr);
nfs_fattr_init(&dir_attr);
status = _nfs4_proc_open(dir, sp, &o_arg, &o_res);
if (status != 0)
goto out_err;
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);
nfs4_put_state_owner(sp);
up_read(&clp->cl_sem);
*res = state;
return 0;
out_err:
if (sp != NULL) {
if (state != NULL)
nfs4_put_open_state(state);
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
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;
}
static struct nfs4_state *nfs4_do_open(struct inode *dir, struct dentry *dentry, int flags, struct iattr *sattr, struct rpc_cred *cred)
{
struct nfs4_exception exception = { };
struct nfs4_state *res;
int status;
do {
status = _nfs4_do_open(dir, dentry, flags, sattr, cred, &res);
if (status == 0)
break;
/* NOTE: BAD_SEQID means the server and client disagree about the
* book-keeping w.r.t. state-changing operations
* (OPEN/CLOSE/LOCK/LOCKU...)
* 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 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...
*/
if (status == -NFS4ERR_BAD_SEQID) {
printk(KERN_WARNING "NFS: v4 server returned a bad sequence-id error!\n");
exception.retry = 1;
continue;
}

Trond Myklebust
committed
/*
* 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)
{
struct nfs_setattrargs arg = {
.fh = fhandle,
.iap = sattr,
.server = server,
.bitmask = server->attr_bitmask,
};
struct nfs_setattrres res = {
.fattr = fattr,
.server = server,
};
struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SETATTR],
.rpc_argp = &arg,
.rpc_resp = &res,
};

Trond Myklebust
committed
int status;
nfs_fattr_init(fattr);
if (state != NULL) {
nfs4_copy_stateid(&arg.stateid, state, current->files);
} else
memcpy(&arg.stateid, &zero_stateid, sizeof(arg.stateid));

Trond Myklebust
committed
status = rpc_call_sync(server->client, &msg, 0);
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)
{
struct nfs4_exception exception = { };
int err;
do {
err = nfs4_handle_exception(server,
_nfs4_do_setattr(server, fattr, fhandle, sattr,
state),
&exception);
} while (exception.retry);
return err;
}
struct nfs4_closedata {
struct inode *inode;
struct nfs4_state *state;
struct nfs_closeargs arg;
struct nfs_closeres res;
struct nfs_fattr fattr;
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 = data;
struct nfs4_state *state = calldata->state;
struct nfs_server *server = NFS_SERVER(calldata->inode);
/* hmm. we are done with the inode, and in the process of freeing
* the state_owner. we keep this around to process errors
*/
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));
break;
case -NFS4ERR_STALE_STATEID:
case -NFS4ERR_EXPIRED:
nfs4_schedule_state_recovery(server->nfs4_state);
break;
default:
if (nfs4_async_handle_error(task, server) == -EAGAIN) {
rpc_restart_call(task);
return;
}
}
nfs_refresh_inode(calldata->inode, calldata->res.fattr);
static void nfs4_close_prepare(struct rpc_task *task, void *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,
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->nreaders == 0)
mode &= ~FMODE_READ;
if (state->nwriters == 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;
nfs_fattr_init(calldata->res.fattr);
msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_DOWNGRADE];
calldata->arg.open_flags = mode;
rpc_call_setup(task, &msg, 0);
static const struct rpc_call_ops nfs4_close_ops = {
.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).
* This means that we can't safely call nfsv4 close on a file until
* the inode is cleared. This in turn means that we are not good
* NFSv4 citizens - we do not indicate to the server to update the file's
* share state even when we are done with one of the three share
* stateid's in the inode.
*
* NOTE: Caller must be holding the sp->so_owner semaphore!
*/
int nfs4_do_close(struct inode *inode, struct nfs4_state *state)
struct nfs_server *server = NFS_SERVER(inode);
calldata = kmalloc(sizeof(*calldata), GFP_KERNEL);
calldata->inode = inode;
calldata->state = state;
calldata->arg.fh = NFS_FH(inode);
calldata->arg.stateid = &state->stateid;
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);