Work around the Solaris 10 ACE ACLs ABI change.
[pspp] / lib / set-mode-acl.c
1 /* set-mode-acl.c - set access control list equivalent to a mode
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, and Bruno Haible.  */
19
20 #include <config.h>
21
22 #include "acl.h"
23
24 #include "acl-internal.h"
25
26 /* If DESC is a valid file descriptor use fchmod to change the
27    file's mode to MODE on systems that have fchown. On systems
28    that don't have fchown and if DESC is invalid, use chown on
29    NAME instead.
30    Return 0 if successful.  Return -1 and set errno upon failure.  */
31
32 int
33 chmod_or_fchmod (const char *name, int desc, mode_t mode)
34 {
35   if (HAVE_FCHMOD && desc != -1)
36     return fchmod (desc, mode);
37   else
38     return chmod (name, mode);
39 }
40
41 /* Set the access control lists of a file. If DESC is a valid file
42    descriptor, use file descriptor operations where available, else use
43    filename based operations on NAME.  If access control lists are not
44    available, fchmod the target file to MODE.  Also sets the
45    non-permission bits of the destination file (S_ISUID, S_ISGID, S_ISVTX)
46    to those from MODE if any are set.
47    Return 0 if successful.  Return -1 and set errno upon failure.  */
48
49 int
50 qset_acl (char const *name, int desc, mode_t mode)
51 {
52 #if USE_ACL
53 # if HAVE_ACL_GET_FILE
54   /* POSIX 1003.1e draft 17 (abandoned) specific version.  */
55   /* Linux, FreeBSD, MacOS X, IRIX, Tru64 */
56 #  if MODE_INSIDE_ACL
57   /* Linux, FreeBSD, IRIX, Tru64 */
58
59   /* We must also have acl_from_text and acl_delete_def_file.
60      (acl_delete_def_file could be emulated with acl_init followed
61       by acl_set_file, but acl_set_file with an empty acl is
62       unspecified.)  */
63
64 #   ifndef HAVE_ACL_FROM_TEXT
65 #    error Must have acl_from_text (see POSIX 1003.1e draft 17).
66 #   endif
67 #   ifndef HAVE_ACL_DELETE_DEF_FILE
68 #    error Must have acl_delete_def_file (see POSIX 1003.1e draft 17).
69 #   endif
70
71   acl_t acl;
72   int ret;
73
74   if (HAVE_ACL_FROM_MODE) /* Linux */
75     {
76       acl = acl_from_mode (mode);
77       if (!acl)
78         return -1;
79     }
80   else /* FreeBSD, IRIX, Tru64 */
81     {
82       /* If we were to create the ACL using the functions acl_init(),
83          acl_create_entry(), acl_set_tag_type(), acl_set_qualifier(),
84          acl_get_permset(), acl_clear_perm[s](), acl_add_perm(), we
85          would need to create a qualifier.  I don't know how to do this.
86          So create it using acl_from_text().  */
87
88 #   if HAVE_ACL_FREE_TEXT /* Tru64 */
89       char acl_text[] = "u::---,g::---,o::---,";
90 #   else /* FreeBSD, IRIX */
91       char acl_text[] = "u::---,g::---,o::---";
92 #   endif
93
94       if (mode & S_IRUSR) acl_text[ 3] = 'r';
95       if (mode & S_IWUSR) acl_text[ 4] = 'w';
96       if (mode & S_IXUSR) acl_text[ 5] = 'x';
97       if (mode & S_IRGRP) acl_text[10] = 'r';
98       if (mode & S_IWGRP) acl_text[11] = 'w';
99       if (mode & S_IXGRP) acl_text[12] = 'x';
100       if (mode & S_IROTH) acl_text[17] = 'r';
101       if (mode & S_IWOTH) acl_text[18] = 'w';
102       if (mode & S_IXOTH) acl_text[19] = 'x';
103
104       acl = acl_from_text (acl_text);
105       if (!acl)
106         return -1;
107     }
108   if (HAVE_ACL_SET_FD && desc != -1)
109     ret = acl_set_fd (desc, acl);
110   else
111     ret = acl_set_file (name, ACL_TYPE_ACCESS, acl);
112   if (ret != 0)
113     {
114       int saved_errno = errno;
115       acl_free (acl);
116
117       if (ACL_NOT_WELL_SUPPORTED (errno))
118         return chmod_or_fchmod (name, desc, mode);
119       else
120         {
121           errno = saved_errno;
122           return -1;
123         }
124     }
125   else
126     acl_free (acl);
127
128   if (S_ISDIR (mode) && acl_delete_def_file (name))
129     return -1;
130
131   if (mode & (S_ISUID | S_ISGID | S_ISVTX))
132     {
133       /* We did not call chmod so far, so the special bits have not yet
134          been set.  */
135       return chmod_or_fchmod (name, desc, mode);
136     }
137   return 0;
138
139 #  else /* !MODE_INSIDE_ACL */
140   /* MacOS X */
141
142 #   if !HAVE_ACL_TYPE_EXTENDED
143 #    error Must have ACL_TYPE_EXTENDED
144 #   endif
145
146   /* On MacOS X,  acl_get_file (name, ACL_TYPE_ACCESS)
147      and          acl_get_file (name, ACL_TYPE_DEFAULT)
148      always return NULL / EINVAL.  You have to use
149                   acl_get_file (name, ACL_TYPE_EXTENDED)
150      or           acl_get_fd (open (name, ...))
151      to retrieve an ACL.
152      On the other hand,
153                   acl_set_file (name, ACL_TYPE_ACCESS, acl)
154      and          acl_set_file (name, ACL_TYPE_DEFAULT, acl)
155      have the same effect as
156                   acl_set_file (name, ACL_TYPE_EXTENDED, acl):
157      Each of these calls sets the file's ACL.  */
158
159   acl_t acl;
160   int ret;
161
162   /* Remove the ACL if the file has ACLs.  */
163   if (HAVE_ACL_GET_FD && desc != -1)
164     acl = acl_get_fd (desc);
165   else
166     acl = acl_get_file (name, ACL_TYPE_EXTENDED);
167   if (acl)
168     {
169       acl_free (acl);
170
171       acl = acl_init (0);
172       if (acl)
173         {
174           if (HAVE_ACL_SET_FD && desc != -1)
175             ret = acl_set_fd (desc, acl);
176           else
177             ret = acl_set_file (name, ACL_TYPE_EXTENDED, acl);
178           if (ret != 0)
179             {
180               int saved_errno = errno;
181
182               acl_free (acl);
183
184               if (ACL_NOT_WELL_SUPPORTED (saved_errno))
185                 return chmod_or_fchmod (name, desc, mode);
186               else
187                 {
188                   errno = saved_errno;
189                   return -1;
190                 }
191             }
192           acl_free (acl);
193         }
194     }
195
196   /* Since !MODE_INSIDE_ACL, we have to call chmod explicitly.  */
197   return chmod_or_fchmod (name, desc, mode);
198 #  endif
199
200 # elif HAVE_ACL && defined GETACLCNT /* Solaris, Cygwin, not HP-UX */
201
202 #  if defined ACL_NO_TRIVIAL
203   /* Solaris 10 (newer version), which has additional API declared in
204      <sys/acl.h> (acl_t) and implemented in libsec (acl_set, acl_trivial,
205      acl_fromtext, ...).  */
206
207   acl_t *aclp;
208   char acl_text[] = "user::---,group::---,mask:---,other:---";
209   int ret;
210   int saved_errno;
211
212   if (mode & S_IRUSR) acl_text[ 6] = 'r';
213   if (mode & S_IWUSR) acl_text[ 7] = 'w';
214   if (mode & S_IXUSR) acl_text[ 8] = 'x';
215   if (mode & S_IRGRP) acl_text[17] = acl_text[26] = 'r';
216   if (mode & S_IWGRP) acl_text[18] = acl_text[27] = 'w';
217   if (mode & S_IXGRP) acl_text[19] = acl_text[28] = 'x';
218   if (mode & S_IROTH) acl_text[36] = 'r';
219   if (mode & S_IWOTH) acl_text[37] = 'w';
220   if (mode & S_IXOTH) acl_text[38] = 'x';
221
222   if (acl_fromtext (acl_text, &aclp) != 0)
223     {
224       errno = ENOMEM;
225       return -1;
226     }
227
228   ret = (desc < 0 ? acl_set (name, aclp) : facl_set (desc, aclp));
229   saved_errno = errno;
230   acl_free (aclp);
231   if (ret < 0)
232     {
233       if (saved_errno == ENOSYS)
234         return chmod_or_fchmod (name, desc, mode);
235       errno = saved_errno;
236       return -1;
237     }
238
239   if (mode & (S_ISUID | S_ISGID | S_ISVTX))
240     {
241       /* We did not call chmod so far, so the special bits have not yet
242          been set.  */
243       return chmod_or_fchmod (name, desc, mode);
244     }
245   return 0;
246
247 #  else /* Solaris, Cygwin, general case */
248
249 #   ifdef ACE_GETACL
250   /* Solaris also has a different variant of ACLs, used in ZFS and NFSv4
251      file systems (whereas the other ones are used in UFS file systems).  */
252
253   /* The flags in the ace_t structure changed in a binary incompatible way
254      when ACL_NO_TRIVIAL etc. were introduced in <sys/acl.h> version 1.15.
255      How to distinguish the two conventions at runtime?
256      We fetch the existing ACL.  In the old convention, usually three ACEs have
257      a_flags = ACE_OWNER / ACE_GROUP / ACE_OTHER, in the range 0x0100..0x0400.
258      In the new convention, these values are not used.  */
259   int convention;
260
261   {
262     int count;
263     ace_t *entries;
264
265     for (;;)
266       {
267         if (desc != -1)
268           count = facl (desc, ACE_GETACLCNT, 0, NULL);
269         else
270           count = acl (name, ACE_GETACLCNT, 0, NULL);
271         if (count <= 0)
272           {
273             convention = -1;
274             break;
275           }
276         entries = (ace_t *) malloc (count * sizeof (ace_t));
277         if (entries == NULL)
278           {
279             errno = ENOMEM;
280             return -1;
281           }
282         if ((desc != -1
283              ? facl (desc, ACE_GETACL, count, entries)
284              : acl (name, ACE_GETACL, count, entries))
285             == count)
286           {
287             int i;
288
289             convention = 0;
290             for (i = 0; i < count; i++)
291               if (entries[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_OTHER))
292                 {
293                   convention = 1;
294                   break;
295                 }
296             free (entries);
297             break;
298           }
299         /* Huh? The number of ACL entries changed since the last call.
300            Repeat.  */
301         free (entries);
302       }
303   }
304
305   if (convention >= 0)
306     {
307       ace_t entries[3];
308       int ret;
309
310       if (convention)
311         {
312           /* Running on Solaris 10.  */
313           entries[0].a_type = ALLOW;
314           entries[0].a_flags = ACE_OWNER;
315           entries[0].a_who = 0; /* irrelevant */
316           entries[0].a_access_mask = (mode >> 6) & 7;
317           entries[1].a_type = ALLOW;
318           entries[1].a_flags = ACE_GROUP;
319           entries[1].a_who = 0; /* irrelevant */
320           entries[1].a_access_mask = (mode >> 3) & 7;
321           entries[2].a_type = ALLOW;
322           entries[2].a_flags = ACE_OTHER;
323           entries[2].a_who = 0;
324           entries[2].a_access_mask = mode & 7;
325         }
326       else
327         {
328           /* Running on Solaris 10 (newer version) or Solaris 11.  */
329           entries[0].a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
330           entries[0].a_flags = NEW_ACE_OWNER;
331           entries[0].a_who = 0; /* irrelevant */
332           entries[0].a_access_mask =
333             (mode & 0400 ? NEW_ACE_READ_DATA : 0)
334             | (mode & 0200 ? NEW_ACE_WRITE_DATA : 0)
335             | (mode & 0100 ? NEW_ACE_EXECUTE : 0);
336           entries[1].a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
337           entries[1].a_flags = NEW_ACE_GROUP | NEW_ACE_IDENTIFIER_GROUP;
338           entries[1].a_who = 0; /* irrelevant */
339           entries[1].a_access_mask =
340             (mode & 0040 ? NEW_ACE_READ_DATA : 0)
341             | (mode & 0020 ? NEW_ACE_WRITE_DATA : 0)
342             | (mode & 0010 ? NEW_ACE_EXECUTE : 0);
343           entries[2].a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
344           entries[2].a_flags = ACE_EVERYONE;
345           entries[2].a_who = 0;
346           entries[2].a_access_mask =
347             (mode & 0004 ? NEW_ACE_READ_DATA : 0)
348             | (mode & 0002 ? NEW_ACE_WRITE_DATA : 0)
349             | (mode & 0001 ? NEW_ACE_EXECUTE : 0);
350         }
351       if (desc != -1)
352         ret = facl (desc, ACE_SETACL,
353                     sizeof (entries) / sizeof (aclent_t), entries);
354       else
355         ret = acl (name, ACE_SETACL,
356                    sizeof (entries) / sizeof (aclent_t), entries);
357       if (ret < 0 && errno != EINVAL && errno != ENOTSUP)
358         {
359           if (errno == ENOSYS)
360             return chmod_or_fchmod (name, desc, mode);
361           return -1;
362         }
363     }
364 #   endif
365
366   {
367     aclent_t entries[3];
368     int ret;
369
370     entries[0].a_type = USER_OBJ;
371     entries[0].a_id = 0; /* irrelevant */
372     entries[0].a_perm = (mode >> 6) & 7;
373     entries[1].a_type = GROUP_OBJ;
374     entries[1].a_id = 0; /* irrelevant */
375     entries[1].a_perm = (mode >> 3) & 7;
376     entries[2].a_type = OTHER_OBJ;
377     entries[2].a_id = 0;
378     entries[2].a_perm = mode & 7;
379
380     if (desc != -1)
381       ret = facl (desc, SETACL, sizeof (entries) / sizeof (aclent_t), entries);
382     else
383       ret = acl (name, SETACL, sizeof (entries) / sizeof (aclent_t), entries);
384     if (ret < 0)
385       {
386         if (errno == ENOSYS)
387           return chmod_or_fchmod (name, desc, mode);
388         return -1;
389       }
390   }
391
392   if (!MODE_INSIDE_ACL || (mode & (S_ISUID | S_ISGID | S_ISVTX)))
393     {
394       /* We did not call chmod so far, so the special bits have not yet
395          been set.  */
396       return chmod_or_fchmod (name, desc, mode);
397     }
398   return 0;
399
400 #  endif
401
402 # elif HAVE_GETACL /* HP-UX */
403
404   struct stat statbuf;
405   struct acl_entry entries[3];
406   int ret;
407
408   if (desc != -1)
409     ret = fstat (desc, &statbuf);
410   else
411     ret = stat (name, &statbuf);
412   if (ret < 0)
413     return -1;
414
415   entries[0].uid = statbuf.st_uid;
416   entries[0].gid = ACL_NSGROUP;
417   entries[0].mode = (mode >> 6) & 7;
418   entries[1].uid = ACL_NSUSER;
419   entries[1].gid = statbuf.st_gid;
420   entries[1].mode = (mode >> 3) & 7;
421   entries[2].uid = ACL_NSUSER;
422   entries[2].gid = ACL_NSGROUP;
423   entries[2].mode = mode & 7;
424
425   if (desc != -1)
426     ret = fsetacl (desc, sizeof (entries) / sizeof (struct acl_entry), entries);
427   else
428     ret = setacl (name, sizeof (entries) / sizeof (struct acl_entry), entries);
429   if (ret < 0)
430     {
431       if (errno == ENOSYS || errno == EOPNOTSUPP)
432         return chmod_or_fchmod (name, desc, mode);
433       return -1;
434     }
435
436   if (mode & (S_ISUID | S_ISGID | S_ISVTX))
437     {
438       /* We did not call chmod so far, so the special bits have not yet
439          been set.  */
440       return chmod_or_fchmod (name, desc, mode);
441     }
442   return 0;
443
444 # elif HAVE_ACLX_GET && 0 /* AIX */
445
446   /* TODO: use aclx_fput or aclx_put, respectively */
447
448 # elif HAVE_STATACL /* older AIX */
449
450   union { struct acl a; char room[128]; } u;
451   int ret;
452
453   u.a.acl_len = (char *) &u.a.acl_ext[0] - (char *) &u.a; /* no entries */
454   u.a.acl_mode = mode & ~(S_IXACL | 0777);
455   u.a.u_access = (mode >> 6) & 7;
456   u.a.g_access = (mode >> 3) & 7;
457   u.a.o_access = mode & 7;
458
459   if (desc != -1)
460     ret = fchacl (desc, &u.a, u.a.acl_len);
461   else
462     ret = chacl (name, &u.a, u.a.acl_len);
463
464   if (ret < 0 && errno == ENOSYS)
465     return chmod_or_fchmod (name, desc, mode);
466
467   return ret;
468
469 # else /* Unknown flavor of ACLs */
470   return chmod_or_fchmod (name, desc, mode);
471 # endif
472 #else /* !USE_ACL */
473   return chmod_or_fchmod (name, desc, mode);
474 #endif
475 }
476
477 /* As with qset_acl, but also output a diagnostic on failure.  */
478
479 int
480 set_acl (char const *name, int desc, mode_t mode)
481 {
482   int r = qset_acl (name, desc, mode);
483   if (r != 0)
484     error (0, errno, _("setting permissions for %s"), quote (name));
485   return r;
486 }