Add support for Solaris 7..10 ACLs.
[gnulib.git] / lib / copy-acl.c
1 /* copy-acl.c - copy access control list from one file to another file
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 /* Copy access control lists from one file to another. If SOURCE_DESC is
28    a valid file descriptor, use file descriptor operations, else use
29    filename based operations on SRC_NAME. Likewise for DEST_DESC and
30    DST_NAME.
31    If access control lists are not available, fchmod the target file to
32    MODE.  Also sets the non-permission bits of the destination file
33    (S_ISUID, S_ISGID, S_ISVTX) to those from MODE if any are set.
34    Return 0 if successful.
35    Return -2 and set errno for an error relating to the source file.
36    Return -1 and set errno for an error relating to the destination file.  */
37
38 static int
39 qcopy_acl (const char *src_name, int source_desc, const char *dst_name,
40            int dest_desc, mode_t mode)
41 {
42 #if USE_ACL && HAVE_ACL_GET_FILE
43   /* POSIX 1003.1e (draft 17 -- abandoned) specific version.  */
44   /* Linux, FreeBSD, MacOS X, IRIX, Tru64 */
45 # if MODE_INSIDE_ACL
46   /* Linux, FreeBSD, IRIX, Tru64 */
47
48   acl_t acl;
49   int ret;
50
51   if (HAVE_ACL_GET_FD && source_desc != -1)
52     acl = acl_get_fd (source_desc);
53   else
54     acl = acl_get_file (src_name, ACL_TYPE_ACCESS);
55   if (acl == NULL)
56     {
57       if (ACL_NOT_WELL_SUPPORTED (errno))
58         return qset_acl (dst_name, dest_desc, mode);
59       else
60         return -2;
61     }
62
63   if (HAVE_ACL_SET_FD && dest_desc != -1)
64     ret = acl_set_fd (dest_desc, acl);
65   else
66     ret = acl_set_file (dst_name, ACL_TYPE_ACCESS, acl);
67   if (ret != 0)
68     {
69       int saved_errno = errno;
70
71       if (ACL_NOT_WELL_SUPPORTED (errno) && !acl_access_nontrivial (acl))
72         {
73           acl_free (acl);
74           return chmod_or_fchmod (dst_name, dest_desc, mode);
75         }
76       else
77         {
78           acl_free (acl);
79           chmod_or_fchmod (dst_name, dest_desc, mode);
80           errno = saved_errno;
81           return -1;
82         }
83     }
84   else
85     acl_free (acl);
86
87   if (mode & (S_ISUID | S_ISGID | S_ISVTX))
88     {
89       /* We did not call chmod so far, and either the mode and the ACL are
90          separate or special bits are to be set which don't fit into ACLs.  */
91
92       if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
93         return -1;
94     }
95
96   if (S_ISDIR (mode))
97     {
98       acl = acl_get_file (src_name, ACL_TYPE_DEFAULT);
99       if (acl == NULL)
100         return -2;
101
102       if (acl_set_file (dst_name, ACL_TYPE_DEFAULT, acl))
103         {
104           int saved_errno = errno;
105
106           acl_free (acl);
107           errno = saved_errno;
108           return -1;
109         }
110       else
111         acl_free (acl);
112     }
113   return 0;
114
115 # else /* !MODE_INSIDE_ACL */
116   /* MacOS X */
117
118 #  if !HAVE_ACL_TYPE_EXTENDED
119 #   error Must have ACL_TYPE_EXTENDED
120 #  endif
121
122   /* On MacOS X,  acl_get_file (name, ACL_TYPE_ACCESS)
123      and          acl_get_file (name, ACL_TYPE_DEFAULT)
124      always return NULL / EINVAL.  You have to use
125                   acl_get_file (name, ACL_TYPE_EXTENDED)
126      or           acl_get_fd (open (name, ...))
127      to retrieve an ACL.
128      On the other hand,
129                   acl_set_file (name, ACL_TYPE_ACCESS, acl)
130      and          acl_set_file (name, ACL_TYPE_DEFAULT, acl)
131      have the same effect as
132                   acl_set_file (name, ACL_TYPE_EXTENDED, acl):
133      Each of these calls sets the file's ACL.  */
134
135   acl_t acl;
136   int ret;
137
138   if (HAVE_ACL_GET_FD && source_desc != -1)
139     acl = acl_get_fd (source_desc);
140   else
141     acl = acl_get_file (src_name, ACL_TYPE_EXTENDED);
142   if (acl == NULL)
143     {
144       if (ACL_NOT_WELL_SUPPORTED (errno))
145         return qset_acl (dst_name, dest_desc, mode);
146       else
147         return -2;
148     }
149
150   if (HAVE_ACL_SET_FD && dest_desc != -1)
151     ret = acl_set_fd (dest_desc, acl);
152   else
153     ret = acl_set_file (dst_name, ACL_TYPE_EXTENDED, acl);
154   if (ret != 0)
155     {
156       int saved_errno = errno;
157
158       if (ACL_NOT_WELL_SUPPORTED (errno) && !acl_extended_nontrivial (acl))
159         {
160           acl_free (acl);
161           return chmod_or_fchmod (dst_name, dest_desc, mode);
162         }
163       else
164         {
165           acl_free (acl);
166           chmod_or_fchmod (dst_name, dest_desc, mode);
167           errno = saved_errno;
168           return -1;
169         }
170     }
171   else
172     acl_free (acl);
173
174   /* Since !MODE_INSIDE_ACL, we have to call chmod explicitly.  */
175   return chmod_or_fchmod (dst_name, dest_desc, mode);
176
177 # endif
178
179 #elif USE_ACL && defined GETACL /* Solaris, Cygwin, not HP-UX */
180
181 # if defined ACL_NO_TRIVIAL
182   /* Solaris 10 (newer version), which has additional API declared in
183      <sys/acl.h> (acl_t) and implemented in libsec (acl_set, acl_trivial,
184      acl_fromtext, ...).  */
185
186   int ret;
187   acl_t *aclp = NULL;
188   ret = (source_desc < 0
189          ? acl_get (src_name, ACL_NO_TRIVIAL, &aclp)
190          : facl_get (source_desc, ACL_NO_TRIVIAL, &aclp));
191   if (ret != 0 && errno != ENOSYS)
192     return -2;
193
194   ret = qset_acl (dst_name, dest_desc, mode);
195   if (ret != 0)
196     return -1;
197
198   if (aclp)
199     {
200       ret = (dest_desc < 0
201              ? acl_set (dst_name, aclp)
202              : facl_set (dest_desc, aclp));
203       if (ret != 0)
204         {
205           int saved_errno = errno;
206
207           acl_free (aclp);
208           errno = saved_errno;
209           return -1;
210         }
211       acl_free (aclp);
212     }
213
214   return 0;
215
216 # else /* Solaris, Cygwin, general case */
217
218   /* Solaris 2.5 through Solaris 10, Cygwin, and contemporaneous versions
219      of Unixware.  The acl() call returns the access and default ACL both
220      at once.  */
221 #  ifdef ACE_GETACL
222   int ace_count;
223   ace_t *ace_entries;
224 #  endif
225   int count;
226   aclent_t *entries;
227   int did_chmod;
228   int saved_errno;
229   int ret;
230
231 #  ifdef ACE_GETACL
232   /* Solaris also has a different variant of ACLs, used in ZFS and NFSv4
233      file systems (whereas the other ones are used in UFS file systems).
234      There is an API
235        pathconf (name, _PC_ACL_ENABLED)
236        fpathconf (desc, _PC_ACL_ENABLED)
237      that allows to determine which of the two kinds of ACLs is supported
238      for the given file.  But some file systems may implement this call
239      incorrectly, so better not use it.
240      When fetching the source ACL, we simply fetch both ACL types.
241      When setting the destination ACL, we try either ACL types, assuming
242      that the kernel will translate the ACL from one form to the other.
243      (See in <http://docs.sun.com/app/docs/doc/819-2241/6n4huc7ia?l=en&a=view>
244      the description of ENOTSUP.)  */
245   for (;;)
246     {
247       ace_count = (source_desc != -1
248                    ? facl (source_desc, ACE_GETACLCNT, 0, NULL)
249                    : acl (src_name, ACE_GETACLCNT, 0, NULL));
250
251       if (ace_count < 0)
252         {
253           if (errno == ENOSYS || errno == EINVAL)
254             {
255               ace_count = 0;
256               ace_entries = NULL;
257               break;
258             }
259           else
260             return -2;
261         }
262
263       if (ace_count == 0)
264         {
265           ace_entries = NULL;
266           break;
267         }
268
269       ace_entries = (ace_t *) malloc (ace_count * sizeof (ace_t));
270       if (ace_entries == NULL)
271         {
272           errno = ENOMEM;
273           return -2;
274         }
275
276       if ((source_desc != -1
277            ? facl (source_desc, ACE_GETACL, ace_count, ace_entries)
278            : acl (src_name, ACE_GETACL, ace_count, ace_entries))
279           == ace_count)
280         break;
281       /* Huh? The number of ACL entries changed since the last call.
282          Repeat.  */
283     }
284 #  endif
285
286   for (;;)
287     {
288       count = (source_desc != -1
289                ? facl (source_desc, GETACLCNT, 0, NULL)
290                : acl (src_name, GETACLCNT, 0, NULL));
291
292       if (count < 0)
293         {
294           if (errno == ENOSYS || errno == ENOTSUP)
295             {
296               count = 0;
297               entries = NULL;
298               break;
299             }
300           else
301             return -2;
302         }
303
304       if (count == 0)
305         {
306           entries = NULL;
307           break;
308         }
309
310       entries = (aclent_t *) malloc (count * sizeof (aclent_t));
311       if (entries == NULL)
312         {
313           errno = ENOMEM;
314           return -2;
315         }
316
317       if ((source_desc != -1
318            ? facl (source_desc, GETACL, count, entries)
319            : acl (src_name, GETACL, count, entries))
320           == count)
321         break;
322       /* Huh? The number of ACL entries changed since the last call.
323          Repeat.  */
324     }
325
326   /* Is there an ACL of either kind?  */
327 #  ifdef ACE_GETACL
328   if (ace_count == 0)
329 #  endif
330     if (count == 0)
331       return qset_acl (dst_name, dest_desc, mode);
332
333   did_chmod = 0; /* set to 1 once the mode bits in 0777 have been set,
334                     set to 2 once the mode bits other than 0777 have been set */
335   saved_errno = 0; /* the first non-ignorable error code */
336
337   /* If both ace_entries and entries are available, try SETACL before
338      ACE_SETACL, because SETACL cannot fail with ENOTSUP whereas ACE_SETACL
339      can.  */
340
341   if (count > 0)
342     {
343       ret = (dest_desc != -1
344              ? facl (dest_desc, SETACL, count, entries)
345              : acl (dst_name, SETACL, count, entries));
346       if (ret < 0)
347         {
348           saved_errno = errno;
349           if (errno == ENOSYS && !acl_nontrivial (count, entries))
350             saved_errno = 0;
351         }
352       else
353         did_chmod = 1;
354     }
355   free (entries);
356
357 #  ifdef ACE_GETACL
358   if (ace_count > 0)
359     {
360       ret = (dest_desc != -1
361              ? facl (dest_desc, ACE_SETACL, ace_count, ace_entries)
362              : acl (dst_name, ACE_SETACL, ace_count, ace_entries));
363       if (ret < 0 && saved_errno == 0)
364         {
365           saved_errno = errno;
366           if ((errno == ENOSYS || errno == EINVAL || errno == ENOTSUP)
367               && !acl_ace_nontrivial (ace_count, ace_entries))
368             saved_errno = 0;
369         }
370     }
371   free (ace_entries);
372 #  endif
373
374   if (did_chmod <= ((mode & (S_ISUID | S_ISGID | S_ISVTX)) ? 1 : 0))
375     {
376       /* We did not call chmod so far, and either the mode and the ACL are
377          separate or special bits are to be set which don't fit into ACLs.  */
378
379       if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
380         {
381           if (saved_errno == 0)
382             saved_errno = errno;
383         }
384     }
385
386   if (saved_errno)
387     {
388       errno = saved_errno;
389       return -1;
390     }
391   return 0;
392
393 # endif
394
395 #else
396
397   return qset_acl (dst_name, dest_desc, mode);
398
399 #endif
400 }
401
402
403 /* Copy access control lists from one file to another. If SOURCE_DESC is
404    a valid file descriptor, use file descriptor operations, else use
405    filename based operations on SRC_NAME. Likewise for DEST_DESC and
406    DST_NAME.
407    If access control lists are not available, fchmod the target file to
408    MODE.  Also sets the non-permission bits of the destination file
409    (S_ISUID, S_ISGID, S_ISVTX) to those from MODE if any are set.
410    Return 0 if successful, otherwise output a diagnostic and return -1.  */
411
412 int
413 copy_acl (const char *src_name, int source_desc, const char *dst_name,
414           int dest_desc, mode_t mode)
415 {
416   int ret = qcopy_acl (src_name, source_desc, dst_name, dest_desc, mode);
417   switch (ret)
418     {
419     case -2:
420       error (0, errno, "%s", quote (src_name));
421       return -1;
422
423     case -1:
424       error (0, errno, _("preserving permissions for %s"), quote (dst_name));
425       return -1;
426
427     default:
428       return 0;
429     }
430 }