OSX mv and File.renameTo() strangeness

I’ve come across an annoying behavior in OSX which I’m documenting here mostly in the hopes that anyone else struggling to track down a similar bug will find this post in Google. (This’ll probably be quite dull to non-Unix geeks…)

My original symptom:

Java’s File.renameTo command won’t work when moving files from /tmp to a user directory encrypted with FileVault.

The actual cause (near as I can tell):

  • In Darwin/OSX (and in BSD), when a file is copied or created in a new directory it automatically takes on the GID (Group ID) of the target directory.

  • A file that is renamed (using the mv command or Java’s File.renameTo) should not change its GID, even if the target directory’s GID is different.

  • The /tmp directory is set with the group “wheel,” which before OSX 10.2 users with admin privileges were in but that’s no longer the case. This means normal users may not change a file to the group “wheel” without invoking admin privileges.

So here’s what was happening. First I created a new file in /tmp. The group ID on the file was automatically set to “wheel” on creation because that’s the GID for /tmp. Moving the file to another directory on the same disk works just fine because under the hood the OS is just swapping around pointers on the disk. However, when I tried to move the file to a directory on a different virtual disk (which is how OSX thinks of FileVault), it first copies the data and then tries to change the group ID of the newly created file to “wheel,” which it doesn’t have permission to do. If I use mv to do the move I get an error message but otherwise the file is moved correctly (albeit with my own group ID instead of wheel). If I use the Java routine File.renameTo(destination) it simply returns false (failure) and refuses to do the move — I suspect it realizes it can’t do it perfectly so it doesn’t even try.

You can get the same effect just moving a file from /tmp to an external firewire drive. In the snippit below, the directory ~bug/ is on the same local disk as /tmp and /Volumes/disk2/ is a mounted firewire disk:

$ ls -ld /tmp/
drwxrwxrwt 19 root wheel 646 Sep 27 20:54 /tmp/

$ groups
bug appserveradm appserverusr admin

$ touch /tmp/test1 /tmp/test2

$ ls -l /tmp/test*
-rw-r--r-- 1 bug wheel 0 Sep 27 20:54 /tmp/test1
-rw-r--r-- 1 bug wheel 0 Sep 27 20:54 /tmp/test2

$ mv /tmp/test1 ~bug/

$ ls -l ~bug/test1
-rw-r--r-- 1 bug wheel 0 Sep 27 20:54 test1

$ mv /tmp/test2 /Volumes/disk2/
mv: /Volumes/Blackjack/test2: set owner/group (was: 502/0): Operation not permitted

$ ls -l /Volumes/disk2/test2
-rw-r--r-- 1 bug bug 0 Sep 27 20:54 /Volumes/disk2/test2