1 /* On Linux, ftw.h needs this special voodoo to work. */
2 #define _XOPEN_SOURCE 500
5 #include <ftw.h> /* nftw() et al. */
7 #include <libgen.h> /* dirname() */
8 #include <limits.h> /* PATH_MAX */
17 #include <acl/libacl.h> /* acl_get_perm, not portable */
18 #include <sys/types.h>
22 mode_t
get_mode(const char* path
) {
24 * Get the mode bits from path.
32 int result
= stat(path
, &s
);
38 /* errno will be set already by stat() */
44 bool is_regular_file(const char* path
) {
46 * Returns true if path is a regular file, false otherwise.
53 int result
= stat(path
, &s
);
55 return S_ISREG(s
.st_mode
);
62 bool is_directory(const char* path
) {
64 * Returns true if path is a directory, false otherwise.
71 int result
= stat(path
, &s
);
73 return S_ISDIR(s
.st_mode
);
82 int acl_set_entry(acl_t
* aclp
,
85 * Update or create the given entry.
89 int gt_result
= acl_get_tag_type(entry
, &entry_tag
);
90 if (gt_result
== -1) {
91 perror("acl_set_entry (acl_get_tag_type)");
95 acl_permset_t entry_permset
;
96 int ps_result
= acl_get_permset(entry
, &entry_permset
);
97 if (ps_result
== -1) {
98 perror("acl_set_entry (acl_get_permset)");
102 acl_entry_t existing_entry
;
103 /* Loop through the given ACL looking for matching entries. */
104 int result
= acl_get_entry(*aclp
, ACL_FIRST_ENTRY
, &existing_entry
);
106 while (result
== 1) {
107 acl_tag_t existing_tag
= ACL_UNDEFINED_TAG
;
108 int tag_result
= acl_get_tag_type(existing_entry
, &existing_tag
);
110 if (tag_result
== -1) {
111 perror("set_acl_tag_permset (acl_get_tag_type)");
115 if (existing_tag
== entry_tag
) {
116 if (entry_tag
== ACL_USER_OBJ
||
117 entry_tag
== ACL_GROUP_OBJ
||
118 entry_tag
== ACL_OTHER
) {
119 /* Only update for these three since all other tags will have
121 acl_permset_t existing_permset
;
122 int gep_result
= acl_get_permset(existing_entry
, &existing_permset
);
123 if (gep_result
== -1) {
124 perror("acl_set_entry (acl_get_permset)");
128 int s_result
= acl_set_permset(existing_entry
, entry_permset
);
129 if (s_result
== -1) {
130 perror("acl_set_entry (acl_set_permset)");
139 result
= acl_get_entry(*aclp
, ACL_NEXT_ENTRY
, &existing_entry
);
142 /* This catches both the initial acl_get_entry and the ones at the
145 perror("acl_set_entry (acl_get_entry)");
149 /* If we've made it this far, we need to add a new entry to the
151 acl_entry_t new_entry
;
153 /* We allocate memory here that we should release! */
154 int c_result
= acl_create_entry(aclp
, &new_entry
);
155 if (c_result
== -1) {
156 perror("acl_set_entry (acl_create_entry)");
160 int st_result
= acl_set_tag_type(new_entry
, entry_tag
);
161 if (st_result
== -1) {
162 perror("acl_set_entry (acl_set_tag_type)");
166 int s_result
= acl_set_permset(new_entry
, entry_permset
);
167 if (s_result
== -1) {
168 perror("acl_set_entry (acl_set_permset)");
172 if (entry_tag
== ACL_USER
|| entry_tag
== ACL_GROUP
) {
173 /* We need to set the qualifier too. */
174 void* entry_qual
= acl_get_qualifier(entry
);
175 if (entry_qual
== (void*)NULL
) {
176 perror("acl_set_entry (acl_get_qualifier)");
180 int sq_result
= acl_set_qualifier(new_entry
, entry_qual
);
181 if (sq_result
== -1) {
182 perror("acl_set_entry (acl_set_qualifier)");
192 int acl_entry_count(acl_t
* acl
) {
194 * Return the number of entries in acl, or -1 on error.
198 int result
= acl_get_entry(*acl
, ACL_FIRST_ENTRY
, &entry
);
200 while (result
== 1) {
202 result
= acl_get_entry(*acl
, ACL_NEXT_ENTRY
, &entry
);
206 perror("acl_is_minimal (acl_get_entry)");
214 int acl_is_minimal(acl_t
* acl
) {
215 /* An ACL is minimal if it has fewer than four entries. Return 0 for
216 * false, 1 for true, and -1 on error.
219 int ec
= acl_entry_count(acl
);
221 perror("acl_is_minimal (acl_entry_count)");
234 int any_can_execute(const char* path
) {
235 /* Returns 1 if any ACL entry has execute access, 0 if none do, and
238 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
240 if (acl
== (acl_t
)NULL
) {
244 /* Our return value. */
247 if (acl_is_minimal(&acl
)) {
248 mode_t mode
= get_mode(path
);
249 if (mode
& (S_IXUSR
| S_IXOTH
| S_IXGRP
)) {
260 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
262 while (ge_result
== 1) {
263 acl_permset_t permset
;
265 int ps_result
= acl_get_permset(entry
, &permset
);
266 if (ps_result
== -1) {
267 perror("any_can_execute (acl_get_permset)");
272 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
273 if (gp_result
== -1) {
274 perror("any_can_execute (acl_get_perm)");
279 if (gp_result
== 1) {
284 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
287 if (ge_result
== -1) {
288 perror("any_can_execute (acl_get_entry)");
299 int inherit_default_acl(const char* path
, const char* parent
) {
300 /* Inherit the default ACL from parent to path. This overwrites any
301 * existing default ACL. Returns 1 for success, 0 for failure, and
305 /* Our return value. */
313 if (!is_directory(path
) || !is_directory(parent
)) {
317 acl_t parent_acl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
318 if (parent_acl
== (acl_t
)NULL
) {
322 acl_t path_acl
= acl_dup(parent_acl
);
324 if (path_acl
== (acl_t
)NULL
) {
325 perror("inherit_default_acl (acl_dup)");
326 acl_free(parent_acl
);
330 int sf_result
= acl_set_file(path
, ACL_TYPE_DEFAULT
, path_acl
);
331 if (sf_result
== -1) {
332 perror("inherit_default_acl (acl_set_file)");
343 int wipe_acls(const char* path
) {
344 /* Remove ACL_USER, ACL_GROUP, and ACL_MASK entries from
345 path. Returns 1 for success, 0 for failure, and -1 on error. */
352 /* Finally, remove individual named/mask entries. */
353 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
354 if (acl
== (acl_t
)NULL
) {
355 perror("wipe_acls (acl_get_file)");
359 /* Our return value. */
363 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
365 while (ge_result
== 1) {
366 int d_result
= acl_delete_entry(acl
, entry
);
367 if (d_result
== -1) {
368 perror("wipe_acls (acl_delete_entry)");
373 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
376 /* Catches the first acl_get_entry as well as the ones at the end of
378 if (ge_result
== -1) {
379 perror("wipe_acls (acl_get_entry)");
384 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
385 if (sf_result
== -1) {
386 perror("wipe_acls (acl_set_file)");
397 int apply_default_acl(const char* path
) {
398 /* Really apply the default ACL by looping through it. Returns one
399 * for success, zero for failure (i.e. no ACL), and -1 on unexpected
406 if (!is_regular_file(path
) && !is_directory(path
)) {
410 /* dirname mangles its argument */
411 char path_copy
[PATH_MAX
];
412 strncpy(path_copy
, path
, PATH_MAX
-1);
413 path_copy
[PATH_MAX
-1] = 0;
415 char* parent
= dirname(path_copy
);
416 if (!is_directory(parent
)) {
417 /* Make sure dirname() did what we think it did. */
421 int ace_result
= any_can_execute(path
);
422 if (ace_result
== -1) {
423 perror("apply_default_acl (any_can_execute)");
427 bool allow_exec
= (bool)ace_result
;
429 acl_t defacl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
431 if (defacl
== (acl_t
)NULL
) {
432 perror("apply_default_acl (acl_get_file)");
436 /* Our return value. */
439 int wipe_result
= wipe_acls(path
);
440 if (wipe_result
== -1) {
441 perror("apply_default_acl (wipe_acls)");
446 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
447 ACL with this one. */
448 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
449 if (acl
== (acl_t
)NULL
) {
450 perror("apply_default_acl (acl_get_file)");
454 /* If it's a directory, inherit the parent's default. */
455 int inherit_result
= inherit_default_acl(path
, parent
);
456 if (inherit_result
== -1) {
457 perror("apply_default_acl (inherit_acls)");
463 int ge_result
= acl_get_entry(defacl
, ACL_FIRST_ENTRY
, &entry
);
465 while (ge_result
== 1) {
466 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
467 int tag_result
= acl_get_tag_type(entry
, &tag
);
469 if (tag_result
== -1) {
470 perror("has_default_tag_acl (acl_get_tag_type)");
476 /* We've got an entry/tag from the default ACL. Get its permset. */
477 acl_permset_t permset
;
478 int ps_result
= acl_get_permset(entry
, &permset
);
479 if (ps_result
== -1) {
480 perror("apply_default_acl (acl_get_permset)");
485 /* If this is a default mask, fix it up. */
486 if (tag
== ACL_MASK
||
487 tag
== ACL_USER_OBJ
||
488 tag
== ACL_GROUP_OBJ
||
491 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
492 minimal ACLs) or acl_other entries, so if execute should be
493 masked, we have to do it manually. */
494 int d_result
= acl_delete_perm(permset
, ACL_EXECUTE
);
495 if (d_result
== -1) {
496 perror("apply_default_acl (acl_delete_perm)");
501 int sp_result
= acl_set_permset(entry
, permset
);
502 if (sp_result
== -1) {
503 perror("apply_default_acl (acl_set_permset)");
510 /* Finally, add the permset to the access ACL. */
511 int set_result
= acl_set_entry(&acl
, entry
);
512 if (set_result
== -1) {
513 perror("apply_default_acl (acl_set_entry)");
518 ge_result
= acl_get_entry(defacl
, ACL_NEXT_ENTRY
, &entry
);
521 /* Catches the first acl_get_entry as well as the ones at the end of
523 if (ge_result
== -1) {
524 perror("apply_default_acl (acl_get_entry)");
529 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
530 if (sf_result
== -1) {
531 perror("apply_default_acl (acl_set_file)");
542 void usage(char* program_name
) {
544 * Print usage information.
546 printf("Apply any applicable default ACLs to the given files or "
548 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
551 printf(" -h, --help Print this help message\n");
552 printf(" -r, --recursive Act on any given directories recursively\n");
556 int apply_default_acl_nftw(const char *target
,
557 const struct stat
*s
,
560 /* A wrapper around the apply_default_acl() function for use with
561 * nftw(). We need to adjust the return value so that nftw() doesn't
562 * think we've failed.
564 bool reapp_result
= apply_default_acl(target
);
574 bool apply_default_acl_recursive(const char *target
) {
575 /* Attempt to apply default ACLs recursively. If target is a
576 * directory, we recurse through its entries. If not, we just
577 * apply the default ACL to target.
579 * We ignore symlinks for consistency with chmod -r.
582 if (!is_directory(target
)) {
583 return apply_default_acl(target
);
586 int max_levels
= 256;
587 int flags
= FTW_PHYS
; /* Don't follow links. */
589 int nftw_result
= nftw(target
,
590 apply_default_acl_nftw
,
594 if (nftw_result
== 0) {
599 /* nftw will return -1 on error, or if the supplied function
600 * (apply_default_acl_nftw) returns a non-zero result, nftw will
603 if (nftw_result
== -1) {
604 perror("apply_default_acl_recursive (nftw)");
611 int main(int argc
, char* argv
[]) {
613 * Call apply_default_acl on each command-line argument.
621 bool recursive
= false;
623 struct option long_options
[] = {
624 /* These options set a flag. */
625 {"help", no_argument
, NULL
, 'h'},
626 {"recursive", no_argument
, NULL
, 'r'},
632 while ((opt
= getopt_long(argc
, argv
, "hr", long_options
, NULL
)) != -1) {
646 int result
= EXIT_SUCCESS
;
649 for (arg_index
= optind
; arg_index
< argc
; arg_index
++) {
650 const char* target
= argv
[arg_index
];
651 bool reapp_result
= false;
654 reapp_result
= apply_default_acl_recursive(target
);
657 /* It's either normal file, or we're not operating recursively. */
658 reapp_result
= apply_default_acl(target
);
662 result
= EXIT_FAILURE
;