1 /* On Linux, ftw.h needs this special voodoo to work. */
2 #define _XOPEN_SOURCE 500
5 #include <ftw.h> /* nftw() et al. */
6 #include <libgen.h> /* dirname() */
7 #include <limits.h> /* PATH_MAX */
16 #include <acl/libacl.h> /* acl_get_perm, not portable */
17 #include <sys/types.h>
21 mode_t
get_mode(const char* path
) {
23 * Get the mode bits from path.
31 int result
= stat(path
, &s
);
37 /* errno will be set already by stat() */
43 bool is_regular_file(const char* path
) {
45 * Returns true if path is a regular file, false otherwise.
52 int result
= stat(path
, &s
);
54 return S_ISREG(s
.st_mode
);
61 bool is_directory(const char* path
) {
63 * Returns true if path is a directory, false otherwise.
70 int result
= stat(path
, &s
);
72 return S_ISDIR(s
.st_mode
);
81 int acl_set_entry(acl_t
* aclp
,
84 * Update or create the given entry.
88 int gt_result
= acl_get_tag_type(entry
, &entry_tag
);
89 if (gt_result
== -1) {
90 perror("acl_set_entry (acl_get_tag_type)");
94 acl_permset_t entry_permset
;
95 int ps_result
= acl_get_permset(entry
, &entry_permset
);
96 if (ps_result
== -1) {
97 perror("acl_set_entry (acl_get_permset)");
101 acl_entry_t existing_entry
;
102 /* Loop through the given ACL looking for matching entries. */
103 int result
= acl_get_entry(*aclp
, ACL_FIRST_ENTRY
, &existing_entry
);
105 while (result
== 1) {
106 acl_tag_t existing_tag
= ACL_UNDEFINED_TAG
;
107 int tag_result
= acl_get_tag_type(existing_entry
, &existing_tag
);
109 if (tag_result
== -1) {
110 perror("set_acl_tag_permset (acl_get_tag_type)");
114 if (existing_tag
== entry_tag
) {
115 if (entry_tag
== ACL_USER_OBJ
||
116 entry_tag
== ACL_GROUP_OBJ
||
117 entry_tag
== ACL_OTHER
) {
118 /* Only update for these three since all other tags will have
120 acl_permset_t existing_permset
;
121 int gep_result
= acl_get_permset(existing_entry
, &existing_permset
);
122 if (gep_result
== -1) {
123 perror("acl_set_entry (acl_get_permset)");
127 int s_result
= acl_set_permset(existing_entry
, entry_permset
);
128 if (s_result
== -1) {
129 perror("acl_set_entry (acl_set_permset)");
138 result
= acl_get_entry(*aclp
, ACL_NEXT_ENTRY
, &existing_entry
);
141 /* This catches both the initial acl_get_entry and the ones at the
144 perror("acl_set_entry (acl_get_entry)");
148 /* If we've made it this far, we need to add a new entry to the
150 acl_entry_t new_entry
;
152 /* We allocate memory here that we should release! */
153 int c_result
= acl_create_entry(aclp
, &new_entry
);
154 if (c_result
== -1) {
155 perror("acl_set_entry (acl_create_entry)");
159 int st_result
= acl_set_tag_type(new_entry
, entry_tag
);
160 if (st_result
== -1) {
161 perror("acl_set_entry (acl_set_tag_type)");
165 int s_result
= acl_set_permset(new_entry
, entry_permset
);
166 if (s_result
== -1) {
167 perror("acl_set_entry (acl_set_permset)");
171 if (entry_tag
== ACL_USER
|| entry_tag
== ACL_GROUP
) {
172 /* We need to set the qualifier too. */
173 void* entry_qual
= acl_get_qualifier(entry
);
174 if (entry_qual
== (void*)NULL
) {
175 perror("acl_set_entry (acl_get_qualifier)");
179 int sq_result
= acl_set_qualifier(new_entry
, entry_qual
);
180 if (sq_result
== -1) {
181 perror("acl_set_entry (acl_set_qualifier)");
191 int acl_entry_count(acl_t
* acl
) {
193 * Return the number of entries in acl, or -1 on error.
197 int result
= acl_get_entry(*acl
, ACL_FIRST_ENTRY
, &entry
);
199 while (result
== 1) {
201 result
= acl_get_entry(*acl
, ACL_NEXT_ENTRY
, &entry
);
205 perror("acl_is_minimal (acl_get_entry)");
213 int acl_is_minimal(acl_t
* acl
) {
214 /* An ACL is minimal if it has fewer than four entries. Return 0 for
215 * false, 1 for true, and -1 on error.
218 int ec
= acl_entry_count(acl
);
220 perror("acl_is_minimal (acl_entry_count)");
233 int any_can_execute(const char* path
) {
234 /* Returns 1 if any ACL entry has execute access, 0 if none do, and
237 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
239 if (acl
== (acl_t
)NULL
) {
243 /* Our return value. */
246 if (acl_is_minimal(&acl
)) {
247 mode_t mode
= get_mode(path
);
248 if (mode
& (S_IXUSR
| S_IXOTH
| S_IXGRP
)) {
259 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
261 while (ge_result
== 1) {
262 acl_permset_t permset
;
264 int ps_result
= acl_get_permset(entry
, &permset
);
265 if (ps_result
== -1) {
266 perror("any_can_execute (acl_get_permset)");
271 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
272 if (gp_result
== -1) {
273 perror("any_can_execute (acl_get_perm)");
278 if (gp_result
== 1) {
283 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
286 if (ge_result
== -1) {
287 perror("any_can_execute (acl_get_entry)");
298 int inherit_default_acl(const char* path
, const char* parent
) {
299 /* Inherit the default ACL from parent to path. This overwrites any
300 * existing default ACL. Returns 1 for success, 0 for failure, and
304 /* Our return value. */
312 if (!is_directory(path
) || !is_directory(parent
)) {
316 acl_t parent_acl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
317 if (parent_acl
== (acl_t
)NULL
) {
321 acl_t path_acl
= acl_dup(parent_acl
);
323 if (path_acl
== (acl_t
)NULL
) {
324 perror("inherit_default_acl (acl_dup)");
325 acl_free(parent_acl
);
329 int sf_result
= acl_set_file(path
, ACL_TYPE_DEFAULT
, path_acl
);
330 if (sf_result
== -1) {
331 perror("inherit_default_acl (acl_set_file)");
342 int wipe_acls(const char* path
) {
343 /* Remove ACL_USER, ACL_GROUP, and ACL_MASK entries from
344 path. Returns 1 for success, 0 for failure, and -1 on error. */
351 /* Finally, remove individual named/mask entries. */
352 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
353 if (acl
== (acl_t
)NULL
) {
354 perror("wipe_acls (acl_get_file)");
358 /* Our return value. */
362 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
364 while (ge_result
== 1) {
365 int d_result
= acl_delete_entry(acl
, entry
);
366 if (d_result
== -1) {
367 perror("wipe_acls (acl_delete_entry)");
372 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
375 /* Catches the first acl_get_entry as well as the ones at the end of
377 if (ge_result
== -1) {
378 perror("wipe_acls (acl_get_entry)");
383 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
384 if (sf_result
== -1) {
385 perror("wipe_acls (acl_set_file)");
396 int reapply_default_acl(const char* path
) {
397 /* Really reapply the default ACL by looping through it. Returns one
398 * for success, zero for failure (i.e. no ACL), and -1 on unexpected
405 if (!is_regular_file(path
) && !is_directory(path
)) {
409 /* dirname mangles its argument */
410 char path_copy
[PATH_MAX
];
411 strncpy(path_copy
, path
, PATH_MAX
-1);
412 path_copy
[PATH_MAX
-1] = 0;
414 char* parent
= dirname(path_copy
);
415 if (!is_directory(parent
)) {
416 /* Make sure dirname() did what we think it did. */
420 int ace_result
= any_can_execute(path
);
421 if (ace_result
== -1) {
422 perror("reapply_default_acl (any_can_execute)");
426 bool allow_exec
= (bool)ace_result
;
428 acl_t defacl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
430 if (defacl
== (acl_t
)NULL
) {
431 perror("reapply_default_acl (acl_get_file)");
435 /* Our return value. */
438 int wipe_result
= wipe_acls(path
);
439 if (wipe_result
== -1) {
440 perror("reapply_default_acl (wipe_acls)");
445 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
446 ACL with this one. */
447 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
448 if (acl
== (acl_t
)NULL
) {
449 perror("reapply_default_acl (acl_get_file)");
453 /* If it's a directory, inherit the parent's default. */
454 int inherit_result
= inherit_default_acl(path
, parent
);
455 if (inherit_result
== -1) {
456 perror("reapply_default_acl (inherit_acls)");
462 int ge_result
= acl_get_entry(defacl
, ACL_FIRST_ENTRY
, &entry
);
464 while (ge_result
== 1) {
465 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
466 int tag_result
= acl_get_tag_type(entry
, &tag
);
468 if (tag_result
== -1) {
469 perror("has_default_tag_acl (acl_get_tag_type)");
475 /* We've got an entry/tag from the default ACL. Get its permset. */
476 acl_permset_t permset
;
477 int ps_result
= acl_get_permset(entry
, &permset
);
478 if (ps_result
== -1) {
479 perror("reapply_default_acl (acl_get_permset)");
484 /* If this is a default mask, fix it up. */
485 if (tag
== ACL_MASK
||
486 tag
== ACL_USER_OBJ
||
487 tag
== ACL_GROUP_OBJ
||
490 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
491 minimal ACLs) or acl_other entries, so if execute should be
492 masked, we have to do it manually. */
493 int d_result
= acl_delete_perm(permset
, ACL_EXECUTE
);
494 if (d_result
== -1) {
495 perror("reapply_default_acl (acl_delete_perm)");
500 int sp_result
= acl_set_permset(entry
, permset
);
501 if (sp_result
== -1) {
502 perror("reapply_default_acl (acl_set_permset)");
509 /* Finally, add the permset to the access ACL. */
510 int set_result
= acl_set_entry(&acl
, entry
);
511 if (set_result
== -1) {
512 perror("reapply_default_acl (acl_set_entry)");
517 ge_result
= acl_get_entry(defacl
, ACL_NEXT_ENTRY
, &entry
);
520 /* Catches the first acl_get_entry as well as the ones at the end of
522 if (ge_result
== -1) {
523 perror("reapply_default_acl (acl_get_entry)");
528 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
529 if (sf_result
== -1) {
530 perror("reapply_default_acl (acl_set_file)");
541 void usage(char* program_name
) {
543 * Print usage information.
545 printf("Reapply any applicable default ACLs to the given files or "
547 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
550 printf(" -h, --help Print this help message\n");
551 printf(" -r, --recursive Act on any given directories recursively\n");
555 bool asked_for_flag(int argc
,
557 const char* short_flag
,
558 const char* long_flag
) {
560 * Check argv for either form of the flag, e.g. -h or --help.
563 for (arg_index
= 1; arg_index
< argc
; arg_index
++) {
564 if (!strcmp(argv
[arg_index
], short_flag
)) {
567 if (!strcmp(argv
[arg_index
], long_flag
)) {
576 bool asked_for_help(int argc
, char* argv
[]) {
577 return asked_for_flag(argc
, argv
, "-h", "--help");
580 bool asked_for_recursive(int argc
, char* argv
[]) {
581 return asked_for_flag(argc
, argv
, "-r", "--recursive");
584 bool is_flag(const char* arg
) {
586 * Is arg a command-line flag (e.g. --recursive)?
588 char valid_flags
[4][32] = { "-h",
594 for (flag_index
= 0; flag_index
< 4; flag_index
++) {
595 if (!strcmp(arg
, valid_flags
[flag_index
])) {
604 int reapply_default_acl_nftw(const char *target
,
605 const struct stat
*s
,
608 /* A wrapper around the reapply_default_acl() function for use with
609 * nftw(). We need to adjust the return value so that nftw() doesn't
610 * think we've failed.
612 bool reapp_result
= reapply_default_acl(target
);
622 bool reapply_default_acl_recursive(const char *target
) {
623 /* Attempt to reapply default ACLs recursively. If target is a
624 * directory, we recurse through its entries. If not, we just
625 * reapply the default ACL to target.
627 * We ignore symlinks for consistency with chmod -r.
630 if (!is_directory(target
)) {
631 return reapply_default_acl(target
);
634 int max_levels
= 256;
635 int flags
= FTW_PHYS
; /* Don't follow links. */
637 int nftw_result
= nftw(target
,
638 reapply_default_acl_nftw
,
642 if (nftw_result
== 0) {
647 /* nftw will return -1 on error, or if the supplied function
648 * (reapply_default_acl_nftw) returns a non-zero result, nftw will
651 if (nftw_result
== -1) {
652 perror("reapply_default_acl_recursive (nftw)");
659 int main(int argc
, char* argv
[]) {
661 * Call reapply_default_acl on each command-line argument.
668 if (asked_for_help(argc
, argv
)) {
673 int result
= EXIT_SUCCESS
;
675 bool recursive
= asked_for_recursive(argc
, argv
);
678 for (arg_index
= 1; arg_index
< argc
; arg_index
++) {
679 /* Don't bother stripping the flags from argv; just ignore
680 * an argument if it's one of our flags.
682 if (is_flag(argv
[arg_index
])) {
686 const char* target
= argv
[arg_index
];
687 bool reapp_result
= false;
690 reapp_result
= reapply_default_acl_recursive(target
);
693 /* It's either normal file, or we're not operating recursively. */
694 reapp_result
= reapply_default_acl(target
);
698 result
= EXIT_FAILURE
;