Commit b7fa0554 authored by Andreas Gruenbacher's avatar Andreas Gruenbacher Committed by Trond Myklebust
Browse files

[PATCH] NFS: Add support for NFSv3 ACLs



 This adds acl support fo nfs clients via the NFSACL protocol extension, by
 implementing the getxattr, listxattr, setxattr, and removexattr iops for the
 system.posix_acl_access and system.posix_acl_default attributes.  This patch
 implements a dumb version that uses no caching (and thus adds some overhead).
 (Another patch in this patchset adds caching as well.)
Signed-off-by: default avatarAndreas Gruenbacher <agruen@suse.de>
Acked-by: default avatarOlaf Kirch <okir@suse.de>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent a257cdd0
......@@ -1268,6 +1268,7 @@ config NFS_FS
depends on INET
select LOCKD
select SUNRPC
select NFS_ACL_SUPPORT if NFS_V3_ACL
help
If you are connected to some other (usually local) Unix computer
(using SLIP, PLIP, PPP or Ethernet) and want to mount files residing
......@@ -1310,6 +1311,16 @@ config NFS_V3
If unsure, say Y.
config NFS_V3_ACL
bool "Provide client support for the NFSv3 ACL protocol extension"
depends on NFS_V3
help
Implement the NFSv3 ACL protocol extension for manipulating POSIX
Access Control Lists. The server should also be compiled with
the NFSv3 ACL protocol extension; see the CONFIG_NFSD_V3_ACL option.
If unsure, say N.
config NFS_V4
bool "Provide NFSv4 client support (EXPERIMENTAL)"
depends on NFS_FS && EXPERIMENTAL
......
......@@ -8,6 +8,7 @@ nfs-y := dir.o file.o inode.o nfs2xdr.o pagelist.o \
proc.o read.o symlink.o unlink.o write.o
nfs-$(CONFIG_ROOT_NFS) += nfsroot.o mount_clnt.o
nfs-$(CONFIG_NFS_V3) += nfs3proc.o nfs3xdr.o
nfs-$(CONFIG_NFS_V3_ACL) += nfs3acl.o
nfs-$(CONFIG_NFS_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4renewd.o \
delegation.o idmap.o \
callback.o callback_xdr.o callback_proc.o
......
......@@ -75,6 +75,27 @@ struct inode_operations nfs_dir_inode_operations = {
.setattr = nfs_setattr,
};
#ifdef CONFIG_NFS_V3
struct inode_operations nfs3_dir_inode_operations = {
.create = nfs_create,
.lookup = nfs_lookup,
.link = nfs_link,
.unlink = nfs_unlink,
.symlink = nfs_symlink,
.mkdir = nfs_mkdir,
.rmdir = nfs_rmdir,
.mknod = nfs_mknod,
.rename = nfs_rename,
.permission = nfs_permission,
.getattr = nfs_getattr,
.setattr = nfs_setattr,
.listxattr = nfs3_listxattr,
.getxattr = nfs3_getxattr,
.setxattr = nfs3_setxattr,
.removexattr = nfs3_removexattr,
};
#endif /* CONFIG_NFS_V3 */
#ifdef CONFIG_NFS_V4
static struct dentry *nfs_atomic_lookup(struct inode *, struct dentry *, struct nameidata *);
......
......@@ -71,6 +71,18 @@ struct inode_operations nfs_file_inode_operations = {
.setattr = nfs_setattr,
};
#ifdef CONFIG_NFS_V3
struct inode_operations nfs3_file_inode_operations = {
.permission = nfs_permission,
.getattr = nfs_getattr,
.setattr = nfs_setattr,
.listxattr = nfs3_listxattr,
.getxattr = nfs3_getxattr,
.setxattr = nfs3_setxattr,
.removexattr = nfs3_removexattr,
};
#endif /* CONFIG_NFS_v3 */
/* Hack for future NFS swap support */
#ifndef IS_SWAPFILE
# define IS_SWAPFILE(inode) (0)
......
......@@ -108,6 +108,21 @@ static struct rpc_program nfs_program = {
.pipe_dir_name = "/nfs",
};
#ifdef CONFIG_NFS_V3_ACL
static struct rpc_stat nfsacl_rpcstat = { &nfsacl_program };
static struct rpc_version * nfsacl_version[] = {
[3] = &nfsacl_version3,
};
struct rpc_program nfsacl_program = {
.name = "nfsacl",
.number = NFS_ACL_PROGRAM,
.nrvers = sizeof(nfsacl_version) / sizeof(nfsacl_version[0]),
.version = nfsacl_version,
.stats = &nfsacl_rpcstat,
};
#endif /* CONFIG_NFS_V3_ACL */
static inline unsigned long
nfs_fattr_to_ino_t(struct nfs_fattr *fattr)
{
......@@ -165,6 +180,9 @@ nfs_umount_begin(struct super_block *sb)
/* -EIO all pending I/O */
if (!IS_ERR(rpc))
rpc_killall_tasks(rpc);
rpc = NFS_SB(sb)->client_acl;
if (!IS_ERR(rpc))
rpc_killall_tasks(rpc);
}
......@@ -461,8 +479,17 @@ nfs_fill_super(struct super_block *sb, struct nfs_mount_data *data, int silent)
atomic_inc(&server->client->cl_count);
server->client_sys = server->client;
}
if (server->flags & NFS_MOUNT_VER3) {
#ifdef CONFIG_NFS_V3_ACL
if (!(server->flags & NFS_MOUNT_NOACL)) {
server->client_acl = rpc_bind_new_program(server->client, &nfsacl_program, 3);
/* No errors! Assume that Sun nfsacls are supported */
if (!IS_ERR(server->client_acl))
server->caps |= NFS_CAP_ACLS;
}
#else
server->flags &= ~NFS_MOUNT_NOACL;
#endif /* CONFIG_NFS_V3_ACL */
if (server->namelen == 0 || server->namelen > NFS3_MAXNAMLEN)
server->namelen = NFS3_MAXNAMLEN;
sb->s_time_gran = 1;
......@@ -546,6 +573,7 @@ static int nfs_show_options(struct seq_file *m, struct vfsmount *mnt)
{ NFS_MOUNT_NOCTO, ",nocto", "" },
{ NFS_MOUNT_NOAC, ",noac", "" },
{ NFS_MOUNT_NONLM, ",nolock", ",lock" },
{ NFS_MOUNT_NOACL, ",noacl", "" },
{ 0, NULL, NULL }
};
struct proc_nfs_info *nfs_infop;
......@@ -1452,7 +1480,7 @@ static struct super_block *nfs_get_sb(struct file_system_type *fs_type,
memset(server, 0, sizeof(struct nfs_server));
/* Zero out the NFS state stuff */
init_nfsv4_state(server);
server->client = server->client_sys = ERR_PTR(-EINVAL);
server->client = server->client_sys = server->client_acl = ERR_PTR(-EINVAL);
root = &server->fh;
if (data->flags & NFS_MOUNT_VER3)
......@@ -1513,6 +1541,8 @@ static void nfs_kill_super(struct super_block *s)
rpc_shutdown_client(server->client);
if (!IS_ERR(server->client_sys))
rpc_shutdown_client(server->client_sys);
if (!IS_ERR(server->client_acl))
rpc_shutdown_client(server->client_acl);
if (!(server->flags & NFS_MOUNT_NONLM))
lockd_down(); /* release rpc.lockd */
......@@ -1794,7 +1824,7 @@ static struct super_block *nfs4_get_sb(struct file_system_type *fs_type,
memset(server, 0, sizeof(struct nfs_server));
/* Zero out the NFS state stuff */
init_nfsv4_state(server);
server->client = server->client_sys = ERR_PTR(-EINVAL);
server->client = server->client_sys = server->client_acl = ERR_PTR(-EINVAL);
p = nfs_copy_user_string(NULL, &data->hostname, 256);
if (IS_ERR(p))
......
#include <linux/fs.h>
#include <linux/nfs.h>
#include <linux/nfs3.h>
#include <linux/nfs_fs.h>
#include <linux/xattr_acl.h>
#include <linux/nfsacl.h>
#define NFSDBG_FACILITY NFSDBG_PROC
ssize_t nfs3_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
struct inode *inode = dentry->d_inode;
struct posix_acl *acl;
int pos=0, len=0;
# define output(s) do { \
if (pos + sizeof(s) <= size) { \
memcpy(buffer + pos, s, sizeof(s)); \
pos += sizeof(s); \
} \
len += sizeof(s); \
} while(0)
acl = nfs3_proc_getacl(inode, ACL_TYPE_ACCESS);
if (IS_ERR(acl))
return PTR_ERR(acl);
if (acl) {
output("system.posix_acl_access");
posix_acl_release(acl);
}
if (S_ISDIR(inode->i_mode)) {
acl = nfs3_proc_getacl(inode, ACL_TYPE_DEFAULT);
if (IS_ERR(acl))
return PTR_ERR(acl);
if (acl) {
output("system.posix_acl_default");
posix_acl_release(acl);
}
}
# undef output
if (!buffer || len <= size)
return len;
return -ERANGE;
}
ssize_t nfs3_getxattr(struct dentry *dentry, const char *name,
void *buffer, size_t size)
{
struct inode *inode = dentry->d_inode;
struct posix_acl *acl;
int type, error = 0;
if (strcmp(name, XATTR_NAME_ACL_ACCESS) == 0)
type = ACL_TYPE_ACCESS;
else if (strcmp(name, XATTR_NAME_ACL_DEFAULT) == 0)
type = ACL_TYPE_DEFAULT;
else
return -EOPNOTSUPP;
acl = nfs3_proc_getacl(inode, type);
if (IS_ERR(acl))
return PTR_ERR(acl);
else if (acl) {
if (type == ACL_TYPE_ACCESS && acl->a_count == 0)
error = -ENODATA;
else
error = posix_acl_to_xattr(acl, buffer, size);
posix_acl_release(acl);
} else
error = -ENODATA;
return error;
}
int nfs3_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
struct inode *inode = dentry->d_inode;
struct posix_acl *acl;
int type, error;
if (strcmp(name, XATTR_NAME_ACL_ACCESS) == 0)
type = ACL_TYPE_ACCESS;
else if (strcmp(name, XATTR_NAME_ACL_DEFAULT) == 0)
type = ACL_TYPE_DEFAULT;
else
return -EOPNOTSUPP;
acl = posix_acl_from_xattr(value, size);
if (IS_ERR(acl))
return PTR_ERR(acl);
error = nfs3_proc_setacl(inode, type, acl);
posix_acl_release(acl);
return error;
}
int nfs3_removexattr(struct dentry *dentry, const char *name)
{
struct inode *inode = dentry->d_inode;
int type;
if (strcmp(name, XATTR_NAME_ACL_ACCESS) == 0)
type = ACL_TYPE_ACCESS;
else if (strcmp(name, XATTR_NAME_ACL_DEFAULT) == 0)
type = ACL_TYPE_DEFAULT;
else
return -EOPNOTSUPP;
return nfs3_proc_setacl(inode, type, NULL);
}
struct posix_acl *nfs3_proc_getacl(struct inode *inode, int type)
{
struct nfs_server *server = NFS_SERVER(inode);
struct nfs_fattr fattr;
struct page *pages[NFSACL_MAXPAGES] = { };
struct nfs3_getaclargs args = {
.fh = NFS_FH(inode),
/* The xdr layer may allocate pages here. */
.pages = pages,
};
struct nfs3_getaclres res = {
.fattr = &fattr,
};
struct posix_acl *acl = NULL;
int status, count;
if (!nfs_server_capable(inode, NFS_CAP_ACLS))
return ERR_PTR(-EOPNOTSUPP);
switch (type) {
case ACL_TYPE_ACCESS:
args.mask = NFS_ACLCNT|NFS_ACL;
break;
case ACL_TYPE_DEFAULT:
if (!S_ISDIR(inode->i_mode))
return NULL;
args.mask = NFS_DFACLCNT|NFS_DFACL;
break;
default:
return ERR_PTR(-EINVAL);
}
dprintk("NFS call getacl\n");
status = rpc_call(server->client_acl, ACLPROC3_GETACL,
&args, &res, 0);
dprintk("NFS reply getacl: %d\n", status);
/* pages may have been allocated at the xdr layer. */
for (count = 0; count < NFSACL_MAXPAGES && args.pages[count]; count++)
__free_page(args.pages[count]);
switch (status) {
case 0:
status = nfs_refresh_inode(inode, &fattr);
break;
case -EPFNOSUPPORT:
case -EPROTONOSUPPORT:
dprintk("NFS_V3_ACL extension not supported; disabling\n");
server->caps &= ~NFS_CAP_ACLS;
case -ENOTSUPP:
status = -EOPNOTSUPP;
default:
goto getout;
}
if ((args.mask & res.mask) != args.mask) {
status = -EIO;
goto getout;
}
if (res.acl_access != NULL) {
if (posix_acl_equiv_mode(res.acl_access, NULL) == 0) {
posix_acl_release(res.acl_access);
res.acl_access = NULL;
}
}
switch(type) {
case ACL_TYPE_ACCESS:
acl = res.acl_access;
res.acl_access = NULL;
break;
case ACL_TYPE_DEFAULT:
acl = res.acl_default;
res.acl_default = NULL;
}
getout:
posix_acl_release(res.acl_access);
posix_acl_release(res.acl_default);
if (status != 0) {
posix_acl_release(acl);
acl = ERR_PTR(status);
}
return acl;
}
static int nfs3_proc_setacls(struct inode *inode, struct posix_acl *acl,
struct posix_acl *dfacl)
{
struct nfs_server *server = NFS_SERVER(inode);
struct nfs_fattr fattr;
struct page *pages[NFSACL_MAXPAGES] = { };
struct nfs3_setaclargs args = {
.inode = inode,
.mask = NFS_ACL,
.acl_access = acl,
.pages = pages,
};
int status, count;
status = -EOPNOTSUPP;
if (!nfs_server_capable(inode, NFS_CAP_ACLS))
goto out;
/* We are doing this here, because XDR marshalling can only
return -ENOMEM. */
status = -ENOSPC;
if (acl != NULL && acl->a_count > NFS_ACL_MAX_ENTRIES)
goto out;
if (dfacl != NULL && dfacl->a_count > NFS_ACL_MAX_ENTRIES)
goto out;
if (S_ISDIR(inode->i_mode)) {
args.mask |= NFS_DFACL;
args.acl_default = dfacl;
}
dprintk("NFS call setacl\n");
nfs_begin_data_update(inode);
status = rpc_call(server->client_acl, ACLPROC3_SETACL,
&args, &fattr, 0);
NFS_FLAGS(inode) |= NFS_INO_INVALID_ACCESS;
nfs_end_data_update(inode);
dprintk("NFS reply setacl: %d\n", status);
/* pages may have been allocated at the xdr layer. */
for (count = 0; count < NFSACL_MAXPAGES && args.pages[count]; count++)
__free_page(args.pages[count]);
switch (status) {
case 0:
status = nfs_refresh_inode(inode, &fattr);
break;
case -EPFNOSUPPORT:
case -EPROTONOSUPPORT:
dprintk("NFS_V3_ACL SETACL RPC not supported"
"(will not retry)\n");
server->caps &= ~NFS_CAP_ACLS;
case -ENOTSUPP:
status = -EOPNOTSUPP;
}
out:
return status;
}
int nfs3_proc_setacl(struct inode *inode, int type, struct posix_acl *acl)
{
struct posix_acl *alloc = NULL, *dfacl = NULL;
int status;
if (S_ISDIR(inode->i_mode)) {
switch(type) {
case ACL_TYPE_ACCESS:
alloc = dfacl = nfs3_proc_getacl(inode,
ACL_TYPE_DEFAULT);
if (IS_ERR(alloc))
goto fail;
break;
case ACL_TYPE_DEFAULT:
dfacl = acl;
alloc = acl = nfs3_proc_getacl(inode,
ACL_TYPE_ACCESS);
if (IS_ERR(alloc))
goto fail;
break;
default:
return -EINVAL;
}
} else if (type != ACL_TYPE_ACCESS)
return -EINVAL;
if (acl == NULL) {
alloc = acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
if (IS_ERR(alloc))
goto fail;
}
status = nfs3_proc_setacls(inode, acl, dfacl);
posix_acl_release(alloc);
return status;
fail:
return PTR_ERR(alloc);
}
......@@ -17,6 +17,7 @@
#include <linux/nfs_page.h>
#include <linux/lockd/bind.h>
#include <linux/smp_lock.h>
#include <linux/nfs_mount.h>
#define NFSDBG_FACILITY NFSDBG_PROC
......@@ -45,7 +46,7 @@ static inline int
nfs3_rpc_call_wrapper(struct rpc_clnt *clnt, u32 proc, void *argp, void *resp, int flags)
{
struct rpc_message msg = {
.rpc_proc = &nfs3_procedures[proc],
.rpc_proc = &clnt->cl_procinfo[proc],
.rpc_argp = argp,
.rpc_resp = resp,
};
......@@ -825,8 +826,8 @@ nfs3_proc_lock(struct file *filp, int cmd, struct file_lock *fl)
struct nfs_rpc_ops nfs_v3_clientops = {
.version = 3, /* protocol version */
.dentry_ops = &nfs_dentry_operations,
.dir_inode_ops = &nfs_dir_inode_operations,
.file_inode_ops = &nfs_file_inode_operations,
.dir_inode_ops = &nfs3_dir_inode_operations,
.file_inode_ops = &nfs3_file_inode_operations,
.getroot = nfs3_proc_get_root,
.getattr = nfs3_proc_getattr,
.setattr = nfs3_proc_setattr,
......
......@@ -21,6 +21,7 @@
#include <linux/nfs.h>
#include <linux/nfs3.h>
#include <linux/nfs_fs.h>
#include <linux/nfsacl.h>
#define NFSDBG_FACILITY NFSDBG_XDR
......@@ -79,6 +80,11 @@ extern int nfs_stat_to_errno(int);
#define NFS3_pathconfres_sz (1+NFS3_post_op_attr_sz+6)
#define NFS3_commitres_sz (1+NFS3_wcc_data_sz+2)
#define ACL3_getaclargs_sz (NFS3_fh_sz+1)
#define ACL3_setaclargs_sz (NFS3_fh_sz+1+2*(2+5*3))
#define ACL3_getaclres_sz (1+NFS3_post_op_attr_sz+1+2*(2+5*3))
#define ACL3_setaclres_sz (1+NFS3_post_op_attr_sz)
/*
* Map file type to S_IFMT bits
*/
......@@ -627,6 +633,74 @@ nfs3_xdr_commitargs(struct rpc_rqst *req, u32 *p, struct nfs_writeargs *args)
return 0;
}
#ifdef CONFIG_NFS_V3_ACL
/*
* Encode GETACL arguments
*/
static int
nfs3_xdr_getaclargs(struct rpc_rqst *req, u32 *p,
struct nfs3_getaclargs *args)
{
struct rpc_auth *auth = req->rq_task->tk_auth;
unsigned int replen;
p = xdr_encode_fhandle(p, args->fh);
*p++ = htonl(args->mask);
req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
if (args->mask & (NFS_ACL | NFS_DFACL)) {
/* Inline the page array */
replen = (RPC_REPHDRSIZE + auth->au_rslack +
ACL3_getaclres_sz) << 2;
xdr_inline_pages(&req->rq_rcv_buf, replen, args->pages, 0,
NFSACL_MAXPAGES << PAGE_SHIFT);
}
return 0;
}
/*
* Encode SETACL arguments
*/
static int
nfs3_xdr_setaclargs(struct rpc_rqst *req, u32 *p,
struct nfs3_setaclargs *args)
{
struct xdr_buf *buf = &req->rq_snd_buf;
unsigned int base, len_in_head, len = nfsacl_size(
(args->mask & NFS_ACL) ? args->acl_access : NULL,
(args->mask & NFS_DFACL) ? args->acl_default : NULL);
int count, err;
p = xdr_encode_fhandle(p, NFS_FH(args->inode));
*p++ = htonl(args->mask);
base = (char *)p - (char *)buf->head->iov_base;
/* put as much of the acls into head as possible. */
len_in_head = min_t(unsigned int, buf->head->iov_len - base, len);
len -= len_in_head;
req->rq_slen = xdr_adjust_iovec(req->rq_svec, p + len_in_head);
for (count = 0; (count << PAGE_SHIFT) < len; count++) {
args->pages[count] = alloc_page(GFP_KERNEL);
if (!args->pages[count]) {
while (count)
__free_page(args->pages[--count]);
return -ENOMEM;
}
}
xdr_encode_pages(buf, args->pages, 0, len);
err = nfsacl_encode(buf, base, args->inode,
(args->mask & NFS_ACL) ?
args->acl_access : NULL, 1, 0);
if (err > 0)
err = nfsacl_encode(buf, base + err, args->inode,
(args->mask & NFS_DFACL) ?
args->acl_default : NULL, 1,
NFS_ACL_DEFAULT);
return (err > 0) ? 0 : err;
}
#endif /* CONFIG_NFS_V3_ACL */
/*
* NFS XDR decode functions
*/
......@@ -978,6 +1052,54 @@ nfs3_xdr_commitres(struct rpc_rqst *req, u32 *p, struct nfs_writeres *res)
return 0;
}
#ifdef CONFIG_NFS_V3_ACL
/*
* Decode GETACL reply
*/
static int
nfs3_xdr_getaclres(struct rpc_rqst *req, u32 *p,
struct nfs3_getaclres *res)
{
struct xdr_buf *buf = &req->rq_rcv_buf;
int status = ntohl(*p++);
struct posix_acl **acl;
unsigned int *aclcnt;
int err, base;
if (status != 0)
return -nfs_stat_to_errno(status);
p = xdr_decode_post_op_attr(p, res->fattr);
res->mask = ntohl(*p++);
if (res->mask & ~(NFS_ACL|NFS_ACLCNT|NFS_DFACL|NFS_DFACLCNT))
return -EINVAL;
base = (char *)p - (char *)req->rq_rcv_buf.head->iov_base;
acl = (res->mask & NFS_ACL) ? &res->acl_access : NULL;
aclcnt = (res->mask & NFS_ACLCNT) ? &res->acl_access_count : NULL;
err = nfsacl_decode(buf, base, aclcnt, acl);
acl = (res->mask & NFS_DFACL) ? &res->acl_default : NULL;
aclcnt = (res->mask & NFS_DFACLCNT) ? &res->acl_default_count : NULL;
if (err > 0)
err = nfsacl_decode(buf, base + err, aclcnt, acl);