This is the mail archive of the cygwin mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: 1.7.7: rm -rf sometimes fails - race condition?


Matthias Andree wrote at 15:16 +0100 on Dec 12, 2010:
 > 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.
[...]
 > /* 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

By the way, it's not unix vs. cygwin.  NFS on unix has the same
issue.  Have you ever seen those .nfs* files lying around?

cd /some/nfs/mount
mkdir dir
echo sleep 99 > dir/foo
sh dir/foo&
rm -rf dir
rm: dir: Directory not empty
ls -al dir
total 117
drwxr-xr-x   2 jhein  jhein     3 Dec 12 09:10 ./
drwxrwxrwt  49 jhein  jhein  1038 Dec 12 09:10 ../
-rw-r--r--   1 jhein  jhein     9 Dec 12 09:10 .nfs.67021bc0.ffff951e4.4

Furthermore, in your test, try changing to 'dir' and exec'ing some long
running process in the child.  Then you should see the rmdir failure
until the child exits.

Here's your test program slightly modified (below).  Invoke 'cyg-rmdir
fail' to see the rmdir fail (even though ls shows the dir empty).
Again, NFS will fail as well as cygwin.

Interestingly, if you don't cd before exec, the rmdir succeeds (in
cygwin).  Invoke the new test as 'cyg-rmdir failnot'.  NFS fails in
both cases.  Only cygwin succeeds if you don't chdir(2) in first.

/* 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 <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

static int fd = -1, failcase = 0;

static void fail(const char *s) {
        perror(s);
        (void)unlink("dir/file"); /* ignore failure */
        (void)rmdir("dir"); /* ignore failure */
        exit(EXIT_FAILURE);
}

static void child(void) {
        if (failcase == 0) {
            /* this does not make the rmdir in the parent fail */
            execl("dir/file", "file", NULL);
            fail("exec failed");
        }
        /* chdir + exec makes the rmdir in the parent fail */
        if (chdir("dir") != 0) fail("chdir");
        execl("./file", "file", NULL);
        fail("exec failed");
}

int main(int argc, char **argv) {
        char *prog="#!/bin/sh\nsleep 10\n";
        int len = strlen(prog);
        if (argc > 1 && strcmp(argv[1], "fail")==0)
            failcase = 1;
        if (mkdir("dir", 0755)) fail("mkdir");
        if ((fd = open("dir/file", O_CREAT|O_RDWR, 0755)) == -1) fail("open");
        if (write(fd, prog, len) != len) fail("write");
        if (close(fd)) fail("close");
        system("set -x;cat dir/file;ls -al dir");
        switch(fork()) {
                case 0:  child(); _exit(EXIT_FAILURE); break;
                case -1: fail("fork");
                default: /* parent */ break;
        }
        sleep(1);
        system("set -x;echo before unlinks;ls -al . dir");
        if (unlink("dir/file")) fail("unlink");
        system("set -x;ls -al . dir");
        if (rmdir("dir")) fail("rmdir");
        sleep(2);
        puts("success");
        return EXIT_SUCCESS;
}


--
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


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]