1.7.7: rm -rf sometimes fails - race condition?

Matthias Andree matthias.andree@gmx.de
Sun Dec 12 15:14:00 GMT 2010


Am 12.12.2010 13:42, schrieb Corinna Vinschen:

> So, what cygwin tries to do in the first place is to move files in use
> into the recycle bin.  However, on Windows you need DELETE access rights
> to be able to do so.  And, this doesn't work for remote drives.  On
> remote drives we can only try to rename the file to some temporary
> filename and hope for the best.  Afterwards Cygwin sets the delete
> dispostion flag and returns success if setting the dispostion flag
> succeeded.  After all, that's the maximum possible on Windows, and for
> all we can tell the file has been deleted.  The fact that the directory
> entry lingers until the last handle to the file has been closed is
> something Cygwin has no control over.

Well, there's the problem.

1. Assume you have this tree:

dir/
dir/file1
dir/file2

2. Assume that dir/file2 is in use.

3. Assume you have sufficient rights and run: rm -rf dir

Cygwin deletes file1, sets the delete disposition for file2, and unlink("file2")
returns before the file is really gone.

rm.exe believes the directory is empty because it deleted all files
successfully.  HOWEVER when rm then runs rmdir("dir"), that fails, because
Cygwin does not mask the Windows artifact that the file removal is deferred.

There is a mismatch between what Cygwin reported to the application (last file
successfully removed) and what is actually there.

Now rm -rf gets rmdir("dir") == -1 with errno == ENOTEMPTY.  This rmdir()
failure is bogus.

Now I think that rmdir() needs to jump through the same hoops as unlink(), only
it is even more complex because Cygwin needs to track or check if all files
(including sub-directories) in a directory have been unlink()ed and rmdir()ed

I think that rmdir() must not fail with ENOTEMPTY or EEXIST if all files have
been successfully removed.


This seems to be racey, the program below does not trigger the problem, so there
must be something else that prevents unlink() from working properly, perhaps
mandatory locks (possibly on the Windows side), or with (deeply) nested
directories.  I don't have time to extend the test case now to reproduce how
"cygport" fails for me.

Is there a chance that Cygwin's rmdir() might be extended to ensure this? I
mean, in a not-too-distant 1.7.X release?

/* DOES NOT SHOW THE PROBLEM AS-IS */
/* test program cyg-rmdir.c, compile with:
   gcc -Wall -Wextra -O -o cyg-rmdir{,.c} -pedantic-errors -std=c89 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

static int fd = -1, f2 = -1;

static void fail(const char *s) {
        perror(s);
        if (fd != -1) close(fd);
        if (f2 != -1) close(f2);
        (void)unlink("dir/file"); /* ignore failure */
        (void)rmdir("dir"); /* ignore failure */
        exit(EXIT_FAILURE);
}

static void child(void) {
        f2 = open("dir/file", O_RDONLY);
        if (f2 == -1) fail("child: open");
        sleep(3);
        close(f2);
        _exit(EXIT_SUCCESS);
}

int main(void) {
        if (mkdir("dir", 0755)) fail("mkdir");
        if ((fd = open("dir/file", O_CREAT|O_RDWR, 0644)) == -1) fail("open");
        if (write(fd, "test\n", 5) != 5) fail("write");
        if (fsync(fd)) fail("fsync");
        switch(fork()) {
                case 0:  child(); _exit(EXIT_FAILURE); break;
                case -1: fail("fork");
                default: /* parent */ break;
        }
        sleep(1);
        if (unlink("dir/file")) fail("unlink");
        if (rmdir("dir")) fail("rmdir");
        if (close(fd)) fail("close");
        sleep(2);
        puts("success");
        return EXIT_SUCCESS;
}
/* END Of cyg-rmdir.c */

-- 
Matthias Andree

--
Problem reports:       http://cygwin.com/problems.html
FAQ:                   http://cygwin.com/faq/
Documentation:         http://cygwin.com/docs.html
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple



More information about the Cygwin mailing list