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

Commit e50a1c2e authored by J. Bruce Fields's avatar J. Bruce Fields Committed by Trond Myklebust
Browse files

[PATCH] NFSv4: client-side caching NFSv4 ACLs



 Add nfs4_acl field to the nfs_inode, and use it to cache acls.  Only cache
 acls of size up to a page.  Also prepare for up to a page of acl data even
 when the user doesn't pass in a buffer, as when they want to get the acl
 length to decide what size buffer to allocate.

 Signed-off-by: default avatarJ. Bruce Fields <bfields@citi.umich.edu>
 Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent 4b580ee3
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -142,10 +142,6 @@ nfs_delete_inode(struct inode * inode)
	clear_inode(inode);
}

/*
 * For the moment, the only task for the NFS clear_inode method is to
 * release the mmap credential
 */
static void
nfs_clear_inode(struct inode *inode)
{
@@ -1923,6 +1919,9 @@ static struct inode *nfs_alloc_inode(struct super_block *sb)
	if (!nfsi)
		return NULL;
	nfsi->flags = 0;
#ifdef CONFIG_NFS_V4
	nfsi->nfs4_acl = NULL;
#endif /* CONFIG_NFS_V4 */
	return &nfsi->vfs_inode;
}

+120 −9
Original line number Diff line number Diff line
@@ -2188,9 +2188,75 @@ static void buf_to_pages(const void *buf, size_t buflen,
	}
}

static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
struct nfs4_cached_acl {
	int cached;
	size_t len;
	char data[];
};

static void nfs4_set_cached_acl(struct inode *inode, struct nfs4_cached_acl *acl)
{
	struct nfs_inode *nfsi = NFS_I(inode);

	spin_lock(&inode->i_lock);
	kfree(nfsi->nfs4_acl);
	nfsi->nfs4_acl = acl;
	spin_unlock(&inode->i_lock);
}

static void nfs4_zap_acl_attr(struct inode *inode)
{
	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 nfs_server *server = NFS_SERVER(inode);
	struct page *pages[NFS4ACL_MAXPAGES];
	struct nfs_getaclargs args = {
		.fh = NFS_FH(inode),
@@ -2198,22 +2264,64 @@ static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
		.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 (!nfs4_server_supports_acls(server))
		return -EOPNOTSUPP;
	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;
		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 (buflen && resp_len > buflen)
		return -ERANGE;
	if (ret == 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)
@@ -2236,6 +2344,8 @@ static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen
		return -EOPNOTSUPP;
	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;
}

@@ -2907,6 +3017,7 @@ struct nfs_rpc_ops nfs_v4_clientops = {
	.file_open      = nfs4_proc_file_open,
	.file_release   = nfs4_proc_file_release,
	.lock		= nfs4_proc_lock,
	.clear_acl_cache = nfs4_zap_acl_attr,
};

/*
+1 −1
Original line number Diff line number Diff line
@@ -169,13 +169,13 @@ struct nfs_inode {
	wait_queue_head_t	nfs_i_wait;

#ifdef CONFIG_NFS_V4
	struct nfs4_cached_acl	*nfs4_acl;
        /* NFSv4 state */
	struct list_head	open_states;
	struct nfs_delegation	*delegation;
	int			 delegation_state;
	struct rw_semaphore	rwsem;
#endif /* CONFIG_NFS_V4*/

	struct inode		vfs_inode;
};