]> gitweb.michael.orlitzky.com - apply-default-acl.git/blob - src/apply-default-acl.c
7c480a98238ad9cb7d7b6703b90e5e2c93a9d9fa
[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 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);
810 if (app_result) {
811 return FTW_CONTINUE;
812 }
813 else {
814 return FTW_STOP;
815 }
816 }
817
818
819
820 /**
821 * @brief Recursive version of @c apply_default_acl().
822 *
823 * If @c target is a directory, we use @c nftw() to call @c
824 * apply_default_acl() recursively on all of its children. Otherwise,
825 * we just delegate to @c apply_default_acl().
826 *
827 * We ignore symlinks for consistency with chmod -r.
828 *
829 * @return
830 * If @c target is not a directory, we return the result of
831 * calling @c apply_default_acl() on @c target. Otherwise, we convert
832 * the return value of @c nftw(). If @c nftw() succeeds (returns 0),
833 * then we return @c true. Otherwise, we return @c false.
834 * \n\n
835 * If there is an error, it will be reported via @c perror, but
836 * we still return @c false.
837 */
838 bool apply_default_acl_recursive(const char *target) {
839
840 if (!is_directory(target)) {
841 return apply_default_acl(target);
842 }
843
844 int max_levels = 256;
845 int flags = FTW_PHYS; /* Don't follow links. */
846
847 int nftw_result = nftw(target,
848 apply_default_acl_nftw,
849 max_levels,
850 flags);
851
852 if (nftw_result == 0) {
853 /* Success */
854 return true;
855 }
856
857 /* nftw will return -1 on error, or if the supplied function
858 * (apply_default_acl_nftw) returns a non-zero result, nftw will
859 * return that.
860 */
861 if (nftw_result == -1) {
862 perror("apply_default_acl_recursive (nftw)");
863 }
864
865 return false;
866 }
867
868
869
870 /**
871 * @brief Call apply_default_acl (possibly recursively) on each
872 * command-line argument.
873 *
874 * @return Either @c EXIT_FAILURE or @c EXIT_SUCCESS. If everything
875 * goes as expected, we return @c EXIT_SUCCESS. Otherwise, we return
876 * @c EXIT_FAILURE.
877 */
878 int main(int argc, char* argv[]) {
879
880 if (argc < 2) {
881 usage(argv[0]);
882 return EXIT_FAILURE;
883 }
884
885 bool recursive = false;
886 /* bool no_exec_mask is declared static/global */
887
888 struct option long_options[] = {
889 /* These options set a flag. */
890 {"help", no_argument, NULL, 'h'},
891 {"recursive", no_argument, NULL, 'r'},
892 {"no-exec-mask", no_argument, NULL, 'x'},
893 {NULL, 0, NULL, 0}
894 };
895
896 int opt = 0;
897
898 while ((opt = getopt_long(argc, argv, "hrx", long_options, NULL)) != -1) {
899 switch (opt) {
900 case 'h':
901 usage(argv[0]);
902 return EXIT_SUCCESS;
903 case 'r':
904 recursive = true;
905 break;
906 case 'x':
907 no_exec_mask = true;
908 break;
909 default:
910 usage(argv[0]);
911 return EXIT_FAILURE;
912 }
913 }
914
915 int result = EXIT_SUCCESS;
916
917 int arg_index = 1;
918 for (arg_index = optind; arg_index < argc; arg_index++) {
919 const char* target = argv[arg_index];
920 bool reapp_result = false;
921
922 if (recursive) {
923 reapp_result = apply_default_acl_recursive(target);
924 }
925 else {
926 /* It's either normal file, or we're not operating recursively. */
927 reapp_result = apply_default_acl(target);
928 }
929
930 if (!reapp_result) {
931 result = EXIT_FAILURE;
932 }
933 }
934
935 return result;
936 }