diff options
Diffstat (limited to 'fs/nfs_common/nfsacl.c')
-rw-r--r-- | fs/nfs_common/nfsacl.c | 257 |
1 files changed, 257 insertions, 0 deletions
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 @@ | |||
1 | /* | ||
2 | * fs/nfs_common/nfsacl.c | ||
3 | * | ||
4 | * Copyright (C) 2002-2003 Andreas Gruenbacher <agruen@suse.de> | ||
5 | */ | ||
6 | |||
7 | /* | ||
8 | * The Solaris nfsacl protocol represents some ACLs slightly differently | ||
9 | * than POSIX 1003.1e draft 17 does (and we do): | ||
10 | * | ||
11 | * - Minimal ACLs always have an ACL_MASK entry, so they have | ||
12 | * four instead of three entries. | ||
13 | * - The ACL_MASK entry in such minimal ACLs always has the same | ||
14 | * permissions as the ACL_GROUP_OBJ entry. (In extended ACLs | ||
15 | * the ACL_MASK and ACL_GROUP_OBJ entries may differ.) | ||
16 | * - The identifier fields of the ACL_USER_OBJ and ACL_GROUP_OBJ | ||
17 | * entries contain the identifiers of the owner and owning group. | ||
18 | * (In POSIX ACLs we always set them to ACL_UNDEFINED_ID). | ||
19 | * - ACL entries in the kernel are kept sorted in ascending order | ||
20 | * of (e_tag, e_id). Solaris ACLs are unsorted. | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/fs.h> | ||
25 | #include <linux/sunrpc/xdr.h> | ||
26 | #include <linux/nfsacl.h> | ||
27 | #include <linux/nfs3.h> | ||
28 | #include <linux/sort.h> | ||
29 | |||
30 | MODULE_LICENSE("GPL"); | ||
31 | |||
32 | EXPORT_SYMBOL(nfsacl_encode); | ||
33 | EXPORT_SYMBOL(nfsacl_decode); | ||
34 | |||
35 | struct nfsacl_encode_desc { | ||
36 | struct xdr_array2_desc desc; | ||
37 | unsigned int count; | ||
38 | struct posix_acl *acl; | ||
39 | int typeflag; | ||
40 | uid_t uid; | ||
41 | gid_t gid; | ||
42 | }; | ||
43 | |||
44 | static int | ||
45 | xdr_nfsace_encode(struct xdr_array2_desc *desc, void *elem) | ||
46 | { | ||
47 | struct nfsacl_encode_desc *nfsacl_desc = | ||
48 | (struct nfsacl_encode_desc *) desc; | ||
49 | u32 *p = (u32 *) elem; | ||
50 | |||
51 | if (nfsacl_desc->count < nfsacl_desc->acl->a_count) { | ||
52 | struct posix_acl_entry *entry = | ||
53 | &nfsacl_desc->acl->a_entries[nfsacl_desc->count++]; | ||
54 | |||
55 | *p++ = htonl(entry->e_tag | nfsacl_desc->typeflag); | ||
56 | switch(entry->e_tag) { | ||
57 | case ACL_USER_OBJ: | ||
58 | *p++ = htonl(nfsacl_desc->uid); | ||
59 | break; | ||
60 | case ACL_GROUP_OBJ: | ||
61 | *p++ = htonl(nfsacl_desc->gid); | ||
62 | break; | ||
63 | case ACL_USER: | ||
64 | case ACL_GROUP: | ||
65 | *p++ = htonl(entry->e_id); | ||
66 | break; | ||
67 | default: /* Solaris depends on that! */ | ||
68 | *p++ = 0; | ||
69 | break; | ||
70 | } | ||
71 | *p++ = htonl(entry->e_perm & S_IRWXO); | ||
72 | } else { | ||
73 | const struct posix_acl_entry *pa, *pe; | ||
74 | int group_obj_perm = ACL_READ|ACL_WRITE|ACL_EXECUTE; | ||
75 | |||
76 | FOREACH_ACL_ENTRY(pa, nfsacl_desc->acl, pe) { | ||
77 | if (pa->e_tag == ACL_GROUP_OBJ) { | ||
78 | group_obj_perm = pa->e_perm & S_IRWXO; | ||
79 | break; | ||
80 | } | ||
81 | } | ||
82 | /* fake up ACL_MASK entry */ | ||
83 | *p++ = htonl(ACL_MASK | nfsacl_desc->typeflag); | ||
84 | *p++ = htonl(0); | ||
85 | *p++ = htonl(group_obj_perm); | ||
86 | } | ||
87 | |||
88 | return 0; | ||
89 | } | ||
90 | |||
91 | unsigned int | ||
92 | nfsacl_encode(struct xdr_buf *buf, unsigned int base, struct inode *inode, | ||
93 | struct posix_acl *acl, int encode_entries, int typeflag) | ||
94 | { | ||
95 | int entries = (acl && acl->a_count) ? max_t(int, acl->a_count, 4) : 0; | ||
96 | struct nfsacl_encode_desc nfsacl_desc = { | ||
97 | .desc = { | ||
98 | .elem_size = 12, | ||
99 | .array_len = encode_entries ? entries : 0, | ||
100 | .xcode = xdr_nfsace_encode, | ||
101 | }, | ||
102 | .acl = acl, | ||
103 | .typeflag = typeflag, | ||
104 | .uid = inode->i_uid, | ||
105 | .gid = inode->i_gid, | ||
106 | }; | ||
107 | int err; | ||
108 | |||
109 | if (entries > NFS_ACL_MAX_ENTRIES || | ||
110 | xdr_encode_word(buf, base, entries)) | ||
111 | return -EINVAL; | ||
112 | err = xdr_encode_array2(buf, base + 4, &nfsacl_desc.desc); | ||
113 | if (!err) | ||
114 | err = 8 + nfsacl_desc.desc.elem_size * | ||
115 | nfsacl_desc.desc.array_len; | ||
116 | return err; | ||
117 | } | ||
118 | |||
119 | struct nfsacl_decode_desc { | ||
120 | struct xdr_array2_desc desc; | ||
121 | unsigned int count; | ||
122 | struct posix_acl *acl; | ||
123 | }; | ||
124 | |||
125 | static int | ||
126 | xdr_nfsace_decode(struct xdr_array2_desc *desc, void *elem) | ||
127 | { | ||
128 | struct nfsacl_decode_desc *nfsacl_desc = | ||
129 | (struct nfsacl_decode_desc *) desc; | ||
130 | u32 *p = (u32 *) elem; | ||
131 | struct posix_acl_entry *entry; | ||
132 | |||
133 | if (!nfsacl_desc->acl) { | ||
134 | if (desc->array_len > NFS_ACL_MAX_ENTRIES) | ||
135 | return -EINVAL; | ||
136 | nfsacl_desc->acl = posix_acl_alloc(desc->array_len, GFP_KERNEL); | ||
137 | if (!nfsacl_desc->acl) | ||
138 | return -ENOMEM; | ||
139 | nfsacl_desc->count = 0; | ||
140 | } | ||
141 | |||
142 | entry = &nfsacl_desc->acl->a_entries[nfsacl_desc->count++]; | ||
143 | entry->e_tag = ntohl(*p++) & ~NFS_ACL_DEFAULT; | ||
144 | entry->e_id = ntohl(*p++); | ||
145 | entry->e_perm = ntohl(*p++); | ||
146 | |||
147 | switch(entry->e_tag) { | ||
148 | case ACL_USER_OBJ: | ||
149 | case ACL_USER: | ||
150 | case ACL_GROUP_OBJ: | ||
151 | case ACL_GROUP: | ||
152 | case ACL_OTHER: | ||
153 | if (entry->e_perm & ~S_IRWXO) | ||
154 | return -EINVAL; | ||
155 | break; | ||
156 | case ACL_MASK: | ||
157 | /* Solaris sometimes sets additonal bits in the mask */ | ||
158 | entry->e_perm &= S_IRWXO; | ||
159 | break; | ||
160 | default: | ||
161 | return -EINVAL; | ||
162 | } | ||
163 | |||
164 | return 0; | ||
165 | } | ||
166 | |||
167 | static int | ||
168 | cmp_acl_entry(const void *x, const void *y) | ||
169 | { | ||
170 | const struct posix_acl_entry *a = x, *b = y; | ||
171 | |||
172 | if (a->e_tag != b->e_tag) | ||
173 | return a->e_tag - b->e_tag; | ||
174 | else if (a->e_id > b->e_id) | ||
175 | return 1; | ||
176 | else if (a->e_id < b->e_id) | ||
177 | return -1; | ||
178 | else | ||
179 | return 0; | ||
180 | } | ||
181 | |||
182 | /* | ||
183 | * Convert from a Solaris ACL to a POSIX 1003.1e draft 17 ACL. | ||
184 | */ | ||
185 | static int | ||
186 | posix_acl_from_nfsacl(struct posix_acl *acl) | ||
187 | { | ||
188 | struct posix_acl_entry *pa, *pe, | ||
189 | *group_obj = NULL, *mask = NULL; | ||
190 | |||
191 | if (!acl) | ||
192 | return 0; | ||
193 | |||
194 | sort(acl->a_entries, acl->a_count, sizeof(struct posix_acl_entry), | ||
195 | cmp_acl_entry, NULL); | ||
196 | |||
197 | /* Clear undefined identifier fields and find the ACL_GROUP_OBJ | ||
198 | and ACL_MASK entries. */ | ||
199 | FOREACH_ACL_ENTRY(pa, acl, pe) { | ||
200 | switch(pa->e_tag) { | ||
201 | case ACL_USER_OBJ: | ||
202 | pa->e_id = ACL_UNDEFINED_ID; | ||
203 | break; | ||
204 | case ACL_GROUP_OBJ: | ||
205 | pa->e_id = ACL_UNDEFINED_ID; | ||
206 | group_obj = pa; | ||
207 | break; | ||
208 | case ACL_MASK: | ||
209 | mask = pa; | ||
210 | /* fall through */ | ||
211 | case ACL_OTHER: | ||
212 | pa->e_id = ACL_UNDEFINED_ID; | ||
213 | break; | ||
214 | } | ||
215 | } | ||
216 | if (acl->a_count == 4 && group_obj && mask && | ||
217 | mask->e_perm == group_obj->e_perm) { | ||
218 | /* remove bogus ACL_MASK entry */ | ||
219 | memmove(mask, mask+1, (3 - (mask - acl->a_entries)) * | ||
220 | sizeof(struct posix_acl_entry)); | ||
221 | acl->a_count = 3; | ||
222 | } | ||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | unsigned int | ||
227 | nfsacl_decode(struct xdr_buf *buf, unsigned int base, unsigned int *aclcnt, | ||
228 | struct posix_acl **pacl) | ||
229 | { | ||
230 | struct nfsacl_decode_desc nfsacl_desc = { | ||
231 | .desc = { | ||
232 | .elem_size = 12, | ||
233 | .xcode = pacl ? xdr_nfsace_decode : NULL, | ||
234 | }, | ||
235 | }; | ||
236 | u32 entries; | ||
237 | int err; | ||
238 | |||
239 | if (xdr_decode_word(buf, base, &entries) || | ||
240 | entries > NFS_ACL_MAX_ENTRIES) | ||
241 | return -EINVAL; | ||
242 | err = xdr_decode_array2(buf, base + 4, &nfsacl_desc.desc); | ||
243 | if (err) | ||
244 | return err; | ||
245 | if (pacl) { | ||
246 | if (entries != nfsacl_desc.desc.array_len || | ||
247 | posix_acl_from_nfsacl(nfsacl_desc.acl) != 0) { | ||
248 | posix_acl_release(nfsacl_desc.acl); | ||
249 | return -EINVAL; | ||
250 | } | ||
251 | *pacl = nfsacl_desc.acl; | ||
252 | } | ||
253 | if (aclcnt) | ||
254 | *aclcnt = entries; | ||
255 | return 8 + nfsacl_desc.desc.elem_size * | ||
256 | nfsacl_desc.desc.array_len; | ||
257 | } | ||