[PATCH] mutex subsystem, semaphore to mutex: VFS, ->i_sem
[pandora-kernel.git] / fs / ext3 / acl.c
1 /*
2  * linux/fs/ext3/acl.c
3  *
4  * Copyright (C) 2001-2003 Andreas Gruenbacher, <agruen@suse.de>
5  */
6
7 #include <linux/init.h>
8 #include <linux/sched.h>
9 #include <linux/slab.h>
10 #include <linux/fs.h>
11 #include <linux/ext3_jbd.h>
12 #include <linux/ext3_fs.h>
13 #include "xattr.h"
14 #include "acl.h"
15
16 /*
17  * Convert from filesystem to in-memory representation.
18  */
19 static struct posix_acl *
20 ext3_acl_from_disk(const void *value, size_t size)
21 {
22         const char *end = (char *)value + size;
23         int n, count;
24         struct posix_acl *acl;
25
26         if (!value)
27                 return NULL;
28         if (size < sizeof(ext3_acl_header))
29                  return ERR_PTR(-EINVAL);
30         if (((ext3_acl_header *)value)->a_version !=
31             cpu_to_le32(EXT3_ACL_VERSION))
32                 return ERR_PTR(-EINVAL);
33         value = (char *)value + sizeof(ext3_acl_header);
34         count = ext3_acl_count(size);
35         if (count < 0)
36                 return ERR_PTR(-EINVAL);
37         if (count == 0)
38                 return NULL;
39         acl = posix_acl_alloc(count, GFP_KERNEL);
40         if (!acl)
41                 return ERR_PTR(-ENOMEM);
42         for (n=0; n < count; n++) {
43                 ext3_acl_entry *entry =
44                         (ext3_acl_entry *)value;
45                 if ((char *)value + sizeof(ext3_acl_entry_short) > end)
46                         goto fail;
47                 acl->a_entries[n].e_tag  = le16_to_cpu(entry->e_tag);
48                 acl->a_entries[n].e_perm = le16_to_cpu(entry->e_perm);
49                 switch(acl->a_entries[n].e_tag) {
50                         case ACL_USER_OBJ:
51                         case ACL_GROUP_OBJ:
52                         case ACL_MASK:
53                         case ACL_OTHER:
54                                 value = (char *)value +
55                                         sizeof(ext3_acl_entry_short);
56                                 acl->a_entries[n].e_id = ACL_UNDEFINED_ID;
57                                 break;
58
59                         case ACL_USER:
60                         case ACL_GROUP:
61                                 value = (char *)value + sizeof(ext3_acl_entry);
62                                 if ((char *)value > end)
63                                         goto fail;
64                                 acl->a_entries[n].e_id =
65                                         le32_to_cpu(entry->e_id);
66                                 break;
67
68                         default:
69                                 goto fail;
70                 }
71         }
72         if (value != end)
73                 goto fail;
74         return acl;
75
76 fail:
77         posix_acl_release(acl);
78         return ERR_PTR(-EINVAL);
79 }
80
81 /*
82  * Convert from in-memory to filesystem representation.
83  */
84 static void *
85 ext3_acl_to_disk(const struct posix_acl *acl, size_t *size)
86 {
87         ext3_acl_header *ext_acl;
88         char *e;
89         size_t n;
90
91         *size = ext3_acl_size(acl->a_count);
92         ext_acl = (ext3_acl_header *)kmalloc(sizeof(ext3_acl_header) +
93                 acl->a_count * sizeof(ext3_acl_entry), GFP_KERNEL);
94         if (!ext_acl)
95                 return ERR_PTR(-ENOMEM);
96         ext_acl->a_version = cpu_to_le32(EXT3_ACL_VERSION);
97         e = (char *)ext_acl + sizeof(ext3_acl_header);
98         for (n=0; n < acl->a_count; n++) {
99                 ext3_acl_entry *entry = (ext3_acl_entry *)e;
100                 entry->e_tag  = cpu_to_le16(acl->a_entries[n].e_tag);
101                 entry->e_perm = cpu_to_le16(acl->a_entries[n].e_perm);
102                 switch(acl->a_entries[n].e_tag) {
103                         case ACL_USER:
104                         case ACL_GROUP:
105                                 entry->e_id =
106                                         cpu_to_le32(acl->a_entries[n].e_id);
107                                 e += sizeof(ext3_acl_entry);
108                                 break;
109
110                         case ACL_USER_OBJ:
111                         case ACL_GROUP_OBJ:
112                         case ACL_MASK:
113                         case ACL_OTHER:
114                                 e += sizeof(ext3_acl_entry_short);
115                                 break;
116
117                         default:
118                                 goto fail;
119                 }
120         }
121         return (char *)ext_acl;
122
123 fail:
124         kfree(ext_acl);
125         return ERR_PTR(-EINVAL);
126 }
127
128 static inline struct posix_acl *
129 ext3_iget_acl(struct inode *inode, struct posix_acl **i_acl)
130 {
131         struct posix_acl *acl = EXT3_ACL_NOT_CACHED;
132
133         spin_lock(&inode->i_lock);
134         if (*i_acl != EXT3_ACL_NOT_CACHED)
135                 acl = posix_acl_dup(*i_acl);
136         spin_unlock(&inode->i_lock);
137
138         return acl;
139 }
140
141 static inline void
142 ext3_iset_acl(struct inode *inode, struct posix_acl **i_acl,
143                   struct posix_acl *acl)
144 {
145         spin_lock(&inode->i_lock);
146         if (*i_acl != EXT3_ACL_NOT_CACHED)
147                 posix_acl_release(*i_acl);
148         *i_acl = posix_acl_dup(acl);
149         spin_unlock(&inode->i_lock);
150 }
151
152 /*
153  * Inode operation get_posix_acl().
154  *
155  * inode->i_mutex: don't care
156  */
157 static struct posix_acl *
158 ext3_get_acl(struct inode *inode, int type)
159 {
160         struct ext3_inode_info *ei = EXT3_I(inode);
161         int name_index;
162         char *value = NULL;
163         struct posix_acl *acl;
164         int retval;
165
166         if (!test_opt(inode->i_sb, POSIX_ACL))
167                 return NULL;
168
169         switch(type) {
170                 case ACL_TYPE_ACCESS:
171                         acl = ext3_iget_acl(inode, &ei->i_acl);
172                         if (acl != EXT3_ACL_NOT_CACHED)
173                                 return acl;
174                         name_index = EXT3_XATTR_INDEX_POSIX_ACL_ACCESS;
175                         break;
176
177                 case ACL_TYPE_DEFAULT:
178                         acl = ext3_iget_acl(inode, &ei->i_default_acl);
179                         if (acl != EXT3_ACL_NOT_CACHED)
180                                 return acl;
181                         name_index = EXT3_XATTR_INDEX_POSIX_ACL_DEFAULT;
182                         break;
183
184                 default:
185                         return ERR_PTR(-EINVAL);
186         }
187         retval = ext3_xattr_get(inode, name_index, "", NULL, 0);
188         if (retval > 0) {
189                 value = kmalloc(retval, GFP_KERNEL);
190                 if (!value)
191                         return ERR_PTR(-ENOMEM);
192                 retval = ext3_xattr_get(inode, name_index, "", value, retval);
193         }
194         if (retval > 0)
195                 acl = ext3_acl_from_disk(value, retval);
196         else if (retval == -ENODATA || retval == -ENOSYS)
197                 acl = NULL;
198         else
199                 acl = ERR_PTR(retval);
200         kfree(value);
201
202         if (!IS_ERR(acl)) {
203                 switch(type) {
204                         case ACL_TYPE_ACCESS:
205                                 ext3_iset_acl(inode, &ei->i_acl, acl);
206                                 break;
207
208                         case ACL_TYPE_DEFAULT:
209                                 ext3_iset_acl(inode, &ei->i_default_acl, acl);
210                                 break;
211                 }
212         }
213         return acl;
214 }
215
216 /*
217  * Set the access or default ACL of an inode.
218  *
219  * inode->i_mutex: down unless called from ext3_new_inode
220  */
221 static int
222 ext3_set_acl(handle_t *handle, struct inode *inode, int type,
223              struct posix_acl *acl)
224 {
225         struct ext3_inode_info *ei = EXT3_I(inode);
226         int name_index;
227         void *value = NULL;
228         size_t size;
229         int error;
230
231         if (S_ISLNK(inode->i_mode))
232                 return -EOPNOTSUPP;
233
234         switch(type) {
235                 case ACL_TYPE_ACCESS:
236                         name_index = EXT3_XATTR_INDEX_POSIX_ACL_ACCESS;
237                         if (acl) {
238                                 mode_t mode = inode->i_mode;
239                                 error = posix_acl_equiv_mode(acl, &mode);
240                                 if (error < 0)
241                                         return error;
242                                 else {
243                                         inode->i_mode = mode;
244                                         ext3_mark_inode_dirty(handle, inode);
245                                         if (error == 0)
246                                                 acl = NULL;
247                                 }
248                         }
249                         break;
250
251                 case ACL_TYPE_DEFAULT:
252                         name_index = EXT3_XATTR_INDEX_POSIX_ACL_DEFAULT;
253                         if (!S_ISDIR(inode->i_mode))
254                                 return acl ? -EACCES : 0;
255                         break;
256
257                 default:
258                         return -EINVAL;
259         }
260         if (acl) {
261                 value = ext3_acl_to_disk(acl, &size);
262                 if (IS_ERR(value))
263                         return (int)PTR_ERR(value);
264         }
265
266         error = ext3_xattr_set_handle(handle, inode, name_index, "",
267                                       value, size, 0);
268
269         kfree(value);
270         if (!error) {
271                 switch(type) {
272                         case ACL_TYPE_ACCESS:
273                                 ext3_iset_acl(inode, &ei->i_acl, acl);
274                                 break;
275
276                         case ACL_TYPE_DEFAULT:
277                                 ext3_iset_acl(inode, &ei->i_default_acl, acl);
278                                 break;
279                 }
280         }
281         return error;
282 }
283
284 static int
285 ext3_check_acl(struct inode *inode, int mask)
286 {
287         struct posix_acl *acl = ext3_get_acl(inode, ACL_TYPE_ACCESS);
288
289         if (IS_ERR(acl))
290                 return PTR_ERR(acl);
291         if (acl) {
292                 int error = posix_acl_permission(inode, acl, mask);
293                 posix_acl_release(acl);
294                 return error;
295         }
296
297         return -EAGAIN;
298 }
299
300 int
301 ext3_permission(struct inode *inode, int mask, struct nameidata *nd)
302 {
303         return generic_permission(inode, mask, ext3_check_acl);
304 }
305
306 /*
307  * Initialize the ACLs of a new inode. Called from ext3_new_inode.
308  *
309  * dir->i_mutex: down
310  * inode->i_mutex: up (access to inode is still exclusive)
311  */
312 int
313 ext3_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
314 {
315         struct posix_acl *acl = NULL;
316         int error = 0;
317
318         if (!S_ISLNK(inode->i_mode)) {
319                 if (test_opt(dir->i_sb, POSIX_ACL)) {
320                         acl = ext3_get_acl(dir, ACL_TYPE_DEFAULT);
321                         if (IS_ERR(acl))
322                                 return PTR_ERR(acl);
323                 }
324                 if (!acl)
325                         inode->i_mode &= ~current->fs->umask;
326         }
327         if (test_opt(inode->i_sb, POSIX_ACL) && acl) {
328                 struct posix_acl *clone;
329                 mode_t mode;
330
331                 if (S_ISDIR(inode->i_mode)) {
332                         error = ext3_set_acl(handle, inode,
333                                              ACL_TYPE_DEFAULT, acl);
334                         if (error)
335                                 goto cleanup;
336                 }
337                 clone = posix_acl_clone(acl, GFP_KERNEL);
338                 error = -ENOMEM;
339                 if (!clone)
340                         goto cleanup;
341
342                 mode = inode->i_mode;
343                 error = posix_acl_create_masq(clone, &mode);
344                 if (error >= 0) {
345                         inode->i_mode = mode;
346                         if (error > 0) {
347                                 /* This is an extended ACL */
348                                 error = ext3_set_acl(handle, inode,
349                                                      ACL_TYPE_ACCESS, clone);
350                         }
351                 }
352                 posix_acl_release(clone);
353         }
354 cleanup:
355         posix_acl_release(acl);
356         return error;
357 }
358
359 /*
360  * Does chmod for an inode that may have an Access Control List. The
361  * inode->i_mode field must be updated to the desired value by the caller
362  * before calling this function.
363  * Returns 0 on success, or a negative error number.
364  *
365  * We change the ACL rather than storing some ACL entries in the file
366  * mode permission bits (which would be more efficient), because that
367  * would break once additional permissions (like  ACL_APPEND, ACL_DELETE
368  * for directories) are added. There are no more bits available in the
369  * file mode.
370  *
371  * inode->i_mutex: down
372  */
373 int
374 ext3_acl_chmod(struct inode *inode)
375 {
376         struct posix_acl *acl, *clone;
377         int error;
378
379         if (S_ISLNK(inode->i_mode))
380                 return -EOPNOTSUPP;
381         if (!test_opt(inode->i_sb, POSIX_ACL))
382                 return 0;
383         acl = ext3_get_acl(inode, ACL_TYPE_ACCESS);
384         if (IS_ERR(acl) || !acl)
385                 return PTR_ERR(acl);
386         clone = posix_acl_clone(acl, GFP_KERNEL);
387         posix_acl_release(acl);
388         if (!clone)
389                 return -ENOMEM;
390         error = posix_acl_chmod_masq(clone, inode->i_mode);
391         if (!error) {
392                 handle_t *handle;
393                 int retries = 0;
394
395         retry:
396                 handle = ext3_journal_start(inode,
397                                 EXT3_DATA_TRANS_BLOCKS(inode->i_sb));
398                 if (IS_ERR(handle)) {
399                         error = PTR_ERR(handle);
400                         ext3_std_error(inode->i_sb, error);
401                         goto out;
402                 }
403                 error = ext3_set_acl(handle, inode, ACL_TYPE_ACCESS, clone);
404                 ext3_journal_stop(handle);
405                 if (error == -ENOSPC &&
406                     ext3_should_retry_alloc(inode->i_sb, &retries))
407                         goto retry;
408         }
409 out:
410         posix_acl_release(clone);
411         return error;
412 }
413
414 /*
415  * Extended attribute handlers
416  */
417 static size_t
418 ext3_xattr_list_acl_access(struct inode *inode, char *list, size_t list_len,
419                            const char *name, size_t name_len)
420 {
421         const size_t size = sizeof(POSIX_ACL_XATTR_ACCESS);
422
423         if (!test_opt(inode->i_sb, POSIX_ACL))
424                 return 0;
425         if (list && size <= list_len)
426                 memcpy(list, POSIX_ACL_XATTR_ACCESS, size);
427         return size;
428 }
429
430 static size_t
431 ext3_xattr_list_acl_default(struct inode *inode, char *list, size_t list_len,
432                             const char *name, size_t name_len)
433 {
434         const size_t size = sizeof(POSIX_ACL_XATTR_DEFAULT);
435
436         if (!test_opt(inode->i_sb, POSIX_ACL))
437                 return 0;
438         if (list && size <= list_len)
439                 memcpy(list, POSIX_ACL_XATTR_DEFAULT, size);
440         return size;
441 }
442
443 static int
444 ext3_xattr_get_acl(struct inode *inode, int type, void *buffer, size_t size)
445 {
446         struct posix_acl *acl;
447         int error;
448
449         if (!test_opt(inode->i_sb, POSIX_ACL))
450                 return -EOPNOTSUPP;
451
452         acl = ext3_get_acl(inode, type);
453         if (IS_ERR(acl))
454                 return PTR_ERR(acl);
455         if (acl == NULL)
456                 return -ENODATA;
457         error = posix_acl_to_xattr(acl, buffer, size);
458         posix_acl_release(acl);
459
460         return error;
461 }
462
463 static int
464 ext3_xattr_get_acl_access(struct inode *inode, const char *name,
465                           void *buffer, size_t size)
466 {
467         if (strcmp(name, "") != 0)
468                 return -EINVAL;
469         return ext3_xattr_get_acl(inode, ACL_TYPE_ACCESS, buffer, size);
470 }
471
472 static int
473 ext3_xattr_get_acl_default(struct inode *inode, const char *name,
474                            void *buffer, size_t size)
475 {
476         if (strcmp(name, "") != 0)
477                 return -EINVAL;
478         return ext3_xattr_get_acl(inode, ACL_TYPE_DEFAULT, buffer, size);
479 }
480
481 static int
482 ext3_xattr_set_acl(struct inode *inode, int type, const void *value,
483                    size_t size)
484 {
485         handle_t *handle;
486         struct posix_acl *acl;
487         int error, retries = 0;
488
489         if (!test_opt(inode->i_sb, POSIX_ACL))
490                 return -EOPNOTSUPP;
491         if ((current->fsuid != inode->i_uid) && !capable(CAP_FOWNER))
492                 return -EPERM;
493
494         if (value) {
495                 acl = posix_acl_from_xattr(value, size);
496                 if (IS_ERR(acl))
497                         return PTR_ERR(acl);
498                 else if (acl) {
499                         error = posix_acl_valid(acl);
500                         if (error)
501                                 goto release_and_out;
502                 }
503         } else
504                 acl = NULL;
505
506 retry:
507         handle = ext3_journal_start(inode, EXT3_DATA_TRANS_BLOCKS(inode->i_sb));
508         if (IS_ERR(handle))
509                 return PTR_ERR(handle);
510         error = ext3_set_acl(handle, inode, type, acl);
511         ext3_journal_stop(handle);
512         if (error == -ENOSPC && ext3_should_retry_alloc(inode->i_sb, &retries))
513                 goto retry;
514
515 release_and_out:
516         posix_acl_release(acl);
517         return error;
518 }
519
520 static int
521 ext3_xattr_set_acl_access(struct inode *inode, const char *name,
522                           const void *value, size_t size, int flags)
523 {
524         if (strcmp(name, "") != 0)
525                 return -EINVAL;
526         return ext3_xattr_set_acl(inode, ACL_TYPE_ACCESS, value, size);
527 }
528
529 static int
530 ext3_xattr_set_acl_default(struct inode *inode, const char *name,
531                            const void *value, size_t size, int flags)
532 {
533         if (strcmp(name, "") != 0)
534                 return -EINVAL;
535         return ext3_xattr_set_acl(inode, ACL_TYPE_DEFAULT, value, size);
536 }
537
538 struct xattr_handler ext3_xattr_acl_access_handler = {
539         .prefix = POSIX_ACL_XATTR_ACCESS,
540         .list   = ext3_xattr_list_acl_access,
541         .get    = ext3_xattr_get_acl_access,
542         .set    = ext3_xattr_set_acl_access,
543 };
544
545 struct xattr_handler ext3_xattr_acl_default_handler = {
546         .prefix = POSIX_ACL_XATTR_DEFAULT,
547         .list   = ext3_xattr_list_acl_default,
548         .get    = ext3_xattr_get_acl_default,
549         .set    = ext3_xattr_set_acl_default,
550 };