Hi

I have a system that precaches directory content at opendir call and I found that coreutils-6.7 rm -r command doesn't work on it (it used to work fine in coreutils 5).

The problem is this: when walking up to the root in directory tree, rm opens parent directory with opendir, then deletes its subdirectory with rmdir and then starts reading the parent with readdir --- readdir reads just deleted entry, rm tries to delete it again and fails.

opendir caching is allowed by standard http://www.opengroup.org/onlinepubs/007908799/xsh/readdir.html ("If a file is removed from or added to the directory after the most recent call to opendir() or rewinddir(), whether a subsequent call to readdir() returns an entry for that file is unspecified."), so the system behaves correctly and rm has a bug.

Here is a trace when I tried rm -rf /A and there was four directories /A/B/C/D --- it correctly deletes D, but reads entry for 'D' again and fails.

I made a fix --- it modifies AD_pop_and_chdir so that it only returns fd of a directory but doesn't try to do fdopendir and do fdopendir in remove_dir after the subdirectory is deleted - it seems to work but it makes the file even more messy.

Mikulas

The trace of rm -rf /A, I added printfs to opendir, readdir and closedir to see what happens:

opendir '/A' -> BFFEF950
readdir BFFEF950, pos 0: name '.', dt 4
readdir BFFEF950, pos 1: name '..', dt 4
readdir BFFEF950, pos 2: name 'B', dt 4
opendir '/A/B' -> BFFEF9F0
closedir: BFFEF950
readdir BFFEF9F0, pos 0: name '.', dt 4
readdir BFFEF9F0, pos 1: name '..', dt 4
readdir BFFEF9F0, pos 2: name 'C', dt 4
opendir '/A/B/C' -> BFFEF980
closedir: BFFEF9F0
readdir BFFEF980, pos 0: name '.', dt 4
readdir BFFEF980, pos 1: name '..', dt 4
readdir BFFEF980, pos 2: name 'D', dt 4
opendir '/A/B/C/D' -> BFFEF9C0
closedir: BFFEF980
readdir BFFEF9C0, pos 0: name '.', dt 4
readdir BFFEF9C0, pos 1: name '..', dt 4
closedir: BFFEF9C0
        --- note here: it opens the parent directory and then deletes the
        subdirectory --- the subsequent readdir will read just deleted
        entry
opendir '/A/B/C' -> BFFEBE00
removed directory: `A/B/C/D'
readdir BFFEBE00, pos 0: name '.', dt 4
readdir BFFEBE00, pos 1: name '..', dt 4
        --- here: entry 'D' no longer exists, but it is read from cached
        information on opendir
readdir BFFEBE00, pos 2: name 'D', dt 4
closedir: BFFEBE00
opendir '/A/B' -> BFFEBE00
readdir BFFEBE00, pos 0: name '.', dt 4
readdir BFFEBE00, pos 1: name '..', dt 4
readdir BFFEBE00, pos 2: name 'C', dt 4
closedir: BFFEBE00
opendir '/A' -> BFFEBE00
readdir BFFEBE00, pos 0: name '.', dt 4
readdir BFFEBE00, pos 1: name '..', dt 4
readdir BFFEBE00, pos 2: name 'B', dt 4
closedir: BFFEBE00
--- src/remove.c_       2006-12-19 05:20:54.000000000 +0200
+++ src/remove.c        2006-12-19 06:41:29.000000000 +0200
@@ -432,7 +436,7 @@
    directory (usually now empty) from which we're coming, and which
    corresponds to the input value of *DIRP.  */
 static char *
-AD_pop_and_chdir (DIR **dirp, Dirstack_state *ds)
+AD_pop_and_chdir (DIR **dirp, int *fdp, Dirstack_state *ds)
 {
   struct AD_ent *leaf_dir_ent = AD_stack_top(ds);
   struct dev_ino leaf_dev_ino = leaf_dir_ent->dev_ino;
@@ -499,22 +503,15 @@
        {
          error (0, 0, _("FATAL: directory %s changed dev/ino"),
                 quote (full_filename (".")));
-         goto close_and_next;
-       }
+         close_and_next:;
+           close (fd);
 
-      *dirp = fdopendir (fd);
-      if (*dirp == NULL)
-       {
-         error (0, errno, _("FATAL: cannot return to .. from %s"),
-                quote (full_filename (".")));
+         next_cmdline_arg:
 
-       close_and_next:;
-         close (fd);
-
-       next_cmdline_arg:;
          free (prev_dir);
          longjmp (ds->current_arg_jumpbuf, 1);
        }
+      *fdp = fd;
     }
   else
     {
@@ -524,9 +521,10 @@
                 quote (full_filename (prev_dir)));
          goto next_cmdline_arg;
        }
-      *dirp = NULL;
+      *fdp = AT_FDCWD;
     }
 
+  *dirp = NULL;
   return prev_dir;
 }
 
@@ -1384,9 +1382,9 @@
       {
        /* The name of the directory that we have just processed,
           nominally removing all of its contents.  */
-       char *empty_dir = AD_pop_and_chdir (&dirp, ds);
-       int fd = (dirp != NULL ? dirfd (dirp) : AT_FDCWD);
-       assert (dirp != NULL || AD_stack_height (ds) == 1);
+       int fd;
+       char *empty_dir = AD_pop_and_chdir (&dirp, &fd, ds);
+       assert (fd != AT_FDCWD || AD_stack_height (ds) == 1);
 
        /* Try to remove EMPTY_DIR only if remove_cwd_entries succeeded.  */
        if (tmp_status == RM_OK)
@@ -1405,6 +1403,7 @@
              {
                free (empty_dir);
                status = s;
+               if (fd != AT_FDCWD) close (fd);
                goto closedir_and_return;
              }
 
@@ -1426,7 +1425,18 @@
 
        free (empty_dir);
 
-       if (AD_stack_height (ds) == 1)
+       if (fd != AT_FDCWD)
+         {
+           dirp = fdopendir (fd);
+           if (dirp == NULL)
+             {
+               error (0, errno, _("FATAL: cannot return to .. from %s"),
+                 quote (full_filename (".")));
+               close (fd);
+               longjmp (ds->current_arg_jumpbuf, 1);
+             } 
+         }
+       else
          break;
       }
     }
_______________________________________________
Bug-coreutils mailing list
Bug-coreutils@gnu.org
http://lists.gnu.org/mailman/listinfo/bug-coreutils

Reply via email to