xref: /openbmc/linux/fs/nfs/nfs3acl.c (revision 3fc3edf1)
1b7fa0554SAndreas Gruenbacher #include <linux/fs.h>
25a0e3ad6STejun Heo #include <linux/gfp.h>
3b7fa0554SAndreas Gruenbacher #include <linux/nfs.h>
4b7fa0554SAndreas Gruenbacher #include <linux/nfs3.h>
5b7fa0554SAndreas Gruenbacher #include <linux/nfs_fs.h>
6334a13ecSChristoph Hellwig #include <linux/posix_acl_xattr.h>
7b7fa0554SAndreas Gruenbacher #include <linux/nfsacl.h>
8b7fa0554SAndreas Gruenbacher 
9f41f7418STrond Myklebust #include "internal.h"
103fc3edf1STrond Myklebust #include "nfs3_fs.h"
11f41f7418STrond Myklebust 
12b7fa0554SAndreas Gruenbacher #define NFSDBG_FACILITY	NFSDBG_PROC
13b7fa0554SAndreas Gruenbacher 
14013cdf10SChristoph Hellwig struct posix_acl *nfs3_get_acl(struct inode *inode, int type)
15b7fa0554SAndreas Gruenbacher {
16b7fa0554SAndreas Gruenbacher 	struct nfs_server *server = NFS_SERVER(inode);
17b7fa0554SAndreas Gruenbacher 	struct page *pages[NFSACL_MAXPAGES] = { };
18b7fa0554SAndreas Gruenbacher 	struct nfs3_getaclargs args = {
19b7fa0554SAndreas Gruenbacher 		.fh = NFS_FH(inode),
20b7fa0554SAndreas Gruenbacher 		/* The xdr layer may allocate pages here. */
21b7fa0554SAndreas Gruenbacher 		.pages = pages,
22b7fa0554SAndreas Gruenbacher 	};
23b7fa0554SAndreas Gruenbacher 	struct nfs3_getaclres res = {
2417280175STrond Myklebust 		NULL,
25b7fa0554SAndreas Gruenbacher 	};
26dead28daSChuck Lever 	struct rpc_message msg = {
27dead28daSChuck Lever 		.rpc_argp	= &args,
28dead28daSChuck Lever 		.rpc_resp	= &res,
29dead28daSChuck Lever 	};
30b7fa0554SAndreas Gruenbacher 	int status, count;
31b7fa0554SAndreas Gruenbacher 
32b7fa0554SAndreas Gruenbacher 	if (!nfs_server_capable(inode, NFS_CAP_ACLS))
33b7fa0554SAndreas Gruenbacher 		return ERR_PTR(-EOPNOTSUPP);
34b7fa0554SAndreas Gruenbacher 
355c6a9f7dSAndreas Gruenbacher 	status = nfs_revalidate_inode(server, inode);
365c6a9f7dSAndreas Gruenbacher 	if (status < 0)
375c6a9f7dSAndreas Gruenbacher 		return ERR_PTR(status);
38b7fa0554SAndreas Gruenbacher 
395c6a9f7dSAndreas Gruenbacher 	/*
405c6a9f7dSAndreas Gruenbacher 	 * Only get the access acl when explicitly requested: We don't
415c6a9f7dSAndreas Gruenbacher 	 * need it for access decisions, and only some applications use
425c6a9f7dSAndreas Gruenbacher 	 * it. Applications which request the access acl first are not
435c6a9f7dSAndreas Gruenbacher 	 * penalized from this optimization.
445c6a9f7dSAndreas Gruenbacher 	 */
455c6a9f7dSAndreas Gruenbacher 	if (type == ACL_TYPE_ACCESS)
465c6a9f7dSAndreas Gruenbacher 		args.mask |= NFS_ACLCNT|NFS_ACL;
475c6a9f7dSAndreas Gruenbacher 	if (S_ISDIR(inode->i_mode))
485c6a9f7dSAndreas Gruenbacher 		args.mask |= NFS_DFACLCNT|NFS_DFACL;
495c6a9f7dSAndreas Gruenbacher 	if (args.mask == 0)
50b7fa0554SAndreas Gruenbacher 		return NULL;
51b7fa0554SAndreas Gruenbacher 
52b7fa0554SAndreas Gruenbacher 	dprintk("NFS call getacl\n");
53dead28daSChuck Lever 	msg.rpc_proc = &server->client_acl->cl_procinfo[ACLPROC3_GETACL];
546e94d629STrond Myklebust 	res.fattr = nfs_alloc_fattr();
556e94d629STrond Myklebust 	if (res.fattr == NULL)
566e94d629STrond Myklebust 		return ERR_PTR(-ENOMEM);
576e94d629STrond Myklebust 
58dead28daSChuck Lever 	status = rpc_call_sync(server->client_acl, &msg, 0);
59b7fa0554SAndreas Gruenbacher 	dprintk("NFS reply getacl: %d\n", status);
60b7fa0554SAndreas Gruenbacher 
61b7fa0554SAndreas Gruenbacher 	/* pages may have been allocated at the xdr layer. */
62b7fa0554SAndreas Gruenbacher 	for (count = 0; count < NFSACL_MAXPAGES && args.pages[count]; count++)
63b7fa0554SAndreas Gruenbacher 		__free_page(args.pages[count]);
64b7fa0554SAndreas Gruenbacher 
65b7fa0554SAndreas Gruenbacher 	switch (status) {
66b7fa0554SAndreas Gruenbacher 		case 0:
676e94d629STrond Myklebust 			status = nfs_refresh_inode(inode, res.fattr);
68b7fa0554SAndreas Gruenbacher 			break;
69b7fa0554SAndreas Gruenbacher 		case -EPFNOSUPPORT:
70b7fa0554SAndreas Gruenbacher 		case -EPROTONOSUPPORT:
71b7fa0554SAndreas Gruenbacher 			dprintk("NFS_V3_ACL extension not supported; disabling\n");
72b7fa0554SAndreas Gruenbacher 			server->caps &= ~NFS_CAP_ACLS;
73b7fa0554SAndreas Gruenbacher 		case -ENOTSUPP:
74b7fa0554SAndreas Gruenbacher 			status = -EOPNOTSUPP;
75b7fa0554SAndreas Gruenbacher 		default:
76b7fa0554SAndreas Gruenbacher 			goto getout;
77b7fa0554SAndreas Gruenbacher 	}
78b7fa0554SAndreas Gruenbacher 	if ((args.mask & res.mask) != args.mask) {
79b7fa0554SAndreas Gruenbacher 		status = -EIO;
80b7fa0554SAndreas Gruenbacher 		goto getout;
81b7fa0554SAndreas Gruenbacher 	}
82b7fa0554SAndreas Gruenbacher 
83b7fa0554SAndreas Gruenbacher 	if (res.acl_access != NULL) {
84718360c5SNoah Massey 		if ((posix_acl_equiv_mode(res.acl_access, NULL) == 0) ||
85013cdf10SChristoph Hellwig 		    res.acl_access->a_count == 0) {
86b7fa0554SAndreas Gruenbacher 			posix_acl_release(res.acl_access);
87b7fa0554SAndreas Gruenbacher 			res.acl_access = NULL;
88b7fa0554SAndreas Gruenbacher 		}
89b7fa0554SAndreas Gruenbacher 	}
90b7fa0554SAndreas Gruenbacher 
91013cdf10SChristoph Hellwig 	if (res.mask & NFS_ACL)
92013cdf10SChristoph Hellwig 		set_cached_acl(inode, ACL_TYPE_ACCESS, res.acl_access);
93013cdf10SChristoph Hellwig 	else
94013cdf10SChristoph Hellwig 		forget_cached_acl(inode, ACL_TYPE_ACCESS);
95b7fa0554SAndreas Gruenbacher 
96013cdf10SChristoph Hellwig 	if (res.mask & NFS_DFACL)
97013cdf10SChristoph Hellwig 		set_cached_acl(inode, ACL_TYPE_DEFAULT, res.acl_default);
98013cdf10SChristoph Hellwig 	else
99013cdf10SChristoph Hellwig 		forget_cached_acl(inode, ACL_TYPE_DEFAULT);
100013cdf10SChristoph Hellwig 
101013cdf10SChristoph Hellwig 	nfs_free_fattr(res.fattr);
102013cdf10SChristoph Hellwig 	if (type == ACL_TYPE_ACCESS) {
103013cdf10SChristoph Hellwig 		posix_acl_release(res.acl_default);
104013cdf10SChristoph Hellwig 		return res.acl_access;
105013cdf10SChristoph Hellwig 	} else {
106013cdf10SChristoph Hellwig 		posix_acl_release(res.acl_access);
107013cdf10SChristoph Hellwig 		return res.acl_default;
108b7fa0554SAndreas Gruenbacher 	}
109b7fa0554SAndreas Gruenbacher 
110b7fa0554SAndreas Gruenbacher getout:
111b7fa0554SAndreas Gruenbacher 	posix_acl_release(res.acl_access);
112b7fa0554SAndreas Gruenbacher 	posix_acl_release(res.acl_default);
1136e94d629STrond Myklebust 	nfs_free_fattr(res.fattr);
114013cdf10SChristoph Hellwig 	return ERR_PTR(status);
115b7fa0554SAndreas Gruenbacher }
116b7fa0554SAndreas Gruenbacher 
1178f493b9cSTrond Myklebust static int __nfs3_proc_setacls(struct inode *inode, struct posix_acl *acl,
118b7fa0554SAndreas Gruenbacher 		struct posix_acl *dfacl)
119b7fa0554SAndreas Gruenbacher {
120b7fa0554SAndreas Gruenbacher 	struct nfs_server *server = NFS_SERVER(inode);
1216e94d629STrond Myklebust 	struct nfs_fattr *fattr;
122ae46141fSTrond Myklebust 	struct page *pages[NFSACL_MAXPAGES];
123b7fa0554SAndreas Gruenbacher 	struct nfs3_setaclargs args = {
124b7fa0554SAndreas Gruenbacher 		.inode = inode,
125b7fa0554SAndreas Gruenbacher 		.mask = NFS_ACL,
126b7fa0554SAndreas Gruenbacher 		.acl_access = acl,
127b7fa0554SAndreas Gruenbacher 		.pages = pages,
128b7fa0554SAndreas Gruenbacher 	};
129dead28daSChuck Lever 	struct rpc_message msg = {
130dead28daSChuck Lever 		.rpc_argp	= &args,
131dead28daSChuck Lever 		.rpc_resp	= &fattr,
132dead28daSChuck Lever 	};
133f87d928fSTrond Myklebust 	int status = 0;
134f87d928fSTrond Myklebust 
135f87d928fSTrond Myklebust 	if (acl == NULL && (!S_ISDIR(inode->i_mode) || dfacl == NULL))
136f87d928fSTrond Myklebust 		goto out;
137b7fa0554SAndreas Gruenbacher 
138b7fa0554SAndreas Gruenbacher 	status = -EOPNOTSUPP;
139b7fa0554SAndreas Gruenbacher 	if (!nfs_server_capable(inode, NFS_CAP_ACLS))
140b7fa0554SAndreas Gruenbacher 		goto out;
141b7fa0554SAndreas Gruenbacher 
142f61f6da0SChuck Lever 	/* We are doing this here because XDR marshalling does not
143f61f6da0SChuck Lever 	 * return any results, it BUGs. */
144b7fa0554SAndreas Gruenbacher 	status = -ENOSPC;
145b7fa0554SAndreas Gruenbacher 	if (acl != NULL && acl->a_count > NFS_ACL_MAX_ENTRIES)
146b7fa0554SAndreas Gruenbacher 		goto out;
147b7fa0554SAndreas Gruenbacher 	if (dfacl != NULL && dfacl->a_count > NFS_ACL_MAX_ENTRIES)
148b7fa0554SAndreas Gruenbacher 		goto out;
149b7fa0554SAndreas Gruenbacher 	if (S_ISDIR(inode->i_mode)) {
150b7fa0554SAndreas Gruenbacher 		args.mask |= NFS_DFACL;
151b7fa0554SAndreas Gruenbacher 		args.acl_default = dfacl;
152ae46141fSTrond Myklebust 		args.len = nfsacl_size(acl, dfacl);
153ae46141fSTrond Myklebust 	} else
154ae46141fSTrond Myklebust 		args.len = nfsacl_size(acl, NULL);
155ae46141fSTrond Myklebust 
156ae46141fSTrond Myklebust 	if (args.len > NFS_ACL_INLINE_BUFSIZE) {
157ae46141fSTrond Myklebust 		unsigned int npages = 1 + ((args.len - 1) >> PAGE_SHIFT);
158ae46141fSTrond Myklebust 
159ae46141fSTrond Myklebust 		status = -ENOMEM;
160ae46141fSTrond Myklebust 		do {
161ae46141fSTrond Myklebust 			args.pages[args.npages] = alloc_page(GFP_KERNEL);
162ae46141fSTrond Myklebust 			if (args.pages[args.npages] == NULL)
163ae46141fSTrond Myklebust 				goto out_freepages;
164ae46141fSTrond Myklebust 			args.npages++;
165ae46141fSTrond Myklebust 		} while (args.npages < npages);
166b7fa0554SAndreas Gruenbacher 	}
167b7fa0554SAndreas Gruenbacher 
168b7fa0554SAndreas Gruenbacher 	dprintk("NFS call setacl\n");
1696e94d629STrond Myklebust 	status = -ENOMEM;
1706e94d629STrond Myklebust 	fattr = nfs_alloc_fattr();
1716e94d629STrond Myklebust 	if (fattr == NULL)
1726e94d629STrond Myklebust 		goto out_freepages;
1736e94d629STrond Myklebust 
174dead28daSChuck Lever 	msg.rpc_proc = &server->client_acl->cl_procinfo[ACLPROC3_SETACL];
1756e94d629STrond Myklebust 	msg.rpc_resp = fattr;
176dead28daSChuck Lever 	status = rpc_call_sync(server->client_acl, &msg, 0);
177f41f7418STrond Myklebust 	nfs_access_zap_cache(inode);
178f41f7418STrond Myklebust 	nfs_zap_acl_cache(inode);
179b7fa0554SAndreas Gruenbacher 	dprintk("NFS reply setacl: %d\n", status);
180b7fa0554SAndreas Gruenbacher 
181b7fa0554SAndreas Gruenbacher 	switch (status) {
182b7fa0554SAndreas Gruenbacher 		case 0:
1836e94d629STrond Myklebust 			status = nfs_refresh_inode(inode, fattr);
184013cdf10SChristoph Hellwig 			set_cached_acl(inode, ACL_TYPE_ACCESS, acl);
185013cdf10SChristoph Hellwig 			set_cached_acl(inode, ACL_TYPE_DEFAULT, dfacl);
186b7fa0554SAndreas Gruenbacher 			break;
187b7fa0554SAndreas Gruenbacher 		case -EPFNOSUPPORT:
188b7fa0554SAndreas Gruenbacher 		case -EPROTONOSUPPORT:
189b7fa0554SAndreas Gruenbacher 			dprintk("NFS_V3_ACL SETACL RPC not supported"
190b7fa0554SAndreas Gruenbacher 					"(will not retry)\n");
191b7fa0554SAndreas Gruenbacher 			server->caps &= ~NFS_CAP_ACLS;
192b7fa0554SAndreas Gruenbacher 		case -ENOTSUPP:
193b7fa0554SAndreas Gruenbacher 			status = -EOPNOTSUPP;
194b7fa0554SAndreas Gruenbacher 	}
1956e94d629STrond Myklebust 	nfs_free_fattr(fattr);
196ae46141fSTrond Myklebust out_freepages:
197ae46141fSTrond Myklebust 	while (args.npages != 0) {
198ae46141fSTrond Myklebust 		args.npages--;
199ae46141fSTrond Myklebust 		__free_page(args.pages[args.npages]);
200ae46141fSTrond Myklebust 	}
201b7fa0554SAndreas Gruenbacher out:
202b7fa0554SAndreas Gruenbacher 	return status;
203b7fa0554SAndreas Gruenbacher }
204b7fa0554SAndreas Gruenbacher 
2058f493b9cSTrond Myklebust int nfs3_proc_setacls(struct inode *inode, struct posix_acl *acl,
2068f493b9cSTrond Myklebust 		struct posix_acl *dfacl)
2078f493b9cSTrond Myklebust {
2088f493b9cSTrond Myklebust 	int ret;
2098f493b9cSTrond Myklebust 	ret = __nfs3_proc_setacls(inode, acl, dfacl);
2108f493b9cSTrond Myklebust 	return (ret == -EOPNOTSUPP) ? 0 : ret;
2118f493b9cSTrond Myklebust 
2128f493b9cSTrond Myklebust }
2138f493b9cSTrond Myklebust 
214013cdf10SChristoph Hellwig int nfs3_set_acl(struct inode *inode, struct posix_acl *acl, int type)
215b7fa0554SAndreas Gruenbacher {
216b7fa0554SAndreas Gruenbacher 	struct posix_acl *alloc = NULL, *dfacl = NULL;
217b7fa0554SAndreas Gruenbacher 	int status;
218b7fa0554SAndreas Gruenbacher 
219b7fa0554SAndreas Gruenbacher 	if (S_ISDIR(inode->i_mode)) {
220b7fa0554SAndreas Gruenbacher 		switch(type) {
221b7fa0554SAndreas Gruenbacher 		case ACL_TYPE_ACCESS:
222013cdf10SChristoph Hellwig 			alloc = dfacl = get_acl(inode, ACL_TYPE_DEFAULT);
223b7fa0554SAndreas Gruenbacher 			if (IS_ERR(alloc))
224b7fa0554SAndreas Gruenbacher 				goto fail;
225b7fa0554SAndreas Gruenbacher 			break;
226b7fa0554SAndreas Gruenbacher 
227b7fa0554SAndreas Gruenbacher 		case ACL_TYPE_DEFAULT:
228b7fa0554SAndreas Gruenbacher 			dfacl = acl;
229013cdf10SChristoph Hellwig 			alloc = acl = get_acl(inode, ACL_TYPE_ACCESS);
230b7fa0554SAndreas Gruenbacher 			if (IS_ERR(alloc))
231b7fa0554SAndreas Gruenbacher 				goto fail;
232b7fa0554SAndreas Gruenbacher 			break;
233b7fa0554SAndreas Gruenbacher 		}
234013cdf10SChristoph Hellwig 	}
235b7fa0554SAndreas Gruenbacher 
236b7fa0554SAndreas Gruenbacher 	if (acl == NULL) {
237b7fa0554SAndreas Gruenbacher 		alloc = acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
238b7fa0554SAndreas Gruenbacher 		if (IS_ERR(alloc))
239b7fa0554SAndreas Gruenbacher 			goto fail;
240b7fa0554SAndreas Gruenbacher 	}
2418f493b9cSTrond Myklebust 	status = __nfs3_proc_setacls(inode, acl, dfacl);
242b7fa0554SAndreas Gruenbacher 	posix_acl_release(alloc);
243b7fa0554SAndreas Gruenbacher 	return status;
244b7fa0554SAndreas Gruenbacher 
245b7fa0554SAndreas Gruenbacher fail:
246b7fa0554SAndreas Gruenbacher 	return PTR_ERR(alloc);
247b7fa0554SAndreas Gruenbacher }
248055ffbeaSAndreas Gruenbacher 
249013cdf10SChristoph Hellwig const struct xattr_handler *nfs3_xattr_handlers[] = {
250013cdf10SChristoph Hellwig 	&posix_acl_access_xattr_handler,
251013cdf10SChristoph Hellwig 	&posix_acl_default_xattr_handler,
252013cdf10SChristoph Hellwig 	NULL,
253013cdf10SChristoph Hellwig };
25474adf83fSChristoph Hellwig 
25574adf83fSChristoph Hellwig static int
25674adf83fSChristoph Hellwig nfs3_list_one_acl(struct inode *inode, int type, const char *name, void *data,
25774adf83fSChristoph Hellwig 		size_t size, ssize_t *result)
25874adf83fSChristoph Hellwig {
25974adf83fSChristoph Hellwig 	struct posix_acl *acl;
26074adf83fSChristoph Hellwig 	char *p = data + *result;
26174adf83fSChristoph Hellwig 
26274adf83fSChristoph Hellwig 	acl = get_acl(inode, type);
2637a9e75a1SAndrey Utkin 	if (IS_ERR_OR_NULL(acl))
26474adf83fSChristoph Hellwig 		return 0;
26574adf83fSChristoph Hellwig 
26674adf83fSChristoph Hellwig 	posix_acl_release(acl);
26774adf83fSChristoph Hellwig 
26874adf83fSChristoph Hellwig 	*result += strlen(name);
26974adf83fSChristoph Hellwig 	*result += 1;
27074adf83fSChristoph Hellwig 	if (!size)
27174adf83fSChristoph Hellwig 		return 0;
27274adf83fSChristoph Hellwig 	if (*result > size)
27374adf83fSChristoph Hellwig 		return -ERANGE;
27474adf83fSChristoph Hellwig 
27574adf83fSChristoph Hellwig 	strcpy(p, name);
27674adf83fSChristoph Hellwig 	return 0;
27774adf83fSChristoph Hellwig }
27874adf83fSChristoph Hellwig 
27974adf83fSChristoph Hellwig ssize_t
28074adf83fSChristoph Hellwig nfs3_listxattr(struct dentry *dentry, char *data, size_t size)
28174adf83fSChristoph Hellwig {
28274adf83fSChristoph Hellwig 	struct inode *inode = dentry->d_inode;
28374adf83fSChristoph Hellwig 	ssize_t result = 0;
28474adf83fSChristoph Hellwig 	int error;
28574adf83fSChristoph Hellwig 
28674adf83fSChristoph Hellwig 	error = nfs3_list_one_acl(inode, ACL_TYPE_ACCESS,
28774adf83fSChristoph Hellwig 			POSIX_ACL_XATTR_ACCESS, data, size, &result);
28874adf83fSChristoph Hellwig 	if (error)
28974adf83fSChristoph Hellwig 		return error;
29074adf83fSChristoph Hellwig 
29174adf83fSChristoph Hellwig 	error = nfs3_list_one_acl(inode, ACL_TYPE_DEFAULT,
29274adf83fSChristoph Hellwig 			POSIX_ACL_XATTR_DEFAULT, data, size, &result);
29374adf83fSChristoph Hellwig 	if (error)
29474adf83fSChristoph Hellwig 		return error;
29574adf83fSChristoph Hellwig 	return result;
29674adf83fSChristoph Hellwig }
297