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: J. Bruce Fields Signed-off-by: Trond Myklebust --- fs/nfs/inode.c | 9 +-- fs/nfs/nfs4_fs.h | 2 fs/nfs/nfs4proc.c | 135 +++++++++++++++++++++++++++++++++++++++++++++---- include/linux/nfs_fs.h | 2 4 files changed, 133 insertions(+), 15 deletions(-) Index: linux-2.6.11/fs/nfs/inode.c =================================================================== --- linux-2.6.11.orig/fs/nfs/inode.c +++ linux-2.6.11/fs/nfs/inode.c @@ -141,16 +141,13 @@ 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) { struct nfs_inode *nfsi = NFS_I(inode); struct rpc_cred *cred; + nfs4_zap_acl_attr(inode); nfs_wb_all(inode); BUG_ON (!list_empty(&nfsi->open_files)); cred = nfsi->cache_access.cred; @@ -1312,6 +1309,7 @@ static int nfs_update_inode(struct inode inode->i_nlink = fattr->nlink; inode->i_uid = fattr->uid; inode->i_gid = fattr->gid; + nfs4_zap_acl_attr(inode); if (fattr->valid & (NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4)) { /* @@ -1921,6 +1919,9 @@ static struct inode *nfs_alloc_inode(str if (!nfsi) return NULL; nfsi->flags = 0; +#ifdef CONFIG_NFS_V4 + nfsi->nfs4_acl = NULL; +#endif /* CONFIG_NFS_V4 */ return &nfsi->vfs_inode; } Index: linux-2.6.11/fs/nfs/nfs4proc.c =================================================================== --- linux-2.6.11.orig/fs/nfs/nfs4proc.c +++ linux-2.6.11/fs/nfs/nfs4proc.c @@ -2162,6 +2162,77 @@ nfs4_proc_file_release(struct inode *ino return 0; } +struct nfs4_cached_acl { + int cached; + size_t len; + char data[]; +}; + +static 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_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_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); +} + +void +nfs4_zap_acl_attr(struct inode *inode) +{ + nfs4_set_cached_acl(inode, NULL); +} + static int nfs4_server_supports_acls(struct nfs_server *server) { @@ -2189,10 +2260,9 @@ static void buf_to_pages(const void *buf } } -ssize_t -nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen) +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), @@ -2200,24 +2270,67 @@ nfs4_proc_get_acl(struct inode *inode, v .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; - buf_to_pages(buf, buflen, args.acl_pages, &args.acl_pgbase); + 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) - ret = resp_len; + 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; } +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); +} + int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen) { @@ -2239,6 +2352,8 @@ nfs4_proc_set_acl(struct inode *inode, c 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; } Index: linux-2.6.11/include/linux/nfs_fs.h =================================================================== --- linux-2.6.11.orig/include/linux/nfs_fs.h +++ linux-2.6.11/include/linux/nfs_fs.h @@ -171,13 +171,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; }; Index: linux-2.6.11/fs/nfs/nfs4_fs.h =================================================================== --- linux-2.6.11.orig/fs/nfs/nfs4_fs.h +++ linux-2.6.11/fs/nfs/nfs4_fs.h @@ -195,6 +195,7 @@ extern struct inode *nfs4_atomic_open(st extern int nfs4_open_revalidate(struct inode *, struct dentry *, int); extern ssize_t nfs4_proc_get_acl(struct inode *, void *buf, size_t buflen); extern int nfs4_proc_set_acl(struct inode *, const void *buf, size_t buflen); +extern void nfs4_zap_acl_attr(struct inode *inode); extern struct nfs4_state_recovery_ops nfs4_reboot_recovery_ops; extern struct nfs4_state_recovery_ops nfs4_network_partition_recovery_ops; @@ -254,6 +255,7 @@ extern struct svc_version nfs4_callback_ #define nfs4_put_state_owner(inode, owner) do { } while (0) #define nfs4_put_open_state(state) do { } while (0) #define nfs4_close_state(a, b) do { } while (0) +#define nfs4_zap_acl_attr(inode) do { } while (0) #endif /* CONFIG_NFS_V4 */ #endif /* __LINUX_FS_NFS_NFS4_FS.H */