New function acl_extended_nontrivial (MacOS X only).
[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 ACL_NO_TRIVIAL
180   /* Solaris 10 NFSv4 ACLs.  */
181
182   int ret;
183   acl_t *aclp = NULL;
184   ret = (source_desc < 0
185          ? acl_get (src_name, ACL_NO_TRIVIAL, &aclp)
186          : facl_get (source_desc, ACL_NO_TRIVIAL, &aclp));
187   if (ret != 0 && errno != ENOSYS)
188     return -2;
189
190   ret = qset_acl (dst_name, dest_desc, mode);
191   if (ret != 0)
192     return -1;
193
194   if (aclp)
195     {
196       ret = (dest_desc < 0
197              ? acl_set (dst_name, aclp)
198              : facl_set (dest_desc, aclp));
199       if (ret != 0)
200         {
201           int saved_errno = errno;
202
203           acl_free (aclp);
204           errno = saved_errno;
205           return -1;
206         }
207       acl_free (aclp);
208     }
209
210   return 0;
211
212 #else
213
214   return qset_acl (dst_name, dest_desc, mode);
215
216 #endif
217 }
218
219
220 /* Copy access control lists from one file to another. If SOURCE_DESC is
221    a valid file descriptor, use file descriptor operations, else use
222    filename based operations on SRC_NAME. Likewise for DEST_DESC and
223    DST_NAME.
224    If access control lists are not available, fchmod the target file to
225    MODE.  Also sets the non-permission bits of the destination file
226    (S_ISUID, S_ISGID, S_ISVTX) to those from MODE if any are set.
227    Return 0 if successful, otherwise output a diagnostic and return -1.  */
228
229 int
230 copy_acl (const char *src_name, int source_desc, const char *dst_name,
231           int dest_desc, mode_t mode)
232 {
233   int ret = qcopy_acl (src_name, source_desc, dst_name, dest_desc, mode);
234   switch (ret)
235     {
236     case -2:
237       error (0, errno, "%s", quote (src_name));
238       return -1;
239
240     case -1:
241       error (0, errno, _("preserving permissions for %s"), quote (dst_name));
242       return -1;
243
244     default:
245       return 0;
246     }
247 }