]> gitweb.michael.orlitzky.com - apply-default-acl.git/blob - src/apply-default-acl.c
5fea3a30a6efcb9c7359d5f9a19d25de7ead83e5
[apply-default-acl.git] / src / apply-default-acl.c
1 /**
2 * @file apply-default-acl.c
3 *
4 * @brief The entire implementation.
5 *
6 */
7
8 /* On Linux, ftw.h needs this special voodoo to work. */
9 #define _XOPEN_SOURCE 500
10 #define _GNU_SOURCE
11
12 #include <errno.h>
13 #include <ftw.h> /* nftw() et al. */
14 #include <getopt.h>
15 #include <libgen.h> /* dirname() */
16 #include <limits.h> /* PATH_MAX */
17 #include <stdbool.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23
24 /* ACLs */
25 #include <acl/libacl.h> /* acl_get_perm, not portable */
26 #include <sys/types.h>
27 #include <sys/acl.h>
28
29 /* Most of the libacl functions return 1 for success, 0 for failure,
30 and -1 on error */
31 #define ACL_ERROR -1
32 #define ACL_FAILURE 0
33 #define ACL_SUCCESS 1
34
35
36
37 /**
38 * @brief Get the mode bits from the given path.
39 *
40 * @param path
41 * The path (file or directory) whose mode we want.
42 *
43 * @return A mode_t (st_mode) structure containing the mode bits.
44 * See sys/stat.h for details.
45 */
46 mode_t get_mode(const char* path) {
47 if (path == NULL) {
48 errno = ENOENT;
49 return -1;
50 }
51
52 struct stat s;
53 int result = stat(path, &s);
54
55 if (result == 0) {
56 return s.st_mode;
57 }
58 else {
59 /* errno will be set already by stat() */
60 return result;
61 }
62 }
63
64
65
66 /**
67 * @brief Determine whether or not the given path is a regular file.
68 *
69 * @param path
70 * The path to test.
71 *
72 * @return true if @c path is a regular file, false otherwise.
73 */
74 bool is_regular_file(const char* path) {
75 if (path == NULL) {
76 return false;
77 }
78
79 struct stat s;
80 int result = stat(path, &s);
81 if (result == 0) {
82 return S_ISREG(s.st_mode);
83 }
84 else {
85 return false;
86 }
87 }
88
89
90
91 /**
92 * @brief Determine whether or not the given path is a directory.
93 *
94 * @param path
95 * The path to test.
96 *
97 * @return true if @c path is a directory, false otherwise.
98 */
99 bool is_directory(const char* path) {
100 if (path == NULL) {
101 return false;
102 }
103
104 struct stat s;
105 int result = stat(path, &s);
106 if (result == 0) {
107 return S_ISDIR(s.st_mode);
108 }
109 else {
110 return false;
111 }
112 }
113
114
115
116 /**
117 * @brief Update (or create) an entry in an @b minimal ACL.
118 *
119 * This function will not work if @c aclp contains extended
120 * entries. This is fine for our purposes, since we call @c wipe_acls
121 * on each path before applying the default to it.
122 *
123 * The assumption that there are no extended entries makes things much
124 * simpler. For example, we only have to update the @c ACL_USER_OBJ,
125 * @c ACL_GROUP_OBJ, and @c ACL_OTHER entries -- all others can simply
126 * be created anew. This means we don't have to fool around comparing
127 * named-user/group entries.
128 *
129 * @param aclp
130 * A pointer to the acl_t structure whose entry we want to modify.
131 *
132 * @param entry
133 * The new entry. If @c entry contains a user/group/other entry, we
134 * update the existing one. Otherwise we create a new entry.
135 *
136 * @return If there is an unexpected library error, @c ACL_ERROR is
137 * returned. Otherwise, @c ACL_SUCCESS.
138 *
139 */
140 int acl_set_entry(acl_t* aclp,
141 acl_entry_t entry) {
142
143 acl_tag_t entry_tag;
144 int gt_result = acl_get_tag_type(entry, &entry_tag);
145 if (gt_result == ACL_ERROR) {
146 perror("acl_set_entry (acl_get_tag_type)");
147 return ACL_ERROR;
148 }
149
150 acl_permset_t entry_permset;
151 int ps_result = acl_get_permset(entry, &entry_permset);
152 if (ps_result == ACL_ERROR) {
153 perror("acl_set_entry (acl_get_permset)");
154 return ACL_ERROR;
155 }
156
157 acl_entry_t existing_entry;
158 /* Loop through the given ACL looking for matching entries. */
159 int result = acl_get_entry(*aclp, ACL_FIRST_ENTRY, &existing_entry);
160
161 while (result == ACL_SUCCESS) {
162 acl_tag_t existing_tag = ACL_UNDEFINED_TAG;
163 int tag_result = acl_get_tag_type(existing_entry, &existing_tag);
164
165 if (tag_result == ACL_ERROR) {
166 perror("set_acl_tag_permset (acl_get_tag_type)");
167 return ACL_ERROR;
168 }
169
170 if (existing_tag == entry_tag) {
171 if (entry_tag == ACL_USER_OBJ ||
172 entry_tag == ACL_GROUP_OBJ ||
173 entry_tag == ACL_OTHER) {
174 /* Only update for these three since all other tags will have
175 been wiped. These three are guaranteed to exist, so if we
176 match one of them, we're allowed to return ACL_SUCCESS
177 below and bypass the rest of the function. */
178 acl_permset_t existing_permset;
179 int gep_result = acl_get_permset(existing_entry, &existing_permset);
180 if (gep_result == ACL_ERROR) {
181 perror("acl_set_entry (acl_get_permset)");
182 return ACL_ERROR;
183 }
184
185 int s_result = acl_set_permset(existing_entry, entry_permset);
186 if (s_result == ACL_ERROR) {
187 perror("acl_set_entry (acl_set_permset)");
188 return ACL_ERROR;
189 }
190
191 return ACL_SUCCESS;
192 }
193
194 }
195
196 result = acl_get_entry(*aclp, ACL_NEXT_ENTRY, &existing_entry);
197 }
198
199 /* This catches both the initial acl_get_entry and the ones at the
200 end of the loop. */
201 if (result == ACL_ERROR) {
202 perror("acl_set_entry (acl_get_entry)");
203 return ACL_ERROR;
204 }
205
206 /* If we've made it this far, we need to add a new entry to the
207 ACL. */
208 acl_entry_t new_entry;
209
210 /* We allocate memory here that we should release! */
211 int c_result = acl_create_entry(aclp, &new_entry);
212 if (c_result == ACL_ERROR) {
213 perror("acl_set_entry (acl_create_entry)");
214 return ACL_ERROR;
215 }
216
217 int st_result = acl_set_tag_type(new_entry, entry_tag);
218 if (st_result == ACL_ERROR) {
219 perror("acl_set_entry (acl_set_tag_type)");
220 return ACL_ERROR;
221 }
222
223 int s_result = acl_set_permset(new_entry, entry_permset);
224 if (s_result == ACL_ERROR) {
225 perror("acl_set_entry (acl_set_permset)");
226 return ACL_ERROR;
227 }
228
229 if (entry_tag == ACL_USER || entry_tag == ACL_GROUP) {
230 /* We need to set the qualifier too. */
231 void* entry_qual = acl_get_qualifier(entry);
232 if (entry_qual == (void*)NULL) {
233 perror("acl_set_entry (acl_get_qualifier)");
234 return ACL_ERROR;
235 }
236
237 int sq_result = acl_set_qualifier(new_entry, entry_qual);
238 if (sq_result == ACL_ERROR) {
239 perror("acl_set_entry (acl_set_qualifier)");
240 return ACL_ERROR;
241 }
242 }
243
244 return ACL_SUCCESS;
245 }
246
247
248
249 /**
250 * @brief Determine the number of entries in the given ACL.
251 *
252 * @param acl
253 * A pointer to an @c acl_t structure.
254 *
255 * @return Either the non-negative number of entries in @c acl, or
256 * @c ACL_ERROR on error.
257 */
258 int acl_entry_count(acl_t* acl) {
259
260 acl_entry_t entry;
261 int entry_count = 0;
262 int result = acl_get_entry(*acl, ACL_FIRST_ENTRY, &entry);
263
264 while (result == ACL_SUCCESS) {
265 entry_count++;
266 result = acl_get_entry(*acl, ACL_NEXT_ENTRY, &entry);
267 }
268
269 if (result == ACL_ERROR) {
270 perror("acl_entry_count (acl_get_entry)");
271 return ACL_ERROR;
272 }
273
274 return entry_count;
275 }
276
277
278
279 /**
280 * @brief Determine whether or not the given ACL is minimal.
281 *
282 * An ACL is minimal if it has fewer than four entries.
283 *
284 * @param acl
285 * A pointer to an acl_t structure.
286 *
287 * @return
288 * - @c ACL_SUCCESS - @c acl is minimal
289 * - @c ACL_FAILURE - @c acl is not minimal
290 * - @c ACL_ERROR - Unexpected library error
291 */
292 int acl_is_minimal(acl_t* acl) {
293
294 int ec = acl_entry_count(acl);
295
296 if (ec == ACL_ERROR) {
297 perror("acl_is_minimal (acl_entry_count)");
298 return ACL_ERROR;
299 }
300
301 if (ec < 4) {
302 return ACL_SUCCESS;
303 }
304 else {
305 return ACL_FAILURE;
306 }
307 }
308
309
310
311 /**
312 * @brief Determine whether the given path has an ACL whose mask
313 * denies execute.
314 *
315 * @param path
316 * The path to check.
317 *
318 * @return
319 * - @c ACL_SUCCESS - @c path has a mask which denies execute.
320 * - @c ACL_FAILURE - The ACL for @c path does not deny execute,
321 * or @c path has no extended ACL at all.
322 * - @c ACL_ERROR - Unexpected library error.
323 */
324 int acl_execute_masked(const char* path) {
325
326 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
327
328 if (acl == (acl_t)NULL) {
329 perror("acl_execute_masked (acl_get_file)");
330 return ACL_ERROR;
331 }
332
333 /* Our return value. */
334 int result = ACL_FAILURE;
335
336 acl_entry_t entry;
337 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
338
339 while (ge_result == ACL_SUCCESS) {
340 acl_tag_t tag = ACL_UNDEFINED_TAG;
341 int tag_result = acl_get_tag_type(entry, &tag);
342
343 if (tag_result == ACL_ERROR) {
344 perror("acl_execute_masked (acl_get_tag_type)");
345 result = ACL_ERROR;
346 goto cleanup;
347 }
348
349 if (tag == ACL_MASK) {
350 /* This is the mask entry, get its permissions, and see if
351 execute is specified. */
352 acl_permset_t permset;
353
354 int ps_result = acl_get_permset(entry, &permset);
355 if (ps_result == ACL_ERROR) {
356 perror("acl_execute_masked (acl_get_permset)");
357 result = ACL_ERROR;
358 goto cleanup;
359 }
360
361 int gp_result = acl_get_perm(permset, ACL_EXECUTE);
362 if (gp_result == ACL_ERROR) {
363 perror("acl_execute_masked (acl_get_perm)");
364 result = ACL_ERROR;
365 goto cleanup;
366 }
367
368 if (gp_result == ACL_FAILURE) {
369 /* No execute bit set in the mask; execute not allowed. */
370 return ACL_SUCCESS;
371 }
372 }
373
374 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
375 }
376
377 cleanup:
378 acl_free(acl);
379 return result;
380 }
381
382
383 /**
384 * @brief Determine whether @c path is executable (by anyone) or a
385 * directory.
386 *
387 * This is used as part of the heuristic to determine whether or not
388 * we should mask the execute bit when inheriting an ACL. If @c path
389 * is a directory, the answer is a clear-cut yes. This behavior is
390 * modeled after the capital 'X' perms of setfacl.
391 *
392 * If @c path is a file, we check the @a effective permissions,
393 * contrary to what setfacl does.
394 *
395 * @param path
396 * The path to check.
397 *
398 * @return
399 * - @c ACL_SUCCESS - @c path is a directory, or someone has effective
400 execute permissions.
401 * - @c ACL_FAILURE - @c path is a regular file and nobody can execute
402 it.
403 * - @c ACL_ERROR - Unexpected library error.
404 */
405 int any_can_execute_or_dir(const char* path) {
406
407 if (is_directory(path)) {
408 /* That was easy... */
409 return ACL_SUCCESS;
410 }
411
412 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
413
414 if (acl == (acl_t)NULL) {
415 perror("any_can_execute_or_dir (acl_get_file)");
416 return ACL_ERROR;
417 }
418
419 /* Our return value. */
420 int result = ACL_FAILURE;
421
422 if (acl_is_minimal(&acl)) {
423 mode_t mode = get_mode(path);
424 if (mode & (S_IXUSR | S_IXOTH | S_IXGRP)) {
425 result = ACL_SUCCESS;
426 goto cleanup;
427 }
428 else {
429 result = ACL_FAILURE;
430 goto cleanup;
431 }
432 }
433
434 acl_entry_t entry;
435 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
436
437 while (ge_result == ACL_SUCCESS) {
438 acl_permset_t permset;
439
440 int ps_result = acl_get_permset(entry, &permset);
441 if (ps_result == ACL_ERROR) {
442 perror("any_can_execute_or_dir (acl_get_permset)");
443 result = ACL_ERROR;
444 goto cleanup;
445 }
446
447 int gp_result = acl_get_perm(permset, ACL_EXECUTE);
448 if (gp_result == ACL_ERROR) {
449 perror("any_can_execute_or_dir (acl_get_perm)");
450 result = ACL_ERROR;
451 goto cleanup;
452 }
453
454 if (gp_result == ACL_SUCCESS) {
455 /* Only return one if this execute bit is not masked. */
456 if (acl_execute_masked(path) != ACL_SUCCESS) {
457 result = ACL_SUCCESS;
458 goto cleanup;
459 }
460 }
461
462 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
463 }
464
465 if (ge_result == ACL_ERROR) {
466 perror("any_can_execute_or_dir (acl_get_entry)");
467 result = ACL_ERROR;
468 goto cleanup;
469 }
470
471 cleanup:
472 acl_free(acl);
473 return result;
474 }
475
476
477
478 /**
479 * @brief Inherit the default ACL from @c parent to @c path.
480 *
481 * The @c parent parameter does not necessarily need to be the parent
482 * of @c path, although that will usually be the case. This overwrites
483 * any existing default ACL on @c path.
484 *
485 * @param parent
486 * The parent directory whose ACL we want to inherit.
487 *
488 * @param path
489 * The target directory whose ACL we wish to overwrite (or create).
490 *
491 * @return
492 * - @c ACL_SUCCESS - The default ACL was inherited successfully.
493 * - @c ACL_FAILURE - Either @c parent or @c path is not a directory.
494 * - @c ACL_ERROR - Unexpected library error.
495 */
496 int inherit_default_acl(const char* path, const char* parent) {
497
498 /* Our return value. */
499 int result = ACL_SUCCESS;
500
501 if (path == NULL) {
502 errno = ENOENT;
503 return ACL_ERROR;
504 }
505
506 if (!is_directory(path) || !is_directory(parent)) {
507 return ACL_FAILURE;
508 }
509
510 acl_t parent_acl = acl_get_file(parent, ACL_TYPE_DEFAULT);
511 if (parent_acl == (acl_t)NULL) {
512 perror("inherit_default_acl (acl_get_file)");
513 return ACL_ERROR;
514 }
515
516 acl_t path_acl = acl_dup(parent_acl);
517
518 if (path_acl == (acl_t)NULL) {
519 perror("inherit_default_acl (acl_dup)");
520 acl_free(parent_acl);
521 return ACL_ERROR;
522 }
523
524 int sf_result = acl_set_file(path, ACL_TYPE_DEFAULT, path_acl);
525 if (sf_result == -1) {
526 perror("inherit_default_acl (acl_set_file)");
527 result = ACL_ERROR;
528 goto cleanup;
529 }
530
531 cleanup:
532 acl_free(path_acl);
533 return result;
534 }
535
536
537
538 /**
539 * @brief Remove @c ACL_USER, @c ACL_GROUP, and @c ACL_MASK entries
540 * from the given path.
541 *
542 * @param path
543 * The path whose ACLs we want to wipe.
544 *
545 * @return
546 * - @c ACL_SUCCESS - The ACLs were wiped successfully, or none
547 * existed in the first place.
548 * - @c ACL_ERROR - Unexpected library error.
549 */
550 int wipe_acls(const char* path) {
551
552 if (path == NULL) {
553 errno = ENOENT;
554 return ACL_ERROR;
555 }
556
557 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
558 if (acl == (acl_t)NULL) {
559 perror("wipe_acls (acl_get_file)");
560 return ACL_ERROR;
561 }
562
563 /* Our return value. */
564 int result = ACL_SUCCESS;
565
566 acl_entry_t entry;
567 int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
568
569 while (ge_result == ACL_SUCCESS) {
570 int d_result = acl_delete_entry(acl, entry);
571 if (d_result == ACL_ERROR) {
572 perror("wipe_acls (acl_delete_entry)");
573 result = ACL_ERROR;
574 goto cleanup;
575 }
576
577 ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
578 }
579
580 /* Catches the first acl_get_entry as well as the ones at the end of
581 the loop. */
582 if (ge_result == ACL_ERROR) {
583 perror("wipe_acls (acl_get_entry)");
584 result = ACL_ERROR;
585 goto cleanup;
586 }
587
588 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
589 if (sf_result == ACL_ERROR) {
590 perror("wipe_acls (acl_set_file)");
591 result = ACL_ERROR;
592 goto cleanup;
593 }
594
595 cleanup:
596 acl_free(acl);
597 return result;
598 }
599
600
601
602 /**
603 * @brief Apply parent default ACL to a path.
604 *
605 * This overwrites any existing ACLs on @c path.
606 *
607 * @param path
608 * The path whose ACL we would like to reset to its default.
609 *
610 * @param no_exec_mask
611 * The value (either true or false) of the --no-exec-mask flag.
612 *
613 * @return
614 * - @c ACL_SUCCESS - The parent default ACL was inherited successfully.
615 * - @c ACL_FAILURE - The target path is not a regular file/directory,
616 * or the parent of @c path is not a directory.
617 * - @c ACL_ERROR - Unexpected library error.
618 */
619 int apply_default_acl(const char* path, bool no_exec_mask) {
620
621 if (path == NULL) {
622 errno = ENOENT;
623 return ACL_ERROR;
624 }
625
626 if (!is_regular_file(path) && !is_directory(path)) {
627 return ACL_FAILURE;
628 }
629
630 /* dirname mangles its argument */
631 char path_copy[PATH_MAX];
632 strncpy(path_copy, path, PATH_MAX-1);
633 path_copy[PATH_MAX-1] = 0;
634
635 char* parent = dirname(path_copy);
636 if (!is_directory(parent)) {
637 /* Make sure dirname() did what we think it did. */
638 return ACL_FAILURE;
639 }
640
641 /* Default to not masking the exec bit; i.e. applying the default
642 ACL literally. If --no-exec-mask was not specified, then we try
643 to "guess" whether or not to mask the exec bit. */
644 bool allow_exec = true;
645
646 if (!no_exec_mask) {
647 int ace_result = any_can_execute_or_dir(path);
648
649 if (ace_result == ACL_ERROR) {
650 perror("apply_default_acl (any_can_execute_or_dir)");
651 return ACL_ERROR;
652 }
653
654 allow_exec = (bool)ace_result;
655 }
656
657 acl_t defacl = acl_get_file(parent, ACL_TYPE_DEFAULT);
658
659 if (defacl == (acl_t)NULL) {
660 perror("apply_default_acl (acl_get_file)");
661 return ACL_ERROR;
662 }
663
664 /* Our return value. */
665 int result = ACL_SUCCESS;
666
667 int wipe_result = wipe_acls(path);
668 if (wipe_result == ACL_ERROR) {
669 perror("apply_default_acl (wipe_acls)");
670 result = ACL_ERROR;
671 goto cleanup;
672 }
673
674 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
675 ACL with this one. */
676 acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
677 if (acl == (acl_t)NULL) {
678 perror("apply_default_acl (acl_get_file)");
679 return ACL_ERROR;
680 }
681
682 /* If it's a directory, inherit the parent's default. */
683 int inherit_result = inherit_default_acl(path, parent);
684 if (inherit_result == ACL_ERROR) {
685 perror("apply_default_acl (inherit_acls)");
686 result = ACL_ERROR;
687 goto cleanup;
688 }
689
690 acl_entry_t entry;
691 int ge_result = acl_get_entry(defacl, ACL_FIRST_ENTRY, &entry);
692
693 while (ge_result == ACL_SUCCESS) {
694 acl_tag_t tag = ACL_UNDEFINED_TAG;
695 int tag_result = acl_get_tag_type(entry, &tag);
696
697 if (tag_result == ACL_ERROR) {
698 perror("apply_default_acl (acl_get_tag_type)");
699 result = ACL_ERROR;
700 goto cleanup;
701 }
702
703
704 /* We've got an entry/tag from the default ACL. Get its permset. */
705 acl_permset_t permset;
706 int ps_result = acl_get_permset(entry, &permset);
707 if (ps_result == ACL_ERROR) {
708 perror("apply_default_acl (acl_get_permset)");
709 result = ACL_ERROR;
710 goto cleanup;
711 }
712
713 /* If this is a default mask, fix it up. */
714 if (tag == ACL_MASK ||
715 tag == ACL_USER_OBJ ||
716 tag == ACL_GROUP_OBJ ||
717 tag == ACL_OTHER) {
718
719 if (!allow_exec) {
720 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
721 minimal ACLs) or acl_other entries, so if execute should be
722 masked, we have to do it manually. */
723 int d_result = acl_delete_perm(permset, ACL_EXECUTE);
724 if (d_result == ACL_ERROR) {
725 perror("apply_default_acl (acl_delete_perm)");
726 result = ACL_ERROR;
727 goto cleanup;
728 }
729
730 int sp_result = acl_set_permset(entry, permset);
731 if (sp_result == ACL_ERROR) {
732 perror("apply_default_acl (acl_set_permset)");
733 result = ACL_ERROR;
734 goto cleanup;
735 }
736 }
737 }
738
739 /* Finally, add the permset to the access ACL. */
740 int set_result = acl_set_entry(&acl, entry);
741 if (set_result == ACL_ERROR) {
742 perror("apply_default_acl (acl_set_entry)");
743 result = ACL_ERROR;
744 goto cleanup;
745 }
746
747 ge_result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry);
748 }
749
750 /* Catches the first acl_get_entry as well as the ones at the end of
751 the loop. */
752 if (ge_result == ACL_ERROR) {
753 perror("apply_default_acl (acl_get_entry)");
754 result = ACL_ERROR;
755 goto cleanup;
756 }
757
758 int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl);
759 if (sf_result == ACL_ERROR) {
760 perror("apply_default_acl (acl_set_file)");
761 result = ACL_ERROR;
762 goto cleanup;
763 }
764
765 cleanup:
766 acl_free(defacl);
767 return result;
768 }
769
770
771
772 /**
773 * @brief Display program usage information.
774 *
775 * @param program_name
776 * The program name to use in the output.
777 *
778 */
779 void usage(char* program_name) {
780 printf("Apply any applicable default ACLs to the given files or "
781 "directories.\n\n");
782 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
783 program_name);
784 printf("Flags:\n");
785 printf(" -h, --help Print this help message\n");
786 printf(" -r, --recursive Act on any given directories recursively\n");
787 printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n");
788
789 return;
790 }
791
792
793 /**
794 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
795 *
796 * For parameter information, see the @c nftw man page.
797 *
798 * @return If the ACL was applied to @c target successfully, we return
799 * @c FTW_CONTINUE to signal to @ nftw() that we should proceed onto
800 * the next file or directory. Otherwise, we return @c FTW_STOP to
801 * signal failure.
802 *
803 */
804 int apply_default_acl_nftw(const char *target,
805 const struct stat *s,
806 int info,
807 struct FTW *ftw) {
808
809 bool app_result = apply_default_acl(target, false);
810 if (app_result) {
811 return FTW_CONTINUE;
812 }
813 else {
814 return FTW_STOP;
815 }
816 }
817
818
819
820 /**
821 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
822 *
823 * This is identical to @c apply_default_acl_nftw(), except it passes
824 * @c true to @c apply_default_acl() as its no_exec_mask argument.
825 *
826 */
827 int apply_default_acl_nftw_x(const char *target,
828 const struct stat *s,
829 int info,
830 struct FTW *ftw) {
831
832 bool app_result = apply_default_acl(target, true);
833 if (app_result) {
834 return FTW_CONTINUE;
835 }
836 else {
837 return FTW_STOP;
838 }
839 }
840
841
842
843 /**
844 * @brief Recursive version of @c apply_default_acl().
845 *
846 * If @c target is a directory, we use @c nftw() to call @c
847 * apply_default_acl() recursively on all of its children. Otherwise,
848 * we just delegate to @c apply_default_acl().
849 *
850 * We ignore symlinks for consistency with chmod -r.
851 *
852 * @param target
853 * The root (path) of the recursive application.
854 *
855 * @param no_exec_mask
856 * The value (either true or false) of the --no-exec-mask flag.
857 *
858 * @return
859 * If @c target is not a directory, we return the result of
860 * calling @c apply_default_acl() on @c target. Otherwise, we convert
861 * the return value of @c nftw(). If @c nftw() succeeds (returns 0),
862 * then we return @c true. Otherwise, we return @c false.
863 * \n\n
864 * If there is an error, it will be reported via @c perror, but
865 * we still return @c false.
866 */
867 bool apply_default_acl_recursive(const char *target, bool no_exec_mask) {
868
869 if (!is_directory(target)) {
870 return apply_default_acl(target, no_exec_mask);
871 }
872
873 int max_levels = 256;
874 int flags = FTW_PHYS; /* Don't follow links. */
875
876 /* There are two separate functions that could be passed to
877 nftw(). One passes no_exec_mask = true to apply_default_acl(),
878 and the other passes no_exec_mask = false. Since the function we
879 pass to nftw() cannot have parameters, we have to create separate
880 options and make the decision here. */
881 int (*fn)(const char *, const struct stat *, int, struct FTW *) = NULL;
882 fn = no_exec_mask ? apply_default_acl_nftw_x : apply_default_acl_nftw;
883
884 int nftw_result = nftw(target, fn, max_levels, flags);
885
886 if (nftw_result == 0) {
887 /* Success */
888 return true;
889 }
890
891 /* nftw will return -1 on error, or if the supplied function
892 * (apply_default_acl_nftw) returns a non-zero result, nftw will
893 * return that.
894 */
895 if (nftw_result == -1) {
896 perror("apply_default_acl_recursive (nftw)");
897 }
898
899 return false;
900 }
901
902
903
904 /**
905 * @brief Call apply_default_acl (possibly recursively) on each
906 * command-line argument.
907 *
908 * @return Either @c EXIT_FAILURE or @c EXIT_SUCCESS. If everything
909 * goes as expected, we return @c EXIT_SUCCESS. Otherwise, we return
910 * @c EXIT_FAILURE.
911 */
912 int main(int argc, char* argv[]) {
913
914 if (argc < 2) {
915 usage(argv[0]);
916 return EXIT_FAILURE;
917 }
918
919 bool recursive = false;
920 bool no_exec_mask = false;
921
922 struct option long_options[] = {
923 /* These options set a flag. */
924 {"help", no_argument, NULL, 'h'},
925 {"recursive", no_argument, NULL, 'r'},
926 {"no-exec-mask", no_argument, NULL, 'x'},
927 {NULL, 0, NULL, 0}
928 };
929
930 int opt = 0;
931
932 while ((opt = getopt_long(argc, argv, "hrx", long_options, NULL)) != -1) {
933 switch (opt) {
934 case 'h':
935 usage(argv[0]);
936 return EXIT_SUCCESS;
937 case 'r':
938 recursive = true;
939 break;
940 case 'x':
941 no_exec_mask = true;
942 break;
943 default:
944 usage(argv[0]);
945 return EXIT_FAILURE;
946 }
947 }
948
949 int result = EXIT_SUCCESS;
950
951 int arg_index = 1;
952 for (arg_index = optind; arg_index < argc; arg_index++) {
953 const char* target = argv[arg_index];
954 bool reapp_result = false;
955
956 if (recursive) {
957 reapp_result = apply_default_acl_recursive(target, no_exec_mask);
958 }
959 else {
960 /* It's either normal file, or we're not operating recursively. */
961 reapp_result = apply_default_acl(target, no_exec_mask);
962 }
963
964 if (!reapp_result) {
965 result = EXIT_FAILURE;
966 }
967 }
968
969 return result;
970 }