Mac OS X: Recursively removing files and directories on HFS file systems

Some implementations of the "rm" command line tool (and other tools that support the recursive removal of files and directories) depend on an unspecified behavior outlined below.
The unspecified behavior is outlined in the following "pseudo code":

opendir(); 
while (readdir()) unlink(); 
closedir();


The unspecified behavior is what readdir() should return after the directory has been modified. Many file systems have been implemented such that subsequent readdir() calls will return the next directory entry. The implementation of the HFS file system cannot guarantee that all enclosed files or directories will be removed using the above method.

The Mac OS X "rm" command does not exhibit this behavior because it uses the fts(3) library functions to traverse the directory hierarchy, which causes the entire contents of the directory to be read before modifying it.

The Solaris and GNU "rm" commands are known to be implemented using the readdir/unlink loop above. So, attempts to recursively remove files and directories using those tools may fail.

Symptom

You are unable to reliably recursively delete files and directories on HFS file systems using tools other than the Mac OS X "rm" command.

Products affected

Any operating system using a tool to recursively remove files on an HFS file systems

Examples: Linux, Solaris, and so forth on an NFS export of the HFS file system

Note: The Mac OS X "rm" command is able to properly execute a recursive removal of files and directories.

Solution

The readdir()/unlink() loop needs to call rewinddir() either after each unlink() call, or whenever readdir returns NULL immediately after files have been unlinked. For example:

"The following pseudocode describes this solution:"

    opendir();
    do {
        unlinkedfiles = 0;
        while(readdir()) {
            unlink();
            unlinkedfiles = 1;
        }
        if (unlinkedfiles)
            rewinddir();
    } while (unlinkedfiles);
    closedir();


The GNU coreutils "rm" command was recently updated to do this on "affected" systems. However, that code is only used if the configuration scripts can determine that the system has this issue. This requires that the GNU coreutils be built on an HFS file system (NFS-exported, if building on a non-Mac OS X platform). Alternatively, one could simply modify the GNU coreutils source to force the constant HAVE_WORKING_READDIR to be undefined or defined to be zero (0).
Published Date: Oct 7, 2016