]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - bin/setfacl/setfacl.c
setfacl: add recursive functionality
[FreeBSD/FreeBSD.git] / bin / setfacl / setfacl.c
1 /*-
2  * Copyright (c) 2001 Chris D. Faulhaber
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29
30 #include <sys/types.h>
31 #include <sys/param.h>
32 #include <sys/stat.h>
33 #include <sys/acl.h>
34 #include <sys/queue.h>
35
36 #include <err.h>
37 #include <errno.h>
38 #include <fts.h>
39 #include <stdbool.h>
40 #include <stdint.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45
46 #include "setfacl.h"
47
48 /* file operations */
49 #define OP_MERGE_ACL            0x00    /* merge acl's (-mM) */
50 #define OP_REMOVE_DEF           0x01    /* remove default acl's (-k) */
51 #define OP_REMOVE_EXT           0x02    /* remove extended acl's (-b) */
52 #define OP_REMOVE_ACL           0x03    /* remove acl's (-xX) */
53 #define OP_REMOVE_BY_NUMBER     0x04    /* remove acl's (-xX) by acl entry number */
54 #define OP_ADD_ACL              0x05    /* add acls entries at a given position */
55
56 /* TAILQ entry for acl operations */
57 struct sf_entry {
58         uint    op;
59         acl_t   acl;
60         uint    entry_number;
61         TAILQ_ENTRY(sf_entry) next;
62 };
63 static TAILQ_HEAD(, sf_entry) entrylist;
64
65 uint have_mask;
66 uint need_mask;
67 uint have_stdin;
68 uint n_flag;
69
70 static void     usage(void);
71
72 static void
73 usage(void)
74 {
75
76         fprintf(stderr, "usage: setfacl [-R [-H | -L | -P]] [-bdhkn] "
77             "[-a position entries] [-m entries] [-M file] "
78             "[-x entries] [-X file] [file ...]\n");
79         exit(1);
80 }
81
82 int
83 main(int argc, char *argv[])
84 {
85         acl_t acl;
86         acl_type_t acl_type;
87         acl_entry_t unused_entry;
88         char filename[PATH_MAX];
89         int local_error, carried_error, ch, entry_number, ret, fts_options;
90         bool h_flag, H_flag, L_flag, R_flag, follow_symlink;
91         size_t fl_count, i;
92         FTS *ftsp;
93         FTSENT *file;
94         char **files_list;
95         struct sf_entry *entry;
96         char *end;
97
98         acl_type = ACL_TYPE_ACCESS;
99         carried_error = local_error = fts_options = 0;
100         have_mask = have_stdin = n_flag = need_mask = 0;
101         h_flag = H_flag = L_flag = R_flag = false;
102
103         TAILQ_INIT(&entrylist);
104
105         while ((ch = getopt(argc, argv, "HLM:PRX:a:bdhkm:nx:")) != -1)
106                 switch(ch) {
107                 case 'H':
108                         H_flag = true;
109                         L_flag = false;
110                         break;
111                 case 'L':
112                         L_flag = true;
113                         H_flag = false;
114                         break;
115                 case 'M':
116                         entry = zmalloc(sizeof(struct sf_entry));
117                         entry->acl = get_acl_from_file(optarg);
118                         if (entry->acl == NULL)
119                                 err(1, "%s: get_acl_from_file() failed", optarg);
120                         entry->op = OP_MERGE_ACL;
121                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
122                         break;
123                 case 'P':
124                         H_flag = L_flag = false;
125                         break;
126                 case 'R':
127                         R_flag = true;
128                         break;
129                 case 'X':
130                         entry = zmalloc(sizeof(struct sf_entry));
131                         entry->acl = get_acl_from_file(optarg);
132                         entry->op = OP_REMOVE_ACL;
133                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
134                         break;
135                 case 'a':
136                         entry = zmalloc(sizeof(struct sf_entry));
137
138                         entry_number = strtol(optarg, &end, 10);
139                         if (end - optarg != (int)strlen(optarg))
140                                 errx(1, "%s: invalid entry number", optarg);
141                         if (entry_number < 0)
142                                 errx(1, "%s: entry number cannot be less than zero", optarg);
143                         entry->entry_number = entry_number;
144
145                         if (argv[optind] == NULL)
146                                 errx(1, "missing ACL");
147                         entry->acl = acl_from_text(argv[optind]);
148                         if (entry->acl == NULL)
149                                 err(1, "%s", argv[optind]);
150                         optind++;
151                         entry->op = OP_ADD_ACL;
152                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
153                         break;
154                 case 'b':
155                         entry = zmalloc(sizeof(struct sf_entry));
156                         entry->op = OP_REMOVE_EXT;
157                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
158                         break;
159                 case 'd':
160                         acl_type = ACL_TYPE_DEFAULT;
161                         break;
162                 case 'h':
163                         h_flag = 1;
164                         break;
165                 case 'k':
166                         entry = zmalloc(sizeof(struct sf_entry));
167                         entry->op = OP_REMOVE_DEF;
168                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
169                         break;
170                 case 'm':
171                         entry = zmalloc(sizeof(struct sf_entry));
172                         entry->acl = acl_from_text(optarg);
173                         if (entry->acl == NULL)
174                                 err(1, "%s", optarg);
175                         entry->op = OP_MERGE_ACL;
176                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
177                         break;
178                 case 'n':
179                         n_flag++;
180                         break;
181                 case 'x':
182                         entry = zmalloc(sizeof(struct sf_entry));
183                         entry_number = strtol(optarg, &end, 10);
184                         if (end - optarg == (int)strlen(optarg)) {
185                                 if (entry_number < 0)
186                                         errx(1, "%s: entry number cannot be less than zero", optarg);
187                                 entry->entry_number = entry_number;
188                                 entry->op = OP_REMOVE_BY_NUMBER;
189                         } else {
190                                 entry->acl = acl_from_text(optarg);
191                                 if (entry->acl == NULL)
192                                         err(1, "%s", optarg);
193                                 entry->op = OP_REMOVE_ACL;
194                         }
195                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
196                         break;
197                 default:
198                         usage();
199                         break;
200                 }
201         argc -= optind;
202         argv += optind;
203
204         if (n_flag == 0 && TAILQ_EMPTY(&entrylist))
205                 usage();
206
207         /* take list of files from stdin */
208         if (argc == 0 || strcmp(argv[0], "-") == 0) {
209                 if (have_stdin)
210                         err(1, "cannot have more than one stdin");
211                 have_stdin = 1;
212                 bzero(&filename, sizeof(filename));
213                 i = 0;
214                 /* Start with an array size sufficient for basic cases. */
215                 fl_count = 1024;
216                 files_list = zmalloc(fl_count * sizeof(char *));
217                 while (fgets(filename, (int)sizeof(filename), stdin)) {
218                         /* remove the \n */
219                         filename[strlen(filename) - 1] = '\0';
220                         files_list[i] = strdup(filename);
221                         if (files_list[i] == NULL)
222                                 err(1, "strdup() failed");
223                         /* Grow array if necessary. */
224                         if (++i == fl_count) {
225                                 fl_count <<= 1;
226                                 if (fl_count > SIZE_MAX / sizeof(char *))
227                                         errx(1, "Too many input files");
228                                 files_list = zrealloc(files_list,
229                                     fl_count * sizeof(char *));
230                         }
231                 }
232
233                 /* fts_open() requires the last array element to be NULL. */
234                 files_list[i] = NULL;
235         } else
236                 files_list = argv;
237
238         if (R_flag) {
239                 if (h_flag)
240                         errx(1, "the -R and -h options may not be "
241                             "specified together.");
242                 if (L_flag) {
243                         fts_options = FTS_LOGICAL;
244                 } else {
245                         fts_options = FTS_PHYSICAL;
246
247                         if (H_flag) {
248                                 fts_options |= FTS_COMFOLLOW;
249                         }
250                 }
251         } else if (h_flag) {
252                 fts_options = FTS_PHYSICAL;
253         } else {
254                 fts_options = FTS_LOGICAL;
255         }
256
257         /* Open all files. */
258         if ((ftsp = fts_open(files_list, fts_options | FTS_NOSTAT, 0)) == NULL)
259                 err(1, "fts_open");
260         while ((file = fts_read(ftsp)) != NULL) {
261                 switch (file->fts_info) {
262                 case FTS_D:
263                         /* Do not recurse if -R not specified. */
264                         if (!R_flag)
265                                 fts_set(ftsp, file, FTS_SKIP);
266                         break;
267                 case FTS_DP:
268                         /* Skip the second visit to a directory. */
269                         continue;
270                 case FTS_DNR:
271                 case FTS_ERR:
272                         warnx("%s: %s", file->fts_path,
273                             strerror(file->fts_errno));
274                         continue;
275                 default:
276                         break;
277                 }
278
279                 if (acl_type == ACL_TYPE_DEFAULT && file->fts_info != FTS_D) {
280                         warnx("%s: default ACL may only be set on "
281                             "a directory", file->fts_path);
282                         carried_error++;
283                         continue;
284                 }
285
286                 local_error = 0;
287
288                 follow_symlink = ((fts_options & FTS_LOGICAL) ||
289                     ((fts_options & FTS_COMFOLLOW) &&
290                     file->fts_level == FTS_ROOTLEVEL));
291
292                 if (follow_symlink)
293                         ret = pathconf(file->fts_accpath, _PC_ACL_NFS4);
294                 else
295                         ret = lpathconf(file->fts_accpath, _PC_ACL_NFS4);
296                 if (ret > 0) {
297                         if (acl_type == ACL_TYPE_DEFAULT) {
298                                 warnx("%s: there are no default entries "
299                                    "in NFSv4 ACLs", file->fts_path);
300                                 carried_error++;
301                                 continue;
302                         }
303                         acl_type = ACL_TYPE_NFS4;
304                 } else if (ret == 0) {
305                         if (acl_type == ACL_TYPE_NFS4)
306                                 acl_type = ACL_TYPE_ACCESS;
307                 } else if (ret < 0 && errno != EINVAL) {
308                         warn("%s: pathconf(..., _PC_ACL_NFS4) failed",
309                             file->fts_path);
310                 }
311
312                 if (follow_symlink)
313                         acl = acl_get_file(file->fts_accpath, acl_type);
314                 else
315                         acl = acl_get_link_np(file->fts_accpath, acl_type);
316                 if (acl == NULL) {
317                         if (follow_symlink)
318                                 warn("%s: acl_get_file() failed",
319                                     file->fts_path);
320                         else
321                                 warn("%s: acl_get_link_np() failed",
322                                     file->fts_path);
323                         carried_error++;
324                         continue;
325                 }
326
327                 /* cycle through each option */
328                 TAILQ_FOREACH(entry, &entrylist, next) {
329                         if (local_error)
330                                 continue;
331
332                         switch(entry->op) {
333                         case OP_ADD_ACL:
334                                 local_error += add_acl(entry->acl,
335                                     entry->entry_number,
336                                     &acl, file->fts_path);
337                                 break;
338                         case OP_MERGE_ACL:
339                                 local_error += merge_acl(entry->acl, &acl,
340                                     file->fts_path);
341                                 need_mask = 1;
342                                 break;
343                         case OP_REMOVE_EXT:
344                                 /*
345                                  * Don't try to call remove_ext() for empty
346                                  * default ACL.
347                                  */
348                                 if (acl_type == ACL_TYPE_DEFAULT &&
349                                     acl_get_entry(acl, ACL_FIRST_ENTRY,
350                                     &unused_entry) == 0) {
351                                         local_error += remove_default(&acl,
352                                             file->fts_path);
353                                         break;
354                                 }
355                                 remove_ext(&acl, file->fts_path);
356                                 need_mask = 0;
357                                 break;
358                         case OP_REMOVE_DEF:
359                                 if (acl_type == ACL_TYPE_NFS4) {
360                                         warnx("%s: there are no default entries in NFSv4 ACLs; "
361                                             "cannot remove", file->fts_path);
362                                         local_error++;
363                                         break;
364                                 }
365                                 if (acl_delete_def_file(file->fts_accpath) == -1) {
366                                         warn("%s: acl_delete_def_file() failed",
367                                             file->fts_path);
368                                         local_error++;
369                                 }
370                                 if (acl_type == ACL_TYPE_DEFAULT)
371                                         local_error += remove_default(&acl,
372                                             file->fts_path);
373                                 need_mask = 0;
374                                 break;
375                         case OP_REMOVE_ACL:
376                                 local_error += remove_acl(entry->acl, &acl,
377                                     file->fts_path);
378                                 need_mask = 1;
379                                 break;
380                         case OP_REMOVE_BY_NUMBER:
381                                 local_error += remove_by_number(entry->entry_number,
382                                     &acl, file->fts_path);
383                                 need_mask = 1;
384                                 break;
385                         }
386                 }
387
388                 /*
389                  * Don't try to set an empty default ACL; it will always fail.
390                  * Use acl_delete_def_file(3) instead.
391                  */
392                 if (acl_type == ACL_TYPE_DEFAULT &&
393                     acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) {
394                         if (acl_delete_def_file(file->fts_accpath) == -1) {
395                                 warn("%s: acl_delete_def_file() failed",
396                                     file->fts_path);
397                                 carried_error++;
398                         }
399                         continue;
400                 }
401
402                 /* don't bother setting the ACL if something is broken */
403                 if (local_error) {
404                         carried_error++;
405                         continue;
406                 }
407
408                 if (acl_type != ACL_TYPE_NFS4 && need_mask &&
409                     set_acl_mask(&acl, file->fts_path) == -1) {
410                         warnx("%s: failed to set ACL mask", file->fts_path);
411                         carried_error++;
412                 } else if (follow_symlink) {
413                         if (acl_set_file(file->fts_accpath, acl_type,
414                             acl) == -1) {
415                                 carried_error++;
416                                 warn("%s: acl_set_file() failed",
417                                     file->fts_path);
418                         }
419                 } else {
420                         if (acl_set_link_np(file->fts_accpath, acl_type,
421                             acl) == -1) {
422                                 carried_error++;
423                                 warn("%s: acl_set_link_np() failed",
424                                     file->fts_path);
425                         }
426                 }
427
428                 acl_free(acl);
429         }
430
431         return (carried_error);
432 }