Occasionally by mistake I end up mv
ing (moving) my main
organisation directories out of my home directory into some random
sub directory. It is fairly easily done accidentally with:
cd
mv FILE * DESTINATION/
which in turn is fairly easy to enter if the tab completion on the
thing you were intending to move turns out to complete to exactly
one file (and thus not need the *
), and so tab-expansion automatically
adds a space in: if you do not realise before hitting return some
of your files/directories vanish inside that destination directory.
After doing this a few times I noticed that certain directories are not affected. Obviously the top directory of wherever I am moving into is not affected, eg:
mv: rename misc to misc/foo/bar/misc: Invalid argument
because that would create a cycle. But much more interestingly most of the "default" directories (ie, created for each user) are also not moved, for a different reason:
mv: rename Desktop to misc/foo/bar/Desktop: Permission denied
I wanted to replicate that behaviour for my own directories in my home directory and went looking for the solution.
This StackExchange question about permission denied
gives a hint to what is happening. In particular some of the directories
have a +
or @
on the end of their permissions mask in a ls -la
output. For instance:
ewen@ashram:~$ ls -ald Desktop/
drwxr-xr-x+ 9 ewen _lpoperator 306 28 May 14:02 Desktop/
ewen@ashram:~$
And looking closely, all of the directories which cannot be moved
have similar symbols. Library
has an @
:
ewen@ashram:~$ ls -ald Library
drwx------@ 55 ewen staff 1870 9 May 10:21 Library
ewen@ashram:~$
Those markers (+
, @
) indicate that there are additional attributes
or permissions fields on the files; and the @
seems to supercede
the +
, so an item showing @
may also have attributes that would
warrant the +
being displayed. The ls(1)
manpage explains:
If the file or directory has extended attributes, the permissions
field printed by the -l option is followed by a '@' character.
Otherwise, if the file or directory has extended security information
(such as an access control list), the permissions field printed by
the -l option is followed by a '+' character.
To see the extended attributes, use the "-O
" flag to ls
, eg:
ewen@ashram:~$ ls -Old Library
drwx------@ 55 ewen staff hidden 1870 9 May 10:21 Library
ewen@ashram:~$
which will show an additional column with "file flags" in the long output; this is an OS X extension I think.
To see the access control list (ACL) use the "-e
" flag to ls
, eg:
ewen@ashram:~$ ls -eld Desktop/
drwxr-xr-x+ 9 ewen _lpoperator 306 28 May 14:02 Desktop/
0: group:everyone deny delete
ewen@ashram:~$ ls -led Library/
drwx------@ 55 ewen staff 1870 9 May 10:21 Library/
0: group:everyone deny delete
ewen@ashram:~$
(It is also possible to use xattr
to show more detail on the "@
"
attributes, down to their binary coding; but at least in older
versions of OS X, such as Mavericks/10.9, there is no getfacl
or
setfacl
-- unlike FreeBSD, which has both
getfacl(1)
and
setfacl(1).
However it appears later versions of OS X do have these utilities.)
As a comment on that StackExchange question explains
the "deny delete
" ACL "also prevents renaming". So clearly it is the
ACL that I want to add to my own directories to give them similar
protection.
This blog post on ACLs on Mac OS X
explains that on OS X the ACLs are managed with the chmod
command,
and a comment on this StackExchange question about matching permissions
gives a function for copying the ACLs -- basically using ls -eld
to
fetch the ACL, and chmod -E
to apply it to the target. According
to the chmod(1)
manpage, on OS X the chmod -E
command:
-E Reads the ACL information from stdin, as a sequential list of
ACEs, separated by newlines. If the information parses cor-
rectly, the existing information is replaced.
so will replace the existing ACL information with the new ACL information.
So we can fetch the ACL that we need with:
ls -led Movies | sed -En '/^ [0-9]+: / { s/^ [0-9]+: //; p; }'
(thank to the StackExchange answer for hints to that regex; rewritten to make it more "markdown friendly") which tells us that we need:
group:everyone deny delete
and then apply that to the item that we want to protect with:
echo "group:everyone deny delete" | chmod -E TARGET1 # [TARGET2 ...]
For certainty, we can then check the targets with:
ls -led TARGET1 # [TARGET2 ...]
to make sure they ACLs were updated.
Finally we can verify that we get the protection against accidental movements that we desire with:
ewen@ashram:~$ mv work misc/notes/
mv: rename work to misc/notes/work: Permission denied
ewen@ashram:~$
This does mean that if there is ever a legitimate reason to move those items, we will have to remove the ACL. But it is a very useful addition to items that should always be in that directory. (It may also mean that the normal editor update cycle -- write new, mv new over old -- will not work; so this ACL trick is probably best reserved for directories rather than frequently edited files.)