summaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/Kconfig24
-rw-r--r--fs/Makefile1
-rw-r--r--fs/nfs_common/Makefile7
-rw-r--r--fs/nfs_common/nfsacl.c257
-rw-r--r--fs/nfsd/Makefile2
-rw-r--r--fs/nfsd/nfs2acl.c336
-rw-r--r--fs/nfsd/nfs3acl.c267
-rw-r--r--fs/nfsd/nfs3xdr.c13
-rw-r--r--fs/nfsd/nfssvc.c27
-rw-r--r--fs/nfsd/nfsxdr.c11
-rw-r--r--fs/nfsd/vfs.c107
11 files changed, 1051 insertions, 1 deletions
diff --git a/fs/Kconfig b/fs/Kconfig
index 178e27494b74..d44b04d9b0a9 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -1353,6 +1353,7 @@ config NFSD
select LOCKD
select SUNRPC
select EXPORTFS
+ select NFS_ACL_SUPPORT if NFSD_V3_ACL || NFSD_V2_ACL
help
If you want your Linux box to act as an NFS *server*, so that other
computers on your local network which support NFS can access certain
@@ -1376,6 +1377,10 @@ config NFSD
To compile the NFS server support as a module, choose M here: the
module will be called nfsd. If unsure, say N.
+config NFSD_V2_ACL
+ bool
+ depends on NFSD
+
config NFSD_V3
bool "Provide NFSv3 server support"
depends on NFSD
@@ -1383,6 +1388,16 @@ config NFSD_V3
If you would like to include the NFSv3 server as well as the NFSv2
server, say Y here. If unsure, say Y.
+config NFSD_V3_ACL
+ bool "Provide server support for the NFSv3 ACL protocol extension"
+ depends on NFSD_V3
+ select NFSD_V2_ACL
+ help
+ Implement the NFSv3 ACL protocol extension for manipulating POSIX
+ Access Control Lists on exported file systems. NFS clients should
+ be compiled with the NFSv3 ACL protocol extension; see the
+ CONFIG_NFS_V3_ACL option. If unsure, say N.
+
config NFSD_V4
bool "Provide NFSv4 server support (EXPERIMENTAL)"
depends on NFSD_V3 && EXPERIMENTAL
@@ -1427,6 +1442,15 @@ config LOCKD_V4
config EXPORTFS
tristate
+config NFS_ACL_SUPPORT
+ tristate
+ select FS_POSIX_ACL
+
+config NFS_COMMON
+ bool
+ depends on NFSD || NFS_FS
+ default y
+
config SUNRPC
tristate
diff --git a/fs/Makefile b/fs/Makefile
index 443f2bc56ccf..fc92e59e9faf 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_BINFMT_FLAT) += binfmt_flat.o
obj-$(CONFIG_FS_MBCACHE) += mbcache.o
obj-$(CONFIG_FS_POSIX_ACL) += posix_acl.o xattr_acl.o
+obj-$(CONFIG_NFS_COMMON) += nfs_common/
obj-$(CONFIG_QUOTA) += dquot.o
obj-$(CONFIG_QFMT_V1) += quota_v1.o
diff --git a/fs/nfs_common/Makefile b/fs/nfs_common/Makefile
new file mode 100644
index 000000000000..f689ed82af3a
--- /dev/null
+++ b/fs/nfs_common/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for Linux filesystem routines that are shared by client and server.
+#
+
+obj-$(CONFIG_NFS_ACL_SUPPORT) += nfs_acl.o
+
+nfs_acl-objs := nfsacl.o
diff --git a/fs/nfs_common/nfsacl.c b/fs/nfs_common/nfsacl.c
new file mode 100644
index 000000000000..18c58c32e326
--- /dev/null
+++ b/fs/nfs_common/nfsacl.c
@@ -0,0 +1,257 @@
+/*
+ * fs/nfs_common/nfsacl.c
+ *
+ * Copyright (C) 2002-2003 Andreas Gruenbacher <agruen@suse.de>
+ */
+
+/*
+ * The Solaris nfsacl protocol represents some ACLs slightly differently
+ * than POSIX 1003.1e draft 17 does (and we do):
+ *
+ * - Minimal ACLs always have an ACL_MASK entry, so they have
+ * four instead of three entries.
+ * - The ACL_MASK entry in such minimal ACLs always has the same
+ * permissions as the ACL_GROUP_OBJ entry. (In extended ACLs
+ * the ACL_MASK and ACL_GROUP_OBJ entries may differ.)
+ * - The identifier fields of the ACL_USER_OBJ and ACL_GROUP_OBJ
+ * entries contain the identifiers of the owner and owning group.
+ * (In POSIX ACLs we always set them to ACL_UNDEFINED_ID).
+ * - ACL entries in the kernel are kept sorted in ascending order
+ * of (e_tag, e_id). Solaris ACLs are unsorted.
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/nfsacl.h>
+#include <linux/nfs3.h>
+#include <linux/sort.h>
+
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(nfsacl_encode);
+EXPORT_SYMBOL(nfsacl_decode);
+
+struct nfsacl_encode_desc {
+ struct xdr_array2_desc desc;
+ unsigned int count;
+ struct posix_acl *acl;
+ int typeflag;
+ uid_t uid;
+ gid_t gid;
+};
+
+static int
+xdr_nfsace_encode(struct xdr_array2_desc *desc, void *elem)
+{
+ struct nfsacl_encode_desc *nfsacl_desc =
+ (struct nfsacl_encode_desc *) desc;
+ u32 *p = (u32 *) elem;
+
+ if (nfsacl_desc->count < nfsacl_desc->acl->a_count) {
+ struct posix_acl_entry *entry =
+ &nfsacl_desc->acl->a_entries[nfsacl_desc->count++];
+
+ *p++ = htonl(entry->e_tag | nfsacl_desc->typeflag);
+ switch(entry->e_tag) {
+ case ACL_USER_OBJ:
+ *p++ = htonl(nfsacl_desc->uid);
+ break;
+ case ACL_GROUP_OBJ:
+ *p++ = htonl(nfsacl_desc->gid);
+ break;
+ case ACL_USER:
+ case ACL_GROUP:
+ *p++ = htonl(entry->e_id);
+ break;
+ default: /* Solaris depends on that! */
+ *p++ = 0;
+ break;
+ }
+ *p++ = htonl(entry->e_perm & S_IRWXO);
+ } else {
+ const struct posix_acl_entry *pa, *pe;
+ int group_obj_perm = ACL_READ|ACL_WRITE|ACL_EXECUTE;
+
+ FOREACH_ACL_ENTRY(pa, nfsacl_desc->acl, pe) {
+ if (pa->e_tag == ACL_GROUP_OBJ) {
+ group_obj_perm = pa->e_perm & S_IRWXO;
+ break;
+ }
+ }
+ /* fake up ACL_MASK entry */
+ *p++ = htonl(ACL_MASK | nfsacl_desc->typeflag);
+ *p++ = htonl(0);
+ *p++ = htonl(group_obj_perm);
+ }
+
+ return 0;
+}
+
+unsigned int
+nfsacl_encode(struct xdr_buf *buf, unsigned int base, struct inode *inode,
+ struct posix_acl *acl, int encode_entries, int typeflag)
+{
+ int entries = (acl && acl->a_count) ? max_t(int, acl->a_count, 4) : 0;
+ struct nfsacl_encode_desc nfsacl_desc = {
+ .desc = {
+ .elem_size = 12,
+ .array_len = encode_entries ? entries : 0,
+ .xcode = xdr_nfsace_encode,
+ },
+ .acl = acl,
+ .typeflag = typeflag,
+ .uid = inode->i_uid,
+ .gid = inode->i_gid,
+ };
+ int err;
+
+ if (entries > NFS_ACL_MAX_ENTRIES ||
+ xdr_encode_word(buf, base, entries))
+ return -EINVAL;
+ err = xdr_encode_array2(buf, base + 4, &nfsacl_desc.desc);
+ if (!err)
+ err = 8 + nfsacl_desc.desc.elem_size *
+ nfsacl_desc.desc.array_len;
+ return err;
+}
+
+struct nfsacl_decode_desc {
+ struct xdr_array2_desc desc;
+ unsigned int count;
+ struct posix_acl *acl;
+};
+
+static int
+xdr_nfsace_decode(struct xdr_array2_desc *desc, void *elem)
+{
+ struct nfsacl_decode_desc *nfsacl_desc =
+ (struct nfsacl_decode_desc *) desc;
+ u32 *p = (u32 *) elem;
+ struct posix_acl_entry *entry;
+
+ if (!nfsacl_desc->acl) {
+ if (desc->array_len > NFS_ACL_MAX_ENTRIES)
+ return -EINVAL;
+ nfsacl_desc->acl = posix_acl_alloc(desc->array_len, GFP_KERNEL);
+ if (!nfsacl_desc->acl)
+ return -ENOMEM;
+ nfsacl_desc->count = 0;
+ }
+
+ entry = &nfsacl_desc->acl->a_entries[nfsacl_desc->count++];
+ entry->e_tag = ntohl(*p++) & ~NFS_ACL_DEFAULT;
+ entry->e_id = ntohl(*p++);
+ entry->e_perm = ntohl(*p++);
+
+ switch(entry->e_tag) {
+ case ACL_USER_OBJ:
+ case ACL_USER:
+ case ACL_GROUP_OBJ:
+ case ACL_GROUP:
+ case ACL_OTHER:
+ if (entry->e_perm & ~S_IRWXO)
+ return -EINVAL;
+ break;
+ case ACL_MASK:
+ /* Solaris sometimes sets additonal bits in the mask */
+ entry->e_perm &= S_IRWXO;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int
+cmp_acl_entry(const void *x, const void *y)
+{
+ const struct posix_acl_entry *a = x, *b = y;
+
+ if (a->e_tag != b->e_tag)
+ return a->e_tag - b->e_tag;
+ else if (a->e_id > b->e_id)
+ return 1;
+ else if (a->e_id < b->e_id)
+ return -1;
+ else
+ return 0;
+}
+
+/*
+ * Convert from a Solaris ACL to a POSIX 1003.1e draft 17 ACL.
+ */
+static int
+posix_acl_from_nfsacl(struct posix_acl *acl)
+{
+ struct posix_acl_entry *pa, *pe,
+ *group_obj = NULL, *mask = NULL;
+
+ if (!acl)
+ return 0;
+
+ sort(acl->a_entries, acl->a_count, sizeof(struct posix_acl_entry),
+ cmp_acl_entry, NULL);
+
+ /* Clear undefined identifier fields and find the ACL_GROUP_OBJ
+ and ACL_MASK entries. */
+ FOREACH_ACL_ENTRY(pa, acl, pe) {
+ switch(pa->e_tag) {
+ case ACL_USER_OBJ:
+ pa->e_id = ACL_UNDEFINED_ID;
+ break;
+ case ACL_GROUP_OBJ:
+ pa->e_id = ACL_UNDEFINED_ID;
+ group_obj = pa;
+ break;
+ case ACL_MASK:
+ mask = pa;
+ /* fall through */
+ case ACL_OTHER:
+ pa->e_id = ACL_UNDEFINED_ID;
+ break;
+ }
+ }
+ if (acl->a_count == 4 && group_obj && mask &&
+ mask->e_perm == group_obj->e_perm) {
+ /* remove bogus ACL_MASK entry */
+ memmove(mask, mask+1, (3 - (mask - acl->a_entries)) *
+ sizeof(struct posix_acl_entry));
+ acl->a_count = 3;
+ }
+ return 0;
+}
+
+unsigned int
+nfsacl_decode(struct xdr_buf *buf, unsigned int base, unsigned int *aclcnt,
+ struct posix_acl **pacl)
+{
+ struct nfsacl_decode_desc nfsacl_desc = {
+ .desc = {
+ .elem_size = 12,
+ .xcode = pacl ? xdr_nfsace_decode : NULL,
+ },
+ };
+ u32 entries;
+ int err;
+
+ if (xdr_decode_word(buf, base, &entries) ||
+ entries > NFS_ACL_MAX_ENTRIES)
+ return -EINVAL;
+ err = xdr_decode_array2(buf, base + 4, &nfsacl_desc.desc);
+ if (err)
+ return err;
+ if (pacl) {
+ if (entries != nfsacl_desc.desc.array_len ||
+ posix_acl_from_nfsacl(nfsacl_desc.acl) != 0) {
+ posix_acl_release(nfsacl_desc.acl);
+ return -EINVAL;
+ }
+ *pacl = nfsacl_desc.acl;
+ }
+ if (aclcnt)
+ *aclcnt = entries;
+ return 8 + nfsacl_desc.desc.elem_size *
+ nfsacl_desc.desc.array_len;
+}
diff --git a/fs/nfsd/Makefile b/fs/nfsd/Makefile
index b8680a247f8b..9f043f44c92f 100644
--- a/fs/nfsd/Makefile
+++ b/fs/nfsd/Makefile
@@ -6,7 +6,9 @@ obj-$(CONFIG_NFSD) += nfsd.o
nfsd-y := nfssvc.o nfsctl.o nfsproc.o nfsfh.o vfs.o \
export.o auth.o lockd.o nfscache.o nfsxdr.o stats.o
+nfsd-$(CONFIG_NFSD_V2_ACL) += nfs2acl.o
nfsd-$(CONFIG_NFSD_V3) += nfs3proc.o nfs3xdr.o
+nfsd-$(CONFIG_NFSD_V3_ACL) += nfs3acl.o
nfsd-$(CONFIG_NFSD_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4idmap.o \
nfs4acl.o nfs4callback.o
nfsd-objs := $(nfsd-y)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
new file mode 100644
index 000000000000..7cbf0682b2f0
--- /dev/null
+++ b/fs/nfsd/nfs2acl.c
@@ -0,0 +1,336 @@
+/*
+ * linux/fs/nfsd/nfsacl.c
+ *
+ * Process version 2 NFSACL requests.
+ *
+ * Copyright (C) 2002-2003 Andreas Gruenbacher <agruen@suse.de>
+ */
+
+#include <linux/sunrpc/svc.h>
+#include <linux/nfs.h>
+#include <linux/nfsd/nfsd.h>
+#include <linux/nfsd/cache.h>
+#include <linux/nfsd/xdr.h>
+#include <linux/nfsd/xdr3.h>
+#include <linux/posix_acl.h>
+#include <linux/nfsacl.h>
+
+#define NFSDDBG_FACILITY NFSDDBG_PROC
+#define RETURN_STATUS(st) { resp->status = (st); return (st); }
+
+/*
+ * NULL call.
+ */
+static int
+nfsacld_proc_null(struct svc_rqst *rqstp, void *argp, void *resp)
+{
+ return nfs_ok;
+}
+
+/*
+ * Get the Access and/or Default ACL of a file.
+ */
+static int nfsacld_proc_getacl(struct svc_rqst * rqstp,
+ struct nfsd3_getaclargs *argp, struct nfsd3_getaclres *resp)
+{
+ svc_fh *fh;
+ struct posix_acl *acl;
+ int nfserr = 0;
+
+ dprintk("nfsd: GETACL(2acl) %s\n", SVCFH_fmt(&argp->fh));
+
+ fh = fh_copy(&resp->fh, &argp->fh);
+ if ((nfserr = fh_verify(rqstp, &resp->fh, 0, MAY_NOP)))
+ RETURN_STATUS(nfserr_inval);
+
+ if (argp->mask & ~(NFS_ACL|NFS_ACLCNT|NFS_DFACL|NFS_DFACLCNT))
+ RETURN_STATUS(nfserr_inval);
+ resp->mask = argp->mask;
+
+ if (resp->mask & (NFS_ACL|NFS_ACLCNT)) {
+ acl = nfsd_get_posix_acl(fh, ACL_TYPE_ACCESS);
+ if (IS_ERR(acl)) {
+ int err = PTR_ERR(acl);
+
+ if (err == -ENODATA || err == -EOPNOTSUPP)
+ acl = NULL;
+ else {
+ nfserr = nfserrno(err);
+ goto fail;
+ }
+ }
+ if (acl == NULL) {
+ /* Solaris returns the inode's minimum ACL. */
+
+ struct inode *inode = fh->fh_dentry->d_inode;
+ acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
+ }
+ resp->acl_access = acl;
+ }
+ if (resp->mask & (NFS_DFACL|NFS_DFACLCNT)) {
+ /* Check how Solaris handles requests for the Default ACL
+ of a non-directory! */
+
+ acl = nfsd_get_posix_acl(fh, ACL_TYPE_DEFAULT);
+ if (IS_ERR(acl)) {
+ int err = PTR_ERR(acl);
+
+ if (err == -ENODATA || err == -EOPNOTSUPP)
+ acl = NULL;
+ else {
+ nfserr = nfserrno(err);
+ goto fail;
+ }
+ }
+ resp->acl_default = acl;
+ }
+
+ /* resp->acl_{access,default} are released in nfssvc_release_getacl. */
+ RETURN_STATUS(0);
+
+fail:
+ posix_acl_release(resp->acl_access);
+ posix_acl_release(resp->acl_default);
+ RETURN_STATUS(nfserr);
+}
+
+/*
+ * Set the Access and/or Default ACL of a file.
+ */
+static int nfsacld_proc_setacl(struct svc_rqst * rqstp,
+ struct nfsd3_setaclargs *argp,
+ struct nfsd_attrstat *resp)
+{
+ svc_fh *fh;
+ int nfserr = 0;
+
+ dprintk("nfsd: SETACL(2acl) %s\n", SVCFH_fmt(&argp->fh));
+
+ fh = fh_copy(&resp->fh, &argp->fh);
+ nfserr = fh_verify(rqstp, &resp->fh, 0, MAY_NOP);
+
+ if (!nfserr) {
+ nfserr = nfserrno( nfsd_set_posix_acl(
+ fh, ACL_TYPE_ACCESS, argp->acl_access) );
+ }
+ if (!nfserr) {
+ nfserr = nfserrno( nfsd_set_posix_acl(
+ fh, ACL_TYPE_DEFAULT, argp->acl_default) );
+ }
+
+ /* argp->acl_{access,default} may have been allocated in
+ nfssvc_decode_setaclargs. */
+ posix_acl_release(argp->acl_access);
+ posix_acl_release(argp->acl_default);
+ return nfserr;
+}
+
+/*
+ * Check file attributes
+ */
+static int nfsacld_proc_getattr(struct svc_rqst * rqstp,
+ struct nfsd_fhandle *argp, struct nfsd_attrstat *resp)
+{
+ dprintk("nfsd: GETATTR %s\n", SVCFH_fmt(&argp->fh));
+
+ fh_copy(&resp->fh, &argp->fh);
+ return fh_verify(rqstp, &resp->fh, 0, MAY_NOP);
+}
+
+/*
+ * Check file access
+ */
+static int nfsacld_proc_access(struct svc_rqst *rqstp, struct nfsd3_accessargs *argp,
+ struct nfsd3_accessres *resp)
+{
+ int nfserr;
+
+ dprintk("nfsd: ACCESS(2acl) %s 0x%x\n",
+ SVCFH_fmt(&argp->fh),
+ argp->access);
+
+ fh_copy(&resp->fh, &argp->fh);
+ resp->access = argp->access;
+ nfserr = nfsd_access(rqstp, &resp->fh, &resp->access, NULL);
+ return nfserr;
+}
+
+/*
+ * XDR decode functions
+ */
+static int nfsaclsvc_decode_getaclargs(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_getaclargs *argp)
+{
+ if (!(p = nfs2svc_decode_fh(p, &argp->fh)))
+ return 0;
+ argp->mask = ntohl(*p); p++;
+
+ return xdr_argsize_check(rqstp, p);
+}
+
+
+static int nfsaclsvc_decode_setaclargs(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_setaclargs *argp)
+{
+ struct kvec *head = rqstp->rq_arg.head;
+ unsigned int base;
+ int n;
+
+ if (!(p = nfs2svc_decode_fh(p, &argp->fh)))
+ return 0;
+ argp->mask = ntohl(*p++);
+ if (argp->mask & ~(NFS_ACL|NFS_ACLCNT|NFS_DFACL|NFS_DFACLCNT) ||
+ !xdr_argsize_check(rqstp, p))
+ return 0;
+
+ base = (char *)p - (char *)head->iov_base;
+ n = nfsacl_decode(&rqstp->rq_arg, base, NULL,
+ (argp->mask & NFS_ACL) ?
+ &argp->acl_access : NULL);
+ if (n > 0)
+ n = nfsacl_decode(&rqstp->rq_arg, base + n, NULL,
+ (argp->mask & NFS_DFACL) ?
+ &argp->acl_default : NULL);
+ return (n > 0);
+}
+
+static int nfsaclsvc_decode_fhandleargs(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd_fhandle *argp)
+{
+ if (!(p = nfs2svc_decode_fh(p, &argp->fh)))
+ return 0;
+ return xdr_argsize_check(rqstp, p);
+}
+
+static int nfsaclsvc_decode_accessargs(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_accessargs *argp)
+{
+ if (!(p = nfs2svc_decode_fh(p, &argp->fh)))
+ return 0;
+ argp->access = ntohl(*p++);
+
+ return xdr_argsize_check(rqstp, p);
+}
+
+/*
+ * XDR encode functions
+ */
+
+/* GETACL */
+static int nfsaclsvc_encode_getaclres(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_getaclres *resp)
+{
+ struct dentry *dentry = resp->fh.fh_dentry;
+ struct inode *inode = dentry->d_inode;
+ int w = nfsacl_size(
+ (resp->mask & NFS_ACL) ? resp->acl_access : NULL,
+ (resp->mask & NFS_DFACL) ? resp->acl_default : NULL);
+ struct kvec *head = rqstp->rq_res.head;
+ unsigned int base;
+ int n;
+
+ if (dentry == NULL || dentry->d_inode == NULL)
+ return 0;
+ inode = dentry->d_inode;
+
+ p = nfs2svc_encode_fattr(rqstp, p, &resp->fh);
+ *p++ = htonl(resp->mask);
+ if (!xdr_ressize_check(rqstp, p))
+ return 0;
+ base = (char *)p - (char *)head->iov_base;
+
+ rqstp->rq_res.page_len = w;
+ while (w > 0) {
+ if (!svc_take_res_page(rqstp))
+ return 0;
+ w -= PAGE_SIZE;
+ }
+
+ n = nfsacl_encode(&rqstp->rq_res, base, inode,
+ resp->acl_access,
+ resp->mask & NFS_ACL, 0);
+ if (n > 0)
+ n = nfsacl_encode(&rqstp->rq_res, base + n, inode,
+ resp->acl_default,
+ resp->mask & NFS_DFACL,
+ NFS_ACL_DEFAULT);
+ if (n <= 0)
+ return 0;
+ return 1;
+}
+
+static int nfsaclsvc_encode_attrstatres(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd_attrstat *resp)
+{
+ p = nfs2svc_encode_fattr(rqstp, p, &resp->fh);
+ return xdr_ressize_check(rqstp, p);
+}
+
+/* ACCESS */
+static int nfsaclsvc_encode_accessres(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_accessres *resp)
+{
+ p = nfs2svc_encode_fattr(rqstp, p, &resp->fh);
+ *p++ = htonl(resp->access);
+ return xdr_ressize_check(rqstp, p);
+}
+
+/*
+ * XDR release functions
+ */
+static int nfsaclsvc_release_getacl(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_getaclres *resp)
+{
+ fh_put(&resp->fh);
+ posix_acl_release(resp->acl_access);
+ posix_acl_release(resp->acl_default);
+ return 1;
+}
+
+static int nfsaclsvc_release_fhandle(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd_fhandle *resp)
+{
+ fh_put(&resp->fh);
+ return 1;
+}
+
+#define nfsaclsvc_decode_voidargs NULL
+#define nfsaclsvc_encode_voidres NULL
+#define nfsaclsvc_release_void NULL
+#define nfsd3_fhandleargs nfsd_fhandle
+#define nfsd3_attrstatres nfsd_attrstat
+#define nfsd3_voidres nfsd3_voidargs
+struct nfsd3_voidargs { int dummy; };
+
+#define PROC(name, argt, rest, relt, cache, respsize) \
+ { (svc_procfunc) nfsacld_proc_##name, \
+ (kxdrproc_t) nfsaclsvc_decode_##argt##args, \
+ (kxdrproc_t) nfsaclsvc_encode_##rest##res, \
+ (kxdrproc_t) nfsaclsvc_release_##relt, \
+ sizeof(struct nfsd3_##argt##args), \
+ sizeof(struct nfsd3_##rest##res), \
+ 0, \
+ cache, \
+ respsize, \
+ }
+
+#define ST 1 /* status*/
+#define AT 21 /* attributes */
+#define pAT (1+AT) /* post attributes - conditional */
+#define ACL (1+NFS_ACL_MAX_ENTRIES*3) /* Access Control List */
+
+static struct svc_procedure nfsd_acl_procedures2[] = {
+ PROC(null, void, void, void, RC_NOCACHE, ST),
+ PROC(getacl, getacl, getacl, getacl, RC_NOCACHE, ST+1+2*(1+ACL)),
+ PROC(setacl, setacl, attrstat, fhandle, RC_NOCACHE, ST+AT),
+ PROC(getattr, fhandle, attrstat, fhandle, RC_NOCACHE, ST+AT),
+ PROC(access, access, access, fhandle, RC_NOCACHE, ST+AT+1),
+};
+
+struct svc_version nfsd_acl_version2 = {
+ .vs_vers = 2,
+ .vs_nproc = 5,
+ .vs_proc = nfsd_acl_procedures2,
+ .vs_dispatch = nfsd_dispatch,
+ .vs_xdrsize = NFS3_SVC_XDRSIZE,
+};
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
new file mode 100644
index 000000000000..64ba40572fea
--- /dev/null
+++ b/fs/nfsd/nfs3acl.c
@@ -0,0 +1,267 @@
+/*
+ * linux/fs/nfsd/nfs3acl.c
+ *
+ * Process version 3 NFSACL requests.
+ *
+ * Copyright (C) 2002-2003 Andreas Gruenbacher <agruen@suse.de>
+ */
+
+#include <linux/sunrpc/svc.h>
+#include <linux/nfs3.h>
+#include <linux/nfsd/nfsd.h>
+#include <linux/nfsd/cache.h>
+#include <linux/nfsd/xdr3.h>
+#include <linux/posix_acl.h>
+#include <linux/nfsacl.h>
+
+#define RETURN_STATUS(st) { resp->status = (st); return (st); }
+
+/*
+ * NULL call.
+ */
+static int
+nfsd3_proc_null(struct svc_rqst *rqstp, void *argp, void *resp)
+{
+ return nfs_ok;
+}
+
+/*
+ * Get the Access and/or Default ACL of a file.
+ */
+static int nfsd3_proc_getacl(struct svc_rqst * rqstp,
+ struct nfsd3_getaclargs *argp, struct nfsd3_getaclres *resp)
+{
+ svc_fh *fh;
+ struct posix_acl *acl;
+ int nfserr = 0;
+
+ fh = fh_copy(&resp->fh, &argp->fh);
+ if ((nfserr = fh_verify(rqstp, &resp->fh, 0, MAY_NOP)))
+ RETURN_STATUS(nfserr_inval);
+
+ if (argp->mask & ~(NFS_ACL|NFS_ACLCNT|NFS_DFACL|NFS_DFACLCNT))
+ RETURN_STATUS(nfserr_inval);
+ resp->mask = argp->mask;
+
+ if (resp->mask & (NFS_ACL|NFS_ACLCNT)) {
+ acl = nfsd_get_posix_acl(fh, ACL_TYPE_ACCESS);
+ if (IS_ERR(acl)) {
+ int err = PTR_ERR(acl);
+
+ if (err == -ENODATA || err == -EOPNOTSUPP)
+ acl = NULL;
+ else {
+ nfserr = nfserrno(err);
+ goto fail;
+ }
+ }
+ if (acl == NULL) {
+ /* Solaris returns the inode's minimum ACL. */
+
+ struct inode *inode = fh->fh_dentry->d_inode;
+ acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
+ }
+ resp->acl_access = acl;
+ }
+ if (resp->mask & (NFS_DFACL|NFS_DFACLCNT)) {
+ /* Check how Solaris handles requests for the Default ACL
+ of a non-directory! */
+
+ acl = nfsd_get_posix_acl(fh, ACL_TYPE_DEFAULT);
+ if (IS_ERR(acl)) {
+ int err = PTR_ERR(acl);
+
+ if (err == -ENODATA || err == -EOPNOTSUPP)
+ acl = NULL;
+ else {
+ nfserr = nfserrno(err);
+ goto fail;
+ }
+ }
+ resp->acl_default = acl;
+ }
+
+ /* resp->acl_{access,default} are released in nfs3svc_release_getacl. */
+ RETURN_STATUS(0);
+
+fail:
+ posix_acl_release(resp->acl_access);
+ posix_acl_release(resp->acl_default);
+ RETURN_STATUS(nfserr);
+}
+
+/*
+ * Set the Access and/or Default ACL of a file.
+ */
+static int nfsd3_proc_setacl(struct svc_rqst * rqstp,
+ struct nfsd3_setaclargs *argp,
+ struct nfsd3_attrstat *resp)
+{
+ svc_fh *fh;
+ int nfserr = 0;
+
+ fh = fh_copy(&resp->fh, &argp->fh);
+ nfserr = fh_verify(rqstp, &resp->fh, 0, MAY_NOP);
+
+ if (!nfserr) {
+ nfserr = nfserrno( nfsd_set_posix_acl(
+ fh, ACL_TYPE_ACCESS, argp->acl_access) );
+ }
+ if (!nfserr) {
+ nfserr = nfserrno( nfsd_set_posix_acl(
+ fh, ACL_TYPE_DEFAULT, argp->acl_default) );
+ }
+
+ /* argp->acl_{access,default} may have been allocated in
+ nfs3svc_decode_setaclargs. */
+ posix_acl_release(argp->acl_access);
+ posix_acl_release(argp->acl_default);
+ RETURN_STATUS(nfserr);
+}
+
+/*
+ * XDR decode functions
+ */
+static int nfs3svc_decode_getaclargs(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_getaclargs *args)
+{
+ if (!(p = nfs3svc_decode_fh(p, &args->fh)))
+ return 0;
+ args->mask = ntohl(*p); p++;
+
+ return xdr_argsize_check(rqstp, p);
+}
+
+
+static int nfs3svc_decode_setaclargs(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_setaclargs *args)
+{
+ struct kvec *head = rqstp->rq_arg.head;
+ unsigned int base;
+ int n;
+
+ if (!(p = nfs3svc_decode_fh(p, &args->fh)))
+ return 0;
+ args->mask = ntohl(*p++);
+ if (args->mask & ~(NFS_ACL|NFS_ACLCNT|NFS_DFACL|NFS_DFACLCNT) ||
+ !xdr_argsize_check(rqstp, p))
+ return 0;
+
+ base = (char *)p - (char *)head->iov_base;
+ n = nfsacl_decode(&rqstp->rq_arg, base, NULL,
+ (args->mask & NFS_ACL) ?
+ &args->acl_access : NULL);
+ if (n > 0)
+ n = nfsacl_decode(&rqstp->rq_arg, base + n, NULL,
+ (args->mask & NFS_DFACL) ?
+ &args->acl_default : NULL);
+ return (n > 0);
+}
+
+/*
+ * XDR encode functions
+ */
+
+/* GETACL */
+static int nfs3svc_encode_getaclres(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_getaclres *resp)
+{
+ struct dentry *dentry = resp->fh.fh_dentry;
+
+ p = nfs3svc_encode_post_op_attr(rqstp, p, &resp->fh);
+ if (resp->status == 0 && dentry && dentry->d_inode) {
+ struct inode *inode = dentry->d_inode;
+ int w = nfsacl_size(
+ (resp->mask & NFS_ACL) ? resp->acl_access : NULL,
+ (resp->mask & NFS_DFACL) ? resp->acl_default : NULL);
+ struct kvec *head = rqstp->rq_res.head;
+ unsigned int base;
+ int n;
+
+ *p++ = htonl(resp->mask);
+ if (!xdr_ressize_check(rqstp, p))
+ return 0;
+ base = (char *)p - (char *)head->iov_base;
+
+ rqstp->rq_res.page_len = w;
+ while (w > 0) {
+ if (!svc_take_res_page(rqstp))
+ return 0;
+ w -= PAGE_SIZE;
+ }
+
+ n = nfsacl_encode(&rqstp->rq_res, base, inode,
+ resp->acl_access,
+ resp->mask & NFS_ACL, 0);
+ if (n > 0)
+ n = nfsacl_encode(&rqstp->rq_res, base + n, inode,
+ resp->acl_default,
+ resp->mask & NFS_DFACL,
+ NFS_ACL_DEFAULT);
+ if (n <= 0)
+ return 0;
+ } else
+ if (!xdr_ressize_check(rqstp, p))
+ return 0;
+
+ return 1;
+}
+
+/* SETACL */
+static int nfs3svc_encode_setaclres(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_attrstat *resp)
+{
+ p = nfs3svc_encode_post_op_attr(rqstp, p, &resp->fh);
+
+ return xdr_ressize_check(rqstp, p);
+}
+
+/*
+ * XDR release functions
+ */
+static int nfs3svc_release_getacl(struct svc_rqst *rqstp, u32 *p,
+ struct nfsd3_getaclres *resp)
+{
+ fh_put(&resp->fh);
+ posix_acl_release(resp->acl_access);
+ posix_acl_release(resp->acl_default);
+ return 1;
+}
+
+#define nfs3svc_decode_voidargs NULL
+#define nfs3svc_release_void NULL
+#define nfsd3_setaclres nfsd3_attrstat
+#define nfsd3_voidres nfsd3_voidargs
+struct nfsd3_voidargs { int dummy; };
+
+#define PROC(name, argt, rest, relt, cache, respsize) \
+ { (svc_procfunc) nfsd3_proc_##name, \
+ (kxdrproc_t) nfs3svc_decode_##argt##args, \
+ (kxdrproc_t) nfs3svc_encode_##rest##res, \
+ (kxdrproc_t) nfs3svc_release_##relt, \
+ sizeof(struct nfsd3_##argt##args), \
+ sizeof(struct nfsd3_##rest##res), \
+ 0, \
+ cache, \
+ respsize, \
+ }
+
+#define ST 1 /* status*/
+#define AT 21 /* attributes */
+#define pAT (1+AT) /* post attributes - conditional */
+#define ACL (1+NFS_ACL_MAX_ENTRIES*3) /* Access Control List */
+
+static struct svc_procedure nfsd_acl_procedures3[] = {
+ PROC(null, void, void, void, RC_NOCACHE, ST),
+ PROC(getacl, getacl, getacl, getacl, RC_NOCACHE, ST+1+2*(1+ACL)),
+ PROC(setacl, setacl, setacl, fhandle, RC_NOCACHE, ST+pAT),
+};
+
+struct svc_version nfsd_acl_version3 = {
+ .vs_vers = 3,
+ .vs_nproc = 3,
+ .vs_proc = nfsd_acl_procedures3,
+ .vs_dispatch = nfsd_dispatch,
+ .vs_xdrsize = NFS3_SVC_XDRSIZE,
+};
+
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 11f806835c5a..e0e134d6baba 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -71,6 +71,12 @@ decode_fh(u32 *p, struct svc_fh *fhp)
return p + XDR_QUADLEN(size);
}
+/* Helper function for NFSv3 ACL code */
+u32 *nfs3svc_decode_fh(u32 *p, struct svc_fh *fhp)
+{
+ return decode_fh(p, fhp);
+}
+
static inline u32 *
encode_fh(u32 *p, struct svc_fh *fhp)
{
@@ -233,6 +239,13 @@ encode_post_op_attr(struct svc_rqst *rqstp, u32 *p, struct svc_fh *fhp)
return p;
}
+/* Helper for NFSv3 ACLs */
+u32 *
+nfs3svc_encode_post_op_attr(struct svc_rqst *rqstp, u32 *p, struct svc_fh *fhp)
+{
+ return encode_post_op_attr(rqstp, p, fhp);
+}
+
/*
* Enocde weak cache consistency data
*/
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c
index 02ded7cfbdcf..79b25b19fec8 100644
--- a/fs/nfsd/nfssvc.c
+++ b/fs/nfsd/nfssvc.c
@@ -31,6 +31,7 @@
#include <linux/nfsd/stats.h>
#include <linux/nfsd/cache.h>
#include <linux/lockd/bind.h>
+#include <linux/nfsacl.h>
#define NFSDDBG_FACILITY NFSDDBG_SVC
@@ -362,6 +363,31 @@ nfsd_dispatch(struct svc_rqst *rqstp, u32 *statp)
return 1;
}
+#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
+static struct svc_stat nfsd_acl_svcstats;
+static struct svc_version * nfsd_acl_version[] = {
+ [2] = &nfsd_acl_version2,
+ [3] = &nfsd_acl_version3,
+};
+
+#define NFSD_ACL_NRVERS (sizeof(nfsd_acl_version)/sizeof(nfsd_acl_version[0]))
+static struct svc_program nfsd_acl_program = {
+ .pg_prog = NFS_ACL_PROGRAM,
+ .pg_nvers = NFSD_ACL_NRVERS,
+ .pg_vers = nfsd_acl_version,
+ .pg_name = "nfsd",
+ .pg_stats = &nfsd_acl_svcstats,
+};
+
+static struct svc_stat nfsd_acl_svcstats = {
+ .program = &nfsd_acl_program,
+};
+
+#define nfsd_acl_program_p &nfsd_acl_program
+#else
+#define nfsd_acl_program_p NULL
+#endif /* defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) */
+
extern struct svc_version nfsd_version2, nfsd_version3, nfsd_version4;
static struct svc_version * nfsd_version[] = {
@@ -376,6 +402,7 @@ static struct svc_version * nfsd_version[] = {
#define NFSD_NRVERS (sizeof(nfsd_version)/sizeof(nfsd_version[0]))
struct svc_program nfsd_program = {
+ .pg_next = nfsd_acl_program_p,
.pg_prog = NFS_PROGRAM, /* program number */
.pg_nvers = NFSD_NRVERS, /* nr of entries in nfsd_version */
.pg_vers = nfsd_version, /* version table */
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 948b08287c99..b45999ff33e6 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -49,6 +49,12 @@ decode_fh(u32 *p, struct svc_fh *fhp)
return p + (NFS_FHSIZE >> 2);
}
+/* Helper function for NFSv2 ACL code */
+u32 *nfs2svc_decode_fh(u32 *p, struct svc_fh *fhp)
+{
+ return decode_fh(p, fhp);
+}
+
static inline u32 *
encode_fh(u32 *p, struct svc_fh *fhp)
{
@@ -190,6 +196,11 @@ encode_fattr(struct svc_rqst *rqstp, u32 *p, struct svc_fh *fhp)
return p;
}
+/* Helper function for NFSv2 ACL code */
+u32 *nfs2svc_encode_fattr(struct svc_rqst *rqstp, u32 *p, struct svc_fh *fhp)
+{
+ return encode_fattr(rqstp, p, fhp);
+}
/*
* XDR decode functions
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index e3e9d217236e..ae3940dc85cc 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -46,8 +46,9 @@
#include <linux/nfsd/nfsfh.h>
#include <linux/quotaops.h>
#include <linux/dnotify.h>
-#ifdef CONFIG_NFSD_V4
+#include <linux/xattr_acl.h>
#include <linux/posix_acl.h>
+#ifdef CONFIG_NFSD_V4
#include <linux/posix_acl_xattr.h>
#include <linux/xattr_acl.h>
#include <linux/xattr.h>
@@ -1857,3 +1858,107 @@ nfsd_racache_init(int cache_size)
nfsdstats.ra_size = cache_size;
return 0;
}
+
+#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
+struct posix_acl *
+nfsd_get_posix_acl(struct svc_fh *fhp, int type)
+{
+ struct inode *inode = fhp->fh_dentry->d_inode;
+ char *name;
+ void *value = NULL;
+ ssize_t size;
+ struct posix_acl *acl;
+
+ if (!IS_POSIXACL(inode) || !inode->i_op || !inode->i_op->getxattr)
+ return ERR_PTR(-EOPNOTSUPP);
+ switch(type) {
+ case ACL_TYPE_ACCESS:
+ name = XATTR_NAME_ACL_ACCESS;
+ break;
+ case ACL_TYPE_DEFAULT:
+ name = XATTR_NAME_ACL_DEFAULT;
+ break;
+ default:
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+
+ size = inode->i_op->getxattr(fhp->fh_dentry, name, NULL, 0);
+
+ if (size < 0) {
+ acl = ERR_PTR(size);
+ goto getout;
+ } else if (size > 0) {
+ value = kmalloc(size, GFP_KERNEL);
+ if (!value) {
+ acl = ERR_PTR(-ENOMEM);
+ goto getout;
+ }
+ size = inode->i_op->getxattr(fhp->fh_dentry, name, value, size);
+ if (size < 0) {
+ acl = ERR_PTR(size);
+ goto getout;
+ }
+ }
+ acl = posix_acl_from_xattr(value, size);
+
+getout:
+ kfree(value);
+ return acl;
+}
+
+int
+nfsd_set_posix_acl(struct svc_fh *fhp, int type, struct posix_acl *acl)
+{
+ struct inode *inode = fhp->fh_dentry->d_inode;
+ char *name;
+ void *value = NULL;
+ size_t size;
+ int error;
+
+ if (!IS_POSIXACL(inode) || !inode->i_op ||
+ !inode->i_op->setxattr || !inode->i_op->removexattr)
+ return -EOPNOTSUPP;
+ switch(type) {
+ case ACL_TYPE_ACCESS:
+ name = XATTR_NAME_ACL_ACCESS;
+ break;
+ case ACL_TYPE_DEFAULT:
+ name = XATTR_NAME_ACL_DEFAULT;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (acl && acl->a_count) {
+ size = xattr_acl_size(acl->a_count);
+ value = kmalloc(size, GFP_KERNEL);
+ if (!value)
+ return -ENOMEM;
+ size = posix_acl_to_xattr(acl, value, size);
+ if (size < 0) {
+ error = size;
+ goto getout;
+ }
+ } else
+ size = 0;
+
+ if (!fhp->fh_locked)
+ fh_lock(fhp); /* unlocking is done automatically */
+ if (size)
+ error = inode->i_op->setxattr(fhp->fh_dentry, name,
+ value, size, 0);
+ else {
+ if (!S_ISDIR(inode->i_mode) && type == ACL_TYPE_DEFAULT)
+ error = 0;
+ else {
+ error = inode->i_op->removexattr(fhp->fh_dentry, name);
+ if (error == -ENODATA)
+ error = 0;
+ }
+ }
+
+getout:
+ kfree(value);
+ return error;
+}
+#endif /* defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) */