New function acl_extended_nontrivial (MacOS X only).
[gnulib.git] / lib / file-has-acl.c
1 /* Test whether a file has a nontrivial access control list.
2
3    Copyright (C) 2002-2003, 2005-2008 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18    Written by Paul Eggert and Andreas Gruenbacher.  */
19
20 #include <config.h>
21
22 #include "acl.h"
23
24 #include "acl-internal.h"
25
26
27 #if USE_ACL && HAVE_ACL_GET_FILE
28
29 # if HAVE_ACL_TYPE_EXTENDED /* MacOS X */
30
31 /* ACL is an ACL, from a file, stored as type ACL_TYPE_EXTENDED.
32    Return 1 if the given ACL is non-trivial.
33    Return 0 if it is trivial.  */
34 int
35 acl_extended_nontrivial (acl_t acl)
36 {
37   /* acl is non-trivial if it is non-empty.  */
38   return (acl_entries (acl) > 0);
39 }
40
41 # else /* Linux, FreeBSD, IRIX, Tru64 */
42
43 /* ACL is an ACL, from a file, stored as type ACL_TYPE_ACCESS.
44    Return 1 if the given ACL is non-trivial.
45    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.
46    Return -1 and set errno upon failure to determine it.  */
47 int
48 acl_access_nontrivial (acl_t acl)
49 {
50   /* acl is non-trivial if it has some entries other than for "user::",
51      "group::", and "other::".  Normally these three should be present
52      at least, allowing us to write
53         return (3 < acl_entries (acl));
54      but the following code is more robust.  */
55 #  if HAVE_ACL_FIRST_ENTRY /* Linux, FreeBSD */
56
57   acl_entry_t ace;
58   int at_end;
59
60   for (at_end = acl_get_entry (acl, ACL_FIRST_ENTRY, &ace);
61        !at_end;
62        at_end = acl_get_entry (acl, ACL_NEXT_ENTRY, &ace))
63     {
64       acl_tag_t tag;
65       if (acl_get_tag_type (ace, &tag) < 0)
66         return -1;
67       if (!(tag == ACL_USER_OBJ || tag == ACL_GROUP_OBJ || tag == ACL_OTHER))
68         return 1;
69     }
70   return 0;
71
72 #  else /* IRIX, Tru64 */
73 #   if HAVE_ACL_TO_SHORT_TEXT /* IRIX */
74   /* Don't use acl_get_entry: it is undocumented.  */
75
76   int count = acl->acl_cnt;
77   int i;
78
79   for (i = 0; i < count; i++)
80     {
81       acl_entry_t ace = &acl->acl_entry[i];
82       acl_tag_t tag = ace->ae_tag;
83
84       if (!(tag == ACL_USER_OBJ || tag == ACL_GROUP_OBJ
85             || tag == ACL_OTHER_OBJ))
86         return 1;
87     }
88   return 0;
89
90 #   endif
91 #   if HAVE_ACL_FREE_TEXT /* Tru64 */
92   /* Don't use acl_get_entry: it takes only one argument and does not work.  */
93
94   int count = acl->acl_num;
95   acl_entry_t ace;
96
97   for (ace = acl->acl_first; count > 0; ace = ace->next, count--)
98     {
99       acl_tag_t tag;
100       acl_perm_t perm;
101
102       tag = ace->entry->acl_type;
103       if (!(tag == ACL_USER_OBJ || tag == ACL_GROUP_OBJ || tag == ACL_OTHER))
104         return 1;
105
106       perm = ace->entry->acl_perm;
107       /* On Tru64, perm can also contain non-standard bits such as
108          PERM_INSERT, PERM_DELETE, PERM_MODIFY, PERM_LOOKUP, ... */
109       if ((perm & ~(ACL_READ | ACL_WRITE | ACL_EXECUTE)) != 0)
110         return 1;
111     }
112   return 0;
113
114 #   endif
115 #  endif
116 }
117
118 # endif
119
120 #endif
121
122
123 /* Return 1 if NAME has a nontrivial access control list, 0 if NAME
124    only has no or a base access control list, and -1 (setting errno)
125    on error.  SB must be set to the stat buffer of FILE.  */
126
127 int
128 file_has_acl (char const *name, struct stat const *sb)
129 {
130 #if USE_ACL
131   if (! S_ISLNK (sb->st_mode))
132     {
133 # if HAVE_ACL_GET_FILE
134
135       /* POSIX 1003.1e (draft 17 -- abandoned) specific version.  */
136       /* Linux, FreeBSD, MacOS X, IRIX, Tru64 */
137       int ret;
138
139       if (HAVE_ACL_EXTENDED_FILE) /* Linux */
140         {
141           /* On Linux, acl_extended_file is an optimized function: It only
142              makes two calls to getxattr(), one for ACL_TYPE_ACCESS, one for
143              ACL_TYPE_DEFAULT.  */
144           ret = acl_extended_file (name);
145         }
146       else /* FreeBSD, MacOS X, IRIX, Tru64 */
147         {
148 #  if HAVE_ACL_TYPE_EXTENDED /* MacOS X */
149           /* On MacOS X, acl_get_file (name, ACL_TYPE_ACCESS)
150              and acl_get_file (name, ACL_TYPE_DEFAULT)
151              always return NULL / EINVAL.  There is no point in making
152              these two useless calls.  The real ACL is retrieved through
153              acl_get_file (name, ACL_TYPE_EXTENDED).  */
154           acl_t acl = acl_get_file (name, ACL_TYPE_EXTENDED);
155           if (acl)
156             {
157               ret = acl_extended_nontrivial (acl);
158               acl_free (acl);
159             }
160           else
161             ret = -1;
162 #  else /* FreeBSD, IRIX, Tru64 */
163           acl_t acl = acl_get_file (name, ACL_TYPE_ACCESS);
164           if (acl)
165             {
166               int saved_errno;
167
168               ret = acl_access_nontrivial (acl);
169               saved_errno = errno;
170               acl_free (acl);
171               errno = saved_errno;
172 #   if HAVE_ACL_FREE_TEXT /* Tru64 */
173               /* On OSF/1, acl_get_file (name, ACL_TYPE_DEFAULT) always
174                  returns NULL with errno not set.  There is no point in
175                  making this call.  */
176 #   else /* FreeBSD, IRIX */
177               /* On Linux, FreeBSD, IRIX, acl_get_file (name, ACL_TYPE_ACCESS)
178                  and acl_get_file (name, ACL_TYPE_DEFAULT) on a directory
179                  either both succeed or both fail; it depends on the
180                  filesystem.  Therefore there is no point in making the second
181                  call if the first one already failed.  */
182               if (ret == 0 && S_ISDIR (sb->st_mode))
183                 {
184                   acl = acl_get_file (name, ACL_TYPE_DEFAULT);
185                   if (acl)
186                     {
187                       ret = (0 < acl_entries (acl));
188                       acl_free (acl);
189                     }
190                   else
191                     ret = -1;
192                 }
193 #   endif
194             }
195           else
196             ret = -1;
197 #  endif
198         }
199       if (ret < 0)
200         return ACL_NOT_WELL_SUPPORTED (errno) ? 0 : -1;
201       return ret;
202
203 # elif HAVE_ACL && defined GETACLCNT /* Solaris, Cygwin, not HP-UX */
204
205 #  if HAVE_ACL_TRIVIAL
206
207       /* Solaris 10, which also has NFSv4 and ZFS style ACLs.  */
208       return acl_trivial (name);
209
210 #  else /* Solaris, Cygwin, general case */
211
212       /* Solaris 2.5 through Solaris 10, Cygwin, and contemporaneous versions
213          of Unixware.  The acl() call returns the access and default ACL both
214          at once.  */
215       int n = acl (name, GETACLCNT, 0, NULL);
216       return n < 0 ? (errno == ENOSYS ? 0 : -1) : (MIN_ACL_ENTRIES < n);
217
218 #  endif
219
220 # endif
221     }
222 #endif
223
224   /* FIXME: Add support for AIX.  Please see Samba's
225      source/lib/sysacls.c file for fix-related ideas.  */
226
227   return 0;
228 }