]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - bin/setfacl/setfacl.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[FreeBSD/stable/10.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 <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 #include "setfacl.h"
44
45 /* file operations */
46 #define OP_MERGE_ACL            0x00    /* merge acl's (-mM) */
47 #define OP_REMOVE_DEF           0x01    /* remove default acl's (-k) */
48 #define OP_REMOVE_EXT           0x02    /* remove extended acl's (-b) */
49 #define OP_REMOVE_ACL           0x03    /* remove acl's (-xX) */
50 #define OP_REMOVE_BY_NUMBER     0x04    /* remove acl's (-xX) by acl entry number */
51 #define OP_ADD_ACL              0x05    /* add acls entries at a given position */
52
53 /* TAILQ entry for acl operations */
54 struct sf_entry {
55         uint    op;
56         acl_t   acl;
57         uint    entry_number;
58         TAILQ_ENTRY(sf_entry) next;
59 };
60 static TAILQ_HEAD(, sf_entry) entrylist;
61
62 /* TAILQ entry for files */
63 struct sf_file {
64         const char *filename;
65         TAILQ_ENTRY(sf_file) next;
66 };
67 static TAILQ_HEAD(, sf_file) filelist;
68
69 uint have_mask;
70 uint need_mask;
71 uint have_stdin;
72 uint n_flag;
73
74 static void     add_filename(const char *filename);
75 static void     usage(void);
76
77 static void
78 add_filename(const char *filename)
79 {
80         struct sf_file *file;
81
82         if (strlen(filename) > PATH_MAX - 1) {
83                 warn("illegal filename");
84                 return;
85         }
86         file = zmalloc(sizeof(struct sf_file));
87         file->filename = filename;
88         TAILQ_INSERT_TAIL(&filelist, file, next);
89 }
90
91 static void
92 usage(void)
93 {
94
95         fprintf(stderr, "usage: setfacl [-bdhkn] [-a position entries] "
96             "[-m entries] [-M file] [-x entries] [-X file] [file ...]\n");
97         exit(1);
98 }
99
100 int
101 main(int argc, char *argv[])
102 {
103         acl_t acl;
104         acl_type_t acl_type;
105         acl_entry_t unused_entry;
106         char filename[PATH_MAX];
107         int local_error, carried_error, ch, i, entry_number, ret;
108         int h_flag;
109         struct sf_file *file;
110         struct sf_entry *entry;
111         const char *fn_dup;
112         char *end;
113         struct stat sb;
114
115         acl_type = ACL_TYPE_ACCESS;
116         carried_error = local_error = 0;
117         h_flag = have_mask = have_stdin = n_flag = need_mask = 0;
118
119         TAILQ_INIT(&entrylist);
120         TAILQ_INIT(&filelist);
121
122         while ((ch = getopt(argc, argv, "M:X:a:bdhkm:nx:")) != -1)
123                 switch(ch) {
124                 case 'M':
125                         entry = zmalloc(sizeof(struct sf_entry));
126                         entry->acl = get_acl_from_file(optarg);
127                         if (entry->acl == NULL)
128                                 err(1, "%s: get_acl_from_file() failed", optarg);
129                         entry->op = OP_MERGE_ACL;
130                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
131                         break;
132                 case 'X':
133                         entry = zmalloc(sizeof(struct sf_entry));
134                         entry->acl = get_acl_from_file(optarg);
135                         entry->op = OP_REMOVE_ACL;
136                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
137                         break;
138                 case 'a':
139                         entry = zmalloc(sizeof(struct sf_entry));
140
141                         entry_number = strtol(optarg, &end, 10);
142                         if (end - optarg != (int)strlen(optarg))
143                                 errx(1, "%s: invalid entry number", optarg);
144                         if (entry_number < 0)
145                                 errx(1, "%s: entry number cannot be less than zero", optarg);
146                         entry->entry_number = entry_number;
147
148                         if (argv[optind] == NULL)
149                                 errx(1, "missing ACL");
150                         entry->acl = acl_from_text(argv[optind]);
151                         if (entry->acl == NULL)
152                                 err(1, "%s", argv[optind]);
153                         optind++;
154                         entry->op = OP_ADD_ACL;
155                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
156                         break;
157                 case 'b':
158                         entry = zmalloc(sizeof(struct sf_entry));
159                         entry->op = OP_REMOVE_EXT;
160                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
161                         break;
162                 case 'd':
163                         acl_type = ACL_TYPE_DEFAULT;
164                         break;
165                 case 'h':
166                         h_flag = 1;
167                         break;
168                 case 'k':
169                         entry = zmalloc(sizeof(struct sf_entry));
170                         entry->op = OP_REMOVE_DEF;
171                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
172                         break;
173                 case 'm':
174                         entry = zmalloc(sizeof(struct sf_entry));
175                         entry->acl = acl_from_text(optarg);
176                         if (entry->acl == NULL)
177                                 err(1, "%s", optarg);
178                         entry->op = OP_MERGE_ACL;
179                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
180                         break;
181                 case 'n':
182                         n_flag++;
183                         break;
184                 case 'x':
185                         entry = zmalloc(sizeof(struct sf_entry));
186                         entry_number = strtol(optarg, &end, 10);
187                         if (end - optarg == (int)strlen(optarg)) {
188                                 if (entry_number < 0)
189                                         errx(1, "%s: entry number cannot be less than zero", optarg);
190                                 entry->entry_number = entry_number;
191                                 entry->op = OP_REMOVE_BY_NUMBER;
192                         } else {
193                                 entry->acl = acl_from_text(optarg);
194                                 if (entry->acl == NULL)
195                                         err(1, "%s", optarg);
196                                 entry->op = OP_REMOVE_ACL;
197                         }
198                         TAILQ_INSERT_TAIL(&entrylist, entry, next);
199                         break;
200                 default:
201                         usage();
202                         break;
203                 }
204         argc -= optind;
205         argv += optind;
206
207         if (n_flag == 0 && TAILQ_EMPTY(&entrylist))
208                 usage();
209
210         /* take list of files from stdin */
211         if (argc == 0 || strcmp(argv[0], "-") == 0) {
212                 if (have_stdin)
213                         err(1, "cannot have more than one stdin");
214                 have_stdin = 1;
215                 bzero(&filename, sizeof(filename));
216                 while (fgets(filename, (int)sizeof(filename), stdin)) {
217                         /* remove the \n */
218                         filename[strlen(filename) - 1] = '\0';
219                         fn_dup = strdup(filename);
220                         if (fn_dup == NULL)
221                                 err(1, "strdup() failed");
222                         add_filename(fn_dup);
223                 }
224         } else
225                 for (i = 0; i < argc; i++)
226                         add_filename(argv[i]);
227
228         /* cycle through each file */
229         TAILQ_FOREACH(file, &filelist, next) {
230                 local_error = 0;
231
232                 if (stat(file->filename, &sb) == -1) {
233                         warn("%s: stat() failed", file->filename);
234                         carried_error++;
235                         continue;
236                 }
237
238                 if (acl_type == ACL_TYPE_DEFAULT && S_ISDIR(sb.st_mode) == 0) {
239                         warnx("%s: default ACL may only be set on a directory",
240                             file->filename);
241                         carried_error++;
242                         continue;
243                 }
244
245                 if (h_flag)
246                         ret = lpathconf(file->filename, _PC_ACL_NFS4);
247                 else
248                         ret = pathconf(file->filename, _PC_ACL_NFS4);
249                 if (ret > 0) {
250                         if (acl_type == ACL_TYPE_DEFAULT) {
251                                 warnx("%s: there are no default entries "
252                                    "in NFSv4 ACLs", file->filename);
253                                 carried_error++;
254                                 continue;
255                         }
256                         acl_type = ACL_TYPE_NFS4;
257                 } else if (ret == 0) {
258                         if (acl_type == ACL_TYPE_NFS4)
259                                 acl_type = ACL_TYPE_ACCESS;
260                 } else if (ret < 0 && errno != EINVAL) {
261                         warn("%s: pathconf(..., _PC_ACL_NFS4) failed",
262                             file->filename);
263                 }
264
265                 if (h_flag)
266                         acl = acl_get_link_np(file->filename, acl_type);
267                 else
268                         acl = acl_get_file(file->filename, acl_type);
269                 if (acl == NULL) {
270                         if (h_flag)
271                                 warn("%s: acl_get_link_np() failed",
272                                     file->filename);
273                         else
274                                 warn("%s: acl_get_file() failed",
275                                     file->filename);
276                         carried_error++;
277                         continue;
278                 }
279
280                 /* cycle through each option */
281                 TAILQ_FOREACH(entry, &entrylist, next) {
282                         if (local_error)
283                                 continue;
284
285                         switch(entry->op) {
286                         case OP_ADD_ACL:
287                                 local_error += add_acl(entry->acl,
288                                     entry->entry_number, &acl, file->filename);
289                                 break;
290                         case OP_MERGE_ACL:
291                                 local_error += merge_acl(entry->acl, &acl,
292                                     file->filename);
293                                 need_mask = 1;
294                                 break;
295                         case OP_REMOVE_EXT:
296                                 /*
297                                  * Don't try to call remove_ext() for empty
298                                  * default ACL.
299                                  */
300                                 if (acl_type == ACL_TYPE_DEFAULT &&
301                                     acl_get_entry(acl, ACL_FIRST_ENTRY,
302                                     &unused_entry) == 0) {
303                                         local_error += remove_default(&acl,
304                                             file->filename);
305                                         break;
306                                 }
307                                 remove_ext(&acl, file->filename);
308                                 need_mask = 0;
309                                 break;
310                         case OP_REMOVE_DEF:
311                                 if (acl_type == ACL_TYPE_NFS4) {
312                                         warnx("%s: there are no default entries in NFSv4 ACLs; "
313                                             "cannot remove", file->filename);
314                                         local_error++;
315                                         break;
316                                 }
317                                 if (acl_delete_def_file(file->filename) == -1) {
318                                         warn("%s: acl_delete_def_file() failed",
319                                             file->filename);
320                                         local_error++;
321                                 }
322                                 if (acl_type == ACL_TYPE_DEFAULT)
323                                         local_error += remove_default(&acl,
324                                             file->filename);
325                                 need_mask = 0;
326                                 break;
327                         case OP_REMOVE_ACL:
328                                 local_error += remove_acl(entry->acl, &acl,
329                                     file->filename);
330                                 need_mask = 1;
331                                 break;
332                         case OP_REMOVE_BY_NUMBER:
333                                 local_error += remove_by_number(entry->entry_number,
334                                     &acl, file->filename);
335                                 need_mask = 1;
336                                 break;
337                         }
338                 }
339
340                 /*
341                  * Don't try to set an empty default ACL; it will always fail.
342                  * Use acl_delete_def_file(3) instead.
343                  */
344                 if (acl_type == ACL_TYPE_DEFAULT &&
345                     acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) {
346                         if (acl_delete_def_file(file->filename) == -1) {
347                                 warn("%s: acl_delete_def_file() failed",
348                                     file->filename);
349                                 carried_error++;
350                         }
351                         continue;
352                 }
353
354                 /* don't bother setting the ACL if something is broken */
355                 if (local_error) {
356                         carried_error++;
357                         continue;
358                 }
359
360                 if (acl_type != ACL_TYPE_NFS4 && need_mask &&
361                     set_acl_mask(&acl, file->filename) == -1) {
362                         warnx("%s: failed to set ACL mask", file->filename);
363                         carried_error++;
364                 } else if (h_flag) {
365                         if (acl_set_link_np(file->filename, acl_type,
366                             acl) == -1) {
367                                 carried_error++;
368                                 warn("%s: acl_set_link_np() failed",
369                                     file->filename);
370                         }
371                 } else {
372                         if (acl_set_file(file->filename, acl_type,
373                             acl) == -1) {
374                                 carried_error++;
375                                 warn("%s: acl_set_file() failed",
376                                     file->filename);
377                         }
378                 }
379
380                 acl_free(acl);
381         }
382
383         return (carried_error);
384 }