Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d530838b authored by Trond Myklebust's avatar Trond Myklebust
Browse files

NFSv4: Fix problem with OPEN_DOWNGRADE



 RFC 3530 states that for OPEN_DOWNGRADE "The share_access and share_deny
 bits specified must be exactly equal to the union of the share_access and
 share_deny bits specified for some subset of the OPENs in effect for
 current openowner on the current file.

 Setattr is currently violating the NFSv4 rules for OPEN_DOWNGRADE in that
 it may cause a downgrade from OPEN4_SHARE_ACCESS_BOTH to
 OPEN4_SHARE_ACCESS_WRITE despite the fact that there exists no open file
 with O_WRONLY access mode.

 Fix the problem by replacing nfs4_find_state() with a modified version of
 nfs_find_open_context().

 Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent 4cecb76f
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -1009,13 +1009,18 @@ void nfs_file_set_open_context(struct file *filp, struct nfs_open_context *ctx)
	spin_unlock(&inode->i_lock);
}

struct nfs_open_context *nfs_find_open_context(struct inode *inode, int mode)
/*
 * Given an inode, search for an open context with the desired characteristics
 */
struct nfs_open_context *nfs_find_open_context(struct inode *inode, struct rpc_cred *cred, int mode)
{
	struct nfs_inode *nfsi = NFS_I(inode);
	struct nfs_open_context *pos, *ctx = NULL;

	spin_lock(&inode->i_lock);
	list_for_each_entry(pos, &nfsi->open_files, list) {
		if (cred != NULL && pos->cred != cred)
			continue;
		if ((pos->mode & mode) == mode) {
			ctx = get_nfs_open_context(pos);
			break;
+0 −1
Original line number Diff line number Diff line
@@ -247,7 +247,6 @@ extern void nfs4_drop_state_owner(struct nfs4_state_owner *);
extern struct nfs4_state * nfs4_get_open_state(struct inode *, struct nfs4_state_owner *);
extern void nfs4_put_open_state(struct nfs4_state *);
extern void nfs4_close_state(struct nfs4_state *, mode_t);
extern struct nfs4_state *nfs4_find_state(struct inode *, struct rpc_cred *, mode_t mode);
extern void nfs4_state_set_mode_locked(struct nfs4_state *, mode_t);
extern void nfs4_schedule_state_recovery(struct nfs4_client *);
extern void nfs4_put_lock_state(struct nfs4_lock_state *lsp);
+10 −13
Original line number Diff line number Diff line
@@ -214,7 +214,7 @@ 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);
	memcpy(&state->stateid, stateid, sizeof(state->stateid));
@@ -1274,7 +1274,8 @@ nfs4_proc_setattr(struct dentry *dentry, struct nfs_fattr *fattr,
{
	struct rpc_cred *cred;
	struct inode *inode = dentry->d_inode;
	struct nfs4_state *state;
	struct nfs_open_context *ctx;
	struct nfs4_state *state = NULL;
	int status;

	nfs_fattr_init(fattr);
@@ -1282,22 +1283,18 @@ nfs4_proc_setattr(struct dentry *dentry, struct nfs_fattr *fattr,
	cred = rpcauth_lookupcred(NFS_SERVER(inode)->client->cl_auth, 0);
	if (IS_ERR(cred))
		return PTR_ERR(cred);
	/* Search for an existing WRITE delegation first */
	state = nfs4_open_delegated(inode, FMODE_WRITE, cred);
	if (!IS_ERR(state)) {
		/* NB: nfs4_open_delegated() bumps the inode->i_count */
		iput(inode);
	} else {
		/* Search for an existing open(O_WRITE) stateid */
		state = nfs4_find_state(inode, cred, FMODE_WRITE);
	}

	/* 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(NFS_SERVER(inode), fattr,
			NFS_FH(inode), sattr, state);
	if (status == 0)
		nfs_setattr_update_inode(inode, sattr);
	if (state != NULL)
		nfs4_close_state(state, FMODE_WRITE);
	if (ctx != NULL)
		put_nfs_open_context(ctx);
	put_rpccred(cred);
	return status;
}
+0 −33
Original line number Diff line number Diff line
@@ -383,28 +383,6 @@ nfs4_state_set_mode_locked(struct nfs4_state *state, mode_t mode)
	state->state = mode;
}

static struct nfs4_state *
__nfs4_find_state(struct inode *inode, struct rpc_cred *cred, mode_t mode)
{
	struct nfs_inode *nfsi = NFS_I(inode);
	struct nfs4_state *state;

	mode &= (FMODE_READ|FMODE_WRITE);
	list_for_each_entry(state, &nfsi->open_states, inode_states) {
		if (state->owner->so_cred != cred)
			continue;
		if ((state->state & mode) != mode)
			continue;
		atomic_inc(&state->count);
		if (mode & FMODE_READ)
			state->nreaders++;
		if (mode & FMODE_WRITE)
			state->nwriters++;
		return state;
	}
	return NULL;
}

static struct nfs4_state *
__nfs4_find_state_byowner(struct inode *inode, struct nfs4_state_owner *owner)
{
@@ -423,17 +401,6 @@ __nfs4_find_state_byowner(struct inode *inode, struct nfs4_state_owner *owner)
	return NULL;
}

struct nfs4_state *
nfs4_find_state(struct inode *inode, struct rpc_cred *cred, mode_t mode)
{
	struct nfs4_state *state;

	spin_lock(&inode->i_lock);
	state = __nfs4_find_state(inode, cred, mode);
	spin_unlock(&inode->i_lock);
	return state;
}

static void
nfs4_free_open_state(struct nfs4_state *state)
{
+2 −2
Original line number Diff line number Diff line
@@ -507,7 +507,7 @@ int nfs_readpage(struct file *file, struct page *page)
		goto out_error;

	if (file == NULL) {
		ctx = nfs_find_open_context(inode, FMODE_READ);
		ctx = nfs_find_open_context(inode, NULL, FMODE_READ);
		if (ctx == NULL)
			return -EBADF;
	} else
@@ -576,7 +576,7 @@ int nfs_readpages(struct file *filp, struct address_space *mapping,
			nr_pages);

	if (filp == NULL) {
		desc.ctx = nfs_find_open_context(inode, FMODE_READ);
		desc.ctx = nfs_find_open_context(inode, NULL, FMODE_READ);
		if (desc.ctx == NULL)
			return -EBADF;
	} else
Loading