Bash – How to recursively apply default ACLs of a directory to its descendants

aclbashcommand linepermissions

If I have a directory with default ACL entries assigned to it, but some or all of its descendant files and sub-directories have access or default ACLs that do not agree with those defaults, how do I recursively (re)apply those default ACL entries to all descendants? There is a qualification. I wish to entirely replace all existing descendant ACLs except for the existing execute status of files. The intent otherwise is that all descendants should have the ACLs they would have if newly created in their current location.

Rogach asked "How do I set permissions recursively on a dir (with ACL enabled)?" but was looking to add a new ACL entry recursively rather than (re)applying an existing default ACL. Although not asked for by Rogach, Franklin Piat's answer to that question also offers a solution to my question, but I believe his solution is flawed. Piat suggests this pair of commands:

find . -mindepth 1 -type d| xargs -n 50 setfacl -b --set-file=<(getfacl . | sed -e 's/x$/X/')
find . -mindepth 1 -type f| xargs -n 50 setfacl -b --set-file=<(getfacl . | grep -v '^default:' | sed -e 's/x$/X/')

The flaws that I want to fix:

  • The current directory's access ACL is being propagated to descendants instead of its default ACL, which is not how I understand ACLs should work.
  • The execute bit is cleared for all files (sed is ineffective).
  • Typical sudoer policies prevent process substitution from working when root privileges are required. See "closefrom_override" option for sudoers(5).

Ideally I would also only need one command line to accomplish this one task.

Best Answer

I have come up with this ugly, inefficient solution but surely there's a better way:

( cd '/some/dir' && sudo find . -mindepth 1 -path './leave/alone' -prune -o \( -type d -exec bash -c 'getfacl -cd . | sed -nre "p;/^\$/!s/^/default:/p" | setfacl -bM - "{}"' \; \) -o \( -type f -exec bash -c 'getfacl -cd . | sed -re "/^#/!s/x\$/X/" | setfacl -bM - "{}"' \; \) )

Some comments about that monstrosity:

  • I've included a sub-directory exclusion to demonstrate that frequent requirement.
  • The cd eliminates the error-prone repetition of the source directory path and the outer parentheses make that change temporary.
  • The use of bash is needed because "-exec" doesn't allow pipelines. It also avoids that '!' character invoking history substitution and resulting in the infamous “event not found” error.
  • For sub-directories we must apply the source default ACLs as both access ACLs and default ACLs.
  • For files we must apply source default ACLs as access ACLs but apply no default ACLs at all if we wish to avoid the “Only directories can have default ACLs” warnings from setfacl.
  • For files we need to replace 'x' permissions from the source default ACLs with 'X' in order that we don't end up forcing all files to become executable. This solution also preserves the executable status of existing files, but only because we use “-bM -” rather than “--set-file=-” with setfacl.
  • This command line is very inefficient because I don't know of a way to use “-exec ... +” or xargs instead of “-exec ... \;”.
  • This command line works, but beware that descendant files get ACLs that are effectively equivalent but not technically equivalent to what occurs when newly creating a new descendant file.

The "Unix philosophy" is fantastic, but the task I'm trying to accomplish is needed too often for it to require such an unwieldy command line. Is there a better way aside from lobbying for additional features in setfacl?