Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/bin/setfacl/setfacl.c
39475 views
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/param.h>
28
#include <sys/acl.h>
29
#include <sys/queue.h>
30
31
#include <err.h>
32
#include <errno.h>
33
#include <fts.h>
34
#include <stdbool.h>
35
#include <stdint.h>
36
#include <stdio.h>
37
#include <stdlib.h>
38
#include <string.h>
39
#include <unistd.h>
40
41
#include "setfacl.h"
42
43
/* file operations */
44
#define OP_MERGE_ACL 0x00 /* merge acl's (-mM) */
45
#define OP_REMOVE_DEF 0x01 /* remove default acl's (-k) */
46
#define OP_REMOVE_EXT 0x02 /* remove extended acl's (-b) */
47
#define OP_REMOVE_ACL 0x03 /* remove acl's (-xX) */
48
#define OP_REMOVE_BY_NUMBER 0x04 /* remove acl's (-xX) by acl entry number */
49
#define OP_ADD_ACL 0x05 /* add acls entries at a given position */
50
51
/* TAILQ entry for acl operations */
52
struct sf_entry {
53
uint op;
54
acl_t acl;
55
uint entry_number;
56
TAILQ_ENTRY(sf_entry) next;
57
};
58
static TAILQ_HEAD(, sf_entry) entrylist;
59
60
bool have_mask;
61
bool have_stdin;
62
bool n_flag;
63
static bool h_flag;
64
static bool H_flag;
65
static bool L_flag;
66
static bool R_flag;
67
static bool need_mask;
68
static acl_type_t acl_type = ACL_TYPE_ACCESS;
69
70
static int handle_file(FTS *ftsp, FTSENT *file);
71
static acl_t clear_inheritance_flags(acl_t acl);
72
static char **stdin_files(void);
73
static void usage(void);
74
75
static void
76
usage(void)
77
{
78
79
fprintf(stderr, "usage: setfacl [-R [-H | -L | -P]] [-bdhkn] "
80
"[-a position entries] [-m entries] [-M file] "
81
"[-x entries] [-X file] [file ...]\n");
82
exit(1);
83
}
84
85
static char **
86
stdin_files(void)
87
{
88
char **files_list;
89
char filename[PATH_MAX];
90
size_t fl_count, i;
91
92
if (have_stdin)
93
err(1, "cannot have more than one stdin");
94
95
i = 0;
96
have_stdin = true;
97
bzero(&filename, sizeof(filename));
98
/* Start with an array size sufficient for basic cases. */
99
fl_count = 1024;
100
files_list = zmalloc(fl_count * sizeof(char *));
101
while (fgets(filename, (int)sizeof(filename), stdin)) {
102
/* remove the \n */
103
filename[strlen(filename) - 1] = '\0';
104
files_list[i] = strdup(filename);
105
if (files_list[i] == NULL)
106
err(1, "strdup() failed");
107
/* Grow array if necessary. */
108
if (++i == fl_count) {
109
fl_count <<= 1;
110
if (fl_count > SIZE_MAX / sizeof(char *))
111
errx(1, "Too many input files");
112
files_list = zrealloc(files_list,
113
fl_count * sizeof(char *));
114
}
115
}
116
117
/* fts_open() requires the last array element to be NULL. */
118
files_list[i] = NULL;
119
120
return (files_list);
121
}
122
123
/*
124
* Remove any inheritance flags from NFSv4 ACLs when running in recursive
125
* mode. This is to avoid files being assigned identical ACLs to their
126
* parent directory while also being set to inherit them.
127
*
128
* The acl argument is assumed to be valid.
129
*/
130
static acl_t
131
clear_inheritance_flags(acl_t acl)
132
{
133
acl_t nacl;
134
acl_entry_t acl_entry;
135
acl_flagset_t acl_flagset;
136
int acl_brand, entry_id;
137
138
(void)acl_get_brand_np(acl, &acl_brand);
139
if (acl_brand != ACL_BRAND_NFS4)
140
return (acl);
141
142
nacl = acl_dup(acl);
143
if (nacl == NULL) {
144
warn("acl_dup() failed");
145
return (acl);
146
}
147
148
entry_id = ACL_FIRST_ENTRY;
149
while (acl_get_entry(nacl, entry_id, &acl_entry) == 1) {
150
entry_id = ACL_NEXT_ENTRY;
151
if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) {
152
warn("acl_get_flagset_np() failed");
153
continue;
154
}
155
if (acl_get_flag_np(acl_flagset, ACL_ENTRY_INHERIT_ONLY) == 1) {
156
if (acl_delete_entry(nacl, acl_entry) != 0)
157
warn("acl_delete_entry() failed");
158
continue;
159
}
160
if (acl_delete_flag_np(acl_flagset,
161
ACL_ENTRY_FILE_INHERIT |
162
ACL_ENTRY_DIRECTORY_INHERIT |
163
ACL_ENTRY_NO_PROPAGATE_INHERIT) != 0)
164
warn("acl_delete_flag_np() failed");
165
}
166
167
return (nacl);
168
}
169
170
static int
171
handle_file(FTS *ftsp, FTSENT *file)
172
{
173
acl_t acl, nacl;
174
acl_entry_t unused_entry;
175
int local_error, ret;
176
struct sf_entry *entry;
177
bool follow_symlink;
178
179
local_error = 0;
180
switch (file->fts_info) {
181
case FTS_D:
182
/* Do not recurse if -R not specified. */
183
if (!R_flag)
184
fts_set(ftsp, file, FTS_SKIP);
185
break;
186
case FTS_DP:
187
/* Skip the second visit to a directory. */
188
return (0);
189
case FTS_DNR:
190
case FTS_ERR:
191
warnx("%s: %s", file->fts_path, strerror(file->fts_errno));
192
return (0);
193
default:
194
break;
195
}
196
197
if (acl_type == ACL_TYPE_DEFAULT && file->fts_info != FTS_D) {
198
warnx("%s: default ACL may only be set on a directory",
199
file->fts_path);
200
return (1);
201
}
202
203
follow_symlink = (!R_flag && !h_flag) || (R_flag && L_flag) ||
204
(R_flag && H_flag && file->fts_level == FTS_ROOTLEVEL);
205
206
if (follow_symlink)
207
ret = pathconf(file->fts_accpath, _PC_ACL_NFS4);
208
else
209
ret = lpathconf(file->fts_accpath, _PC_ACL_NFS4);
210
if (ret > 0) {
211
if (acl_type == ACL_TYPE_DEFAULT) {
212
warnx("%s: there are no default entries in NFSv4 ACLs",
213
file->fts_path);
214
return (1);
215
}
216
acl_type = ACL_TYPE_NFS4;
217
} else if (ret == 0) {
218
if (acl_type == ACL_TYPE_NFS4)
219
acl_type = ACL_TYPE_ACCESS;
220
} else if (ret < 0 && errno != EINVAL && errno != ENOENT) {
221
warn("%s: pathconf(_PC_ACL_NFS4) failed",
222
file->fts_path);
223
}
224
225
if (follow_symlink)
226
acl = acl_get_file(file->fts_accpath, acl_type);
227
else
228
acl = acl_get_link_np(file->fts_accpath, acl_type);
229
if (acl == NULL) {
230
if (follow_symlink)
231
warn("%s: acl_get_file() failed", file->fts_path);
232
else
233
warn("%s: acl_get_link_np() failed", file->fts_path);
234
return (1);
235
}
236
237
/* Cycle through each option. */
238
TAILQ_FOREACH(entry, &entrylist, next) {
239
nacl = entry->acl;
240
switch (entry->op) {
241
case OP_ADD_ACL:
242
if (R_flag && file->fts_info != FTS_D &&
243
acl_type == ACL_TYPE_NFS4)
244
nacl = clear_inheritance_flags(nacl);
245
local_error += add_acl(nacl, entry->entry_number, &acl,
246
file->fts_path);
247
break;
248
case OP_MERGE_ACL:
249
if (R_flag && file->fts_info != FTS_D &&
250
acl_type == ACL_TYPE_NFS4)
251
nacl = clear_inheritance_flags(nacl);
252
local_error += merge_acl(nacl, &acl, file->fts_path);
253
need_mask = true;
254
break;
255
case OP_REMOVE_EXT:
256
/*
257
* Don't try to call remove_ext() for empty
258
* default ACL.
259
*/
260
if (acl_type == ACL_TYPE_DEFAULT &&
261
acl_get_entry(acl, ACL_FIRST_ENTRY,
262
&unused_entry) == 0) {
263
local_error += remove_default(&acl,
264
file->fts_path);
265
break;
266
}
267
remove_ext(&acl, file->fts_path);
268
need_mask = false;
269
break;
270
case OP_REMOVE_DEF:
271
if (acl_type == ACL_TYPE_NFS4) {
272
warnx("%s: there are no default entries in "
273
"NFSv4 ACLs; cannot remove",
274
file->fts_path);
275
local_error++;
276
break;
277
}
278
if (acl_delete_def_file(file->fts_accpath) == -1) {
279
warn("%s: acl_delete_def_file() failed",
280
file->fts_path);
281
local_error++;
282
}
283
if (acl_type == ACL_TYPE_DEFAULT)
284
local_error += remove_default(&acl,
285
file->fts_path);
286
need_mask = false;
287
break;
288
case OP_REMOVE_ACL:
289
local_error += remove_acl(nacl, &acl, file->fts_path);
290
need_mask = true;
291
break;
292
case OP_REMOVE_BY_NUMBER:
293
local_error += remove_by_number(entry->entry_number,
294
&acl, file->fts_path);
295
need_mask = true;
296
break;
297
}
298
299
if (nacl != entry->acl) {
300
acl_free(nacl);
301
nacl = NULL;
302
}
303
if (local_error)
304
break;
305
}
306
307
ret = 0;
308
309
/*
310
* Don't try to set an empty default ACL; it will always fail.
311
* Use acl_delete_def_file(3) instead.
312
*/
313
if (acl_type == ACL_TYPE_DEFAULT &&
314
acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) {
315
if (acl_delete_def_file(file->fts_accpath) == -1) {
316
warn("%s: acl_delete_def_file() failed",
317
file->fts_path);
318
ret = 1;
319
}
320
goto out;
321
}
322
323
/* Don't bother setting the ACL if something is broken. */
324
if (local_error) {
325
ret = 1;
326
} else if (acl_type != ACL_TYPE_NFS4 && need_mask &&
327
set_acl_mask(&acl, file->fts_path) == -1) {
328
warnx("%s: failed to set ACL mask", file->fts_path);
329
ret = 1;
330
} else if (follow_symlink) {
331
if (acl_set_file(file->fts_accpath, acl_type, acl) == -1) {
332
warn("%s: acl_set_file() failed", file->fts_path);
333
ret = 1;
334
}
335
} else {
336
if (acl_set_link_np(file->fts_accpath, acl_type, acl) == -1) {
337
warn("%s: acl_set_link_np() failed", file->fts_path);
338
ret = 1;
339
}
340
}
341
342
out:
343
acl_free(acl);
344
return (ret);
345
}
346
347
int
348
main(int argc, char *argv[])
349
{
350
int carried_error, ch, entry_number, fts_options;
351
FTS *ftsp;
352
FTSENT *file;
353
char **files_list;
354
struct sf_entry *entry;
355
char *end;
356
357
acl_type = ACL_TYPE_ACCESS;
358
carried_error = fts_options = 0;
359
have_mask = have_stdin = n_flag = false;
360
361
TAILQ_INIT(&entrylist);
362
363
while ((ch = getopt(argc, argv, "HLM:PRX:a:bdhkm:nx:")) != -1)
364
switch(ch) {
365
case 'H':
366
H_flag = true;
367
L_flag = false;
368
break;
369
case 'L':
370
L_flag = true;
371
H_flag = false;
372
break;
373
case 'M':
374
entry = zmalloc(sizeof(struct sf_entry));
375
entry->acl = get_acl_from_file(optarg);
376
if (entry->acl == NULL)
377
err(1, "%s: get_acl_from_file() failed",
378
optarg);
379
entry->op = OP_MERGE_ACL;
380
TAILQ_INSERT_TAIL(&entrylist, entry, next);
381
break;
382
case 'P':
383
H_flag = L_flag = false;
384
break;
385
case 'R':
386
R_flag = true;
387
break;
388
case 'X':
389
entry = zmalloc(sizeof(struct sf_entry));
390
entry->acl = get_acl_from_file(optarg);
391
entry->op = OP_REMOVE_ACL;
392
TAILQ_INSERT_TAIL(&entrylist, entry, next);
393
break;
394
case 'a':
395
entry = zmalloc(sizeof(struct sf_entry));
396
397
entry_number = strtol(optarg, &end, 10);
398
if (end - optarg != (int)strlen(optarg))
399
errx(1, "%s: invalid entry number", optarg);
400
if (entry_number < 0)
401
errx(1,
402
"%s: entry number cannot be less than zero",
403
optarg);
404
entry->entry_number = entry_number;
405
406
if (argv[optind] == NULL)
407
errx(1, "missing ACL");
408
entry->acl = acl_from_text(argv[optind]);
409
if (entry->acl == NULL)
410
err(1, "%s", argv[optind]);
411
optind++;
412
entry->op = OP_ADD_ACL;
413
TAILQ_INSERT_TAIL(&entrylist, entry, next);
414
break;
415
case 'b':
416
entry = zmalloc(sizeof(struct sf_entry));
417
entry->op = OP_REMOVE_EXT;
418
TAILQ_INSERT_TAIL(&entrylist, entry, next);
419
break;
420
case 'd':
421
acl_type = ACL_TYPE_DEFAULT;
422
break;
423
case 'h':
424
h_flag = 1;
425
break;
426
case 'k':
427
entry = zmalloc(sizeof(struct sf_entry));
428
entry->op = OP_REMOVE_DEF;
429
TAILQ_INSERT_TAIL(&entrylist, entry, next);
430
break;
431
case 'm':
432
entry = zmalloc(sizeof(struct sf_entry));
433
entry->acl = acl_from_text(optarg);
434
if (entry->acl == NULL)
435
err(1, "%s", optarg);
436
entry->op = OP_MERGE_ACL;
437
TAILQ_INSERT_TAIL(&entrylist, entry, next);
438
break;
439
case 'n':
440
n_flag = true;
441
break;
442
case 'x':
443
entry = zmalloc(sizeof(struct sf_entry));
444
entry_number = strtol(optarg, &end, 10);
445
if (end - optarg == (int)strlen(optarg)) {
446
if (entry_number < 0)
447
errx(1,
448
"%s: entry number cannot be less than zero",
449
optarg);
450
entry->entry_number = entry_number;
451
entry->op = OP_REMOVE_BY_NUMBER;
452
} else {
453
entry->acl = acl_from_text(optarg);
454
if (entry->acl == NULL)
455
err(1, "%s", optarg);
456
entry->op = OP_REMOVE_ACL;
457
}
458
TAILQ_INSERT_TAIL(&entrylist, entry, next);
459
break;
460
default:
461
usage();
462
}
463
argc -= optind;
464
argv += optind;
465
466
if (!n_flag && TAILQ_EMPTY(&entrylist))
467
usage();
468
469
/* Take list of files from stdin. */
470
if (argc == 0 || strcmp(argv[0], "-") == 0) {
471
files_list = stdin_files();
472
} else
473
files_list = argv;
474
475
if (R_flag) {
476
if (h_flag)
477
errx(1, "the -R and -h options may not be "
478
"specified together.");
479
if (L_flag) {
480
fts_options = FTS_LOGICAL;
481
} else {
482
fts_options = FTS_PHYSICAL;
483
484
if (H_flag) {
485
fts_options |= FTS_COMFOLLOW;
486
}
487
}
488
} else if (h_flag) {
489
fts_options = FTS_PHYSICAL;
490
} else {
491
fts_options = FTS_LOGICAL;
492
}
493
494
/* Open all files. */
495
if ((ftsp = fts_open(files_list, fts_options | FTS_NOSTAT, 0)) == NULL)
496
err(1, "fts_open");
497
while (errno = 0, (file = fts_read(ftsp)) != NULL)
498
carried_error += handle_file(ftsp, file);
499
if (errno != 0)
500
err(1, "fts_read");
501
502
return (carried_error);
503
}
504
505