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