2 * @file apply-default-acl.c
4 * @brief The entire implementation.
8 /* On Linux, ftw.h needs this special voodoo to work. */
9 #define _XOPEN_SOURCE 500
12 #include <ftw.h> /* nftw() et al. */
14 #include <libgen.h> /* dirname() */
15 #include <limits.h> /* PATH_MAX */
24 #include <acl/libacl.h> /* acl_get_perm, not portable */
25 #include <sys/types.h>
29 /* Command-line options */
30 static bool no_exec_mask
= false;
34 * @brief Get the mode bits from the given path.
37 * The path (file or directory) whose mode we want.
39 * @return A mode_t (st_mode) structure containing the mode bits.
40 * See sys/stat.h for details.
42 mode_t
get_mode(const char* path
) {
49 int result
= stat(path
, &s
);
55 /* errno will be set already by stat() */
62 * @brief Determine whether or not the given path is a regular file.
67 * @return true if @c path is a regular file, false otherwise.
69 bool is_regular_file(const char* path
) {
75 int result
= stat(path
, &s
);
77 return S_ISREG(s
.st_mode
);
86 * @brief Determine whether or not the given path is a directory.
91 * @return true if @c path is a directory, false otherwise.
93 bool is_directory(const char* path
) {
99 int result
= stat(path
, &s
);
101 return S_ISDIR(s
.st_mode
);
110 int acl_set_entry(acl_t
* aclp
,
113 * Update or create the given entry.
117 int gt_result
= acl_get_tag_type(entry
, &entry_tag
);
118 if (gt_result
== -1) {
119 perror("acl_set_entry (acl_get_tag_type)");
123 acl_permset_t entry_permset
;
124 int ps_result
= acl_get_permset(entry
, &entry_permset
);
125 if (ps_result
== -1) {
126 perror("acl_set_entry (acl_get_permset)");
130 acl_entry_t existing_entry
;
131 /* Loop through the given ACL looking for matching entries. */
132 int result
= acl_get_entry(*aclp
, ACL_FIRST_ENTRY
, &existing_entry
);
134 while (result
== 1) {
135 acl_tag_t existing_tag
= ACL_UNDEFINED_TAG
;
136 int tag_result
= acl_get_tag_type(existing_entry
, &existing_tag
);
138 if (tag_result
== -1) {
139 perror("set_acl_tag_permset (acl_get_tag_type)");
143 if (existing_tag
== entry_tag
) {
144 if (entry_tag
== ACL_USER_OBJ
||
145 entry_tag
== ACL_GROUP_OBJ
||
146 entry_tag
== ACL_OTHER
) {
147 /* Only update for these three since all other tags will have
149 acl_permset_t existing_permset
;
150 int gep_result
= acl_get_permset(existing_entry
, &existing_permset
);
151 if (gep_result
== -1) {
152 perror("acl_set_entry (acl_get_permset)");
156 int s_result
= acl_set_permset(existing_entry
, entry_permset
);
157 if (s_result
== -1) {
158 perror("acl_set_entry (acl_set_permset)");
167 result
= acl_get_entry(*aclp
, ACL_NEXT_ENTRY
, &existing_entry
);
170 /* This catches both the initial acl_get_entry and the ones at the
173 perror("acl_set_entry (acl_get_entry)");
177 /* If we've made it this far, we need to add a new entry to the
179 acl_entry_t new_entry
;
181 /* We allocate memory here that we should release! */
182 int c_result
= acl_create_entry(aclp
, &new_entry
);
183 if (c_result
== -1) {
184 perror("acl_set_entry (acl_create_entry)");
188 int st_result
= acl_set_tag_type(new_entry
, entry_tag
);
189 if (st_result
== -1) {
190 perror("acl_set_entry (acl_set_tag_type)");
194 int s_result
= acl_set_permset(new_entry
, entry_permset
);
195 if (s_result
== -1) {
196 perror("acl_set_entry (acl_set_permset)");
200 if (entry_tag
== ACL_USER
|| entry_tag
== ACL_GROUP
) {
201 /* We need to set the qualifier too. */
202 void* entry_qual
= acl_get_qualifier(entry
);
203 if (entry_qual
== (void*)NULL
) {
204 perror("acl_set_entry (acl_get_qualifier)");
208 int sq_result
= acl_set_qualifier(new_entry
, entry_qual
);
209 if (sq_result
== -1) {
210 perror("acl_set_entry (acl_set_qualifier)");
220 int acl_entry_count(acl_t
* acl
) {
222 * Return the number of entries in acl, or -1 on error.
226 int result
= acl_get_entry(*acl
, ACL_FIRST_ENTRY
, &entry
);
228 while (result
== 1) {
230 result
= acl_get_entry(*acl
, ACL_NEXT_ENTRY
, &entry
);
234 perror("acl_is_minimal (acl_get_entry)");
242 int acl_is_minimal(acl_t
* acl
) {
243 /* An ACL is minimal if it has fewer than four entries. Return 0 for
244 * false, 1 for true, and -1 on error.
247 int ec
= acl_entry_count(acl
);
249 perror("acl_is_minimal (acl_entry_count)");
262 int acl_execute_masked(const char* path
) {
263 /* Returns 1 i the given path has an ACL mask which denies
264 execute. Returns 0 if it does not (or if it has no ACL/mask at
265 all), and -1 on error. */
267 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
269 if (acl
== (acl_t
)NULL
) {
273 /* Our return value. */
277 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
279 while (ge_result
== 1) {
280 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
281 int tag_result
= acl_get_tag_type(entry
, &tag
);
283 if (tag_result
== -1) {
284 perror("acl_execute_masked (acl_get_tag_type)");
289 if (tag
== ACL_MASK
) {
290 /* This is the mask entry, get its permissions, and see if
291 execute is specified. */
292 acl_permset_t permset
;
294 int ps_result
= acl_get_permset(entry
, &permset
);
295 if (ps_result
== -1) {
296 perror("acl_execute_masked (acl_get_permset)");
301 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
302 if (gp_result
== -1) {
303 perror("acl_execute_masked (acl_get_perm)");
308 if (gp_result
== 0) {
309 /* No execute bit set in the mask; execute not allowed. */
314 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
323 int any_can_execute_or_dir(const char* path
) {
324 /* If the given path is a directory, returns 1. Otherwise, returns 1
325 * if any ACL entry has EFFECTIVE execute access, 0 if none do, and
328 * This is meant to somewhat mimic setfacl's handling of the capital
329 * 'X' perm, which allows execute access if the target is a
330 * directory or someone can already execute it. We differ in that we
331 * check the effective execute rather than just the execute bits.
334 if (is_directory(path
)) {
335 /* That was easy... */
339 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
341 if (acl
== (acl_t
)NULL
) {
345 /* Our return value. */
348 if (acl_is_minimal(&acl
)) {
349 mode_t mode
= get_mode(path
);
350 if (mode
& (S_IXUSR
| S_IXOTH
| S_IXGRP
)) {
361 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
363 while (ge_result
== 1) {
364 acl_permset_t permset
;
366 int ps_result
= acl_get_permset(entry
, &permset
);
367 if (ps_result
== -1) {
368 perror("any_can_execute_or_dir (acl_get_permset)");
373 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
374 if (gp_result
== -1) {
375 perror("any_can_execute_or_dir (acl_get_perm)");
380 if (gp_result
== 1) {
381 /* Only return one if this execute bit is not masked. */
382 if (acl_execute_masked(path
) != 1) {
388 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
391 if (ge_result
== -1) {
392 perror("any_can_execute_or_dir (acl_get_entry)");
403 int inherit_default_acl(const char* path
, const char* parent
) {
404 /* Inherit the default ACL from parent to path. This overwrites any
405 * existing default ACL. Returns 1 for success, 0 for failure, and
409 /* Our return value. */
417 if (!is_directory(path
) || !is_directory(parent
)) {
421 acl_t parent_acl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
422 if (parent_acl
== (acl_t
)NULL
) {
426 acl_t path_acl
= acl_dup(parent_acl
);
428 if (path_acl
== (acl_t
)NULL
) {
429 perror("inherit_default_acl (acl_dup)");
430 acl_free(parent_acl
);
434 int sf_result
= acl_set_file(path
, ACL_TYPE_DEFAULT
, path_acl
);
435 if (sf_result
== -1) {
436 perror("inherit_default_acl (acl_set_file)");
447 int wipe_acls(const char* path
) {
448 /* Remove ACL_USER, ACL_GROUP, and ACL_MASK entries from
449 path. Returns 1 for success, 0 for failure, and -1 on error. */
456 /* Finally, remove individual named/mask entries. */
457 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
458 if (acl
== (acl_t
)NULL
) {
459 perror("wipe_acls (acl_get_file)");
463 /* Our return value. */
467 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
469 while (ge_result
== 1) {
470 int d_result
= acl_delete_entry(acl
, entry
);
471 if (d_result
== -1) {
472 perror("wipe_acls (acl_delete_entry)");
477 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
480 /* Catches the first acl_get_entry as well as the ones at the end of
482 if (ge_result
== -1) {
483 perror("wipe_acls (acl_get_entry)");
488 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
489 if (sf_result
== -1) {
490 perror("wipe_acls (acl_set_file)");
501 int apply_default_acl(const char* path
) {
502 /* Really apply the default ACL by looping through it. Returns one
503 * for success, zero for failure (i.e. no ACL), and -1 on unexpected
511 if (!is_regular_file(path
) && !is_directory(path
)) {
515 /* dirname mangles its argument */
516 char path_copy
[PATH_MAX
];
517 strncpy(path_copy
, path
, PATH_MAX
-1);
518 path_copy
[PATH_MAX
-1] = 0;
520 char* parent
= dirname(path_copy
);
521 if (!is_directory(parent
)) {
522 /* Make sure dirname() did what we think it did. */
526 /* Default to not masking the exec bit; i.e. applying the default
527 ACL literally. If --no-exec-mask was not specified, then we try
528 to "guess" whether or not to mask the exec bit. */
529 bool allow_exec
= true;
532 int ace_result
= any_can_execute_or_dir(path
);
534 if (ace_result
== -1) {
535 perror("apply_default_acl (any_can_execute_or_dir)");
538 allow_exec
= (bool)ace_result
;
541 acl_t defacl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
543 if (defacl
== (acl_t
)NULL
) {
544 perror("apply_default_acl (acl_get_file)");
548 /* Our return value. */
551 int wipe_result
= wipe_acls(path
);
552 if (wipe_result
== -1) {
553 perror("apply_default_acl (wipe_acls)");
558 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
559 ACL with this one. */
560 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
561 if (acl
== (acl_t
)NULL
) {
562 perror("apply_default_acl (acl_get_file)");
566 /* If it's a directory, inherit the parent's default. */
567 int inherit_result
= inherit_default_acl(path
, parent
);
568 if (inherit_result
== -1) {
569 perror("apply_default_acl (inherit_acls)");
575 int ge_result
= acl_get_entry(defacl
, ACL_FIRST_ENTRY
, &entry
);
577 while (ge_result
== 1) {
578 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
579 int tag_result
= acl_get_tag_type(entry
, &tag
);
581 if (tag_result
== -1) {
582 perror("apply_default_acl (acl_get_tag_type)");
588 /* We've got an entry/tag from the default ACL. Get its permset. */
589 acl_permset_t permset
;
590 int ps_result
= acl_get_permset(entry
, &permset
);
591 if (ps_result
== -1) {
592 perror("apply_default_acl (acl_get_permset)");
597 /* If this is a default mask, fix it up. */
598 if (tag
== ACL_MASK
||
599 tag
== ACL_USER_OBJ
||
600 tag
== ACL_GROUP_OBJ
||
604 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
605 minimal ACLs) or acl_other entries, so if execute should be
606 masked, we have to do it manually. */
607 int d_result
= acl_delete_perm(permset
, ACL_EXECUTE
);
608 if (d_result
== -1) {
609 perror("apply_default_acl (acl_delete_perm)");
614 int sp_result
= acl_set_permset(entry
, permset
);
615 if (sp_result
== -1) {
616 perror("apply_default_acl (acl_set_permset)");
623 /* Finally, add the permset to the access ACL. */
624 int set_result
= acl_set_entry(&acl
, entry
);
625 if (set_result
== -1) {
626 perror("apply_default_acl (acl_set_entry)");
631 ge_result
= acl_get_entry(defacl
, ACL_NEXT_ENTRY
, &entry
);
634 /* Catches the first acl_get_entry as well as the ones at the end of
636 if (ge_result
== -1) {
637 perror("apply_default_acl (acl_get_entry)");
642 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
643 if (sf_result
== -1) {
644 perror("apply_default_acl (acl_set_file)");
655 void usage(char* program_name
) {
657 * Print usage information.
659 printf("Apply any applicable default ACLs to the given files or "
661 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
664 printf(" -h, --help Print this help message\n");
665 printf(" -r, --recursive Act on any given directories recursively\n");
666 printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n");
672 int apply_default_acl_nftw(const char *target
,
673 const struct stat
*s
,
676 /* A wrapper around the apply_default_acl() function for use with
677 * nftw(). We need to adjust the return value so that nftw() doesn't
678 * think we've failed.
680 bool reapp_result
= apply_default_acl(target
);
690 bool apply_default_acl_recursive(const char *target
) {
691 /* Attempt to apply default ACLs recursively. If target is a
692 * directory, we recurse through its entries. If not, we just
693 * apply the default ACL to target.
695 * We ignore symlinks for consistency with chmod -r.
698 if (!is_directory(target
)) {
699 return apply_default_acl(target
);
702 int max_levels
= 256;
703 int flags
= FTW_PHYS
; /* Don't follow links. */
705 int nftw_result
= nftw(target
,
706 apply_default_acl_nftw
,
710 if (nftw_result
== 0) {
715 /* nftw will return -1 on error, or if the supplied function
716 * (apply_default_acl_nftw) returns a non-zero result, nftw will
719 if (nftw_result
== -1) {
720 perror("apply_default_acl_recursive (nftw)");
727 int main(int argc
, char* argv
[]) {
729 * Call apply_default_acl on each command-line argument.
736 bool recursive
= false;
737 /* bool no_exec_mask is declared static/global */
739 struct option long_options
[] = {
740 /* These options set a flag. */
741 {"help", no_argument
, NULL
, 'h'},
742 {"recursive", no_argument
, NULL
, 'r'},
743 {"no-exec-mask", no_argument
, NULL
, 'x'},
749 while ((opt
= getopt_long(argc
, argv
, "hrx", long_options
, NULL
)) != -1) {
766 int result
= EXIT_SUCCESS
;
769 for (arg_index
= optind
; arg_index
< argc
; arg_index
++) {
770 const char* target
= argv
[arg_index
];
771 bool reapp_result
= false;
774 reapp_result
= apply_default_acl_recursive(target
);
777 /* It's either normal file, or we're not operating recursively. */
778 reapp_result
= apply_default_acl(target
);
782 result
= EXIT_FAILURE
;