]> git.baikalelectronics.ru Git - kernel.git/commitdiff
btrfs: don't loop on large offsets in readdir
authorZach Brown <zab@redhat.com>
Thu, 11 Jul 2013 23:19:42 +0000 (16:19 -0700)
committerChris Mason <chris.mason@fusionio.com>
Fri, 9 Aug 2013 23:34:56 +0000 (19:34 -0400)
When btrfs readdir() hits the last entry it sets the readdir offset to a
huge value to stop buggy apps from breaking when the same name is
returned by readdir() with concurrent rename()s.

But unconditionally setting the offset to INT_MAX causes readdir() to
loop returning any entries with offsets past INT_MAX.  It only takes a
few hours of constant file creation and removal to create entries past
INT_MAX.

So let's set the huge offset to LLONG_MAX if the last entry has already
overflowed 32bit loff_t.   Without large offsets behaviour is identical.
With large offsets 64bit apps will work and 32bit apps will be no more
broken than they currently are if they see large offsets.

Signed-off-by: Zach Brown <zab@redhat.com>
Signed-off-by: Josef Bacik <jbacik@fusionio.com>
Signed-off-by: Chris Mason <chris.mason@fusionio.com>
fs/btrfs/inode.c

index c72033ee6017b09325a752c0e3bb760aa6ffc147..021694c081814828272ff03a8b9e1f64732a46cc 100644 (file)
@@ -5170,14 +5170,31 @@ next:
        }
 
        /* Reached end of directory/root. Bump pos past the last item. */
-       if (key_type == BTRFS_DIR_INDEX_KEY)
-               /*
-                * 32-bit glibc will use getdents64, but then strtol -
-                * so the last number we can serve is this.
-                */
-               ctx->pos = 0x7fffffff;
-       else
-               ctx->pos++;
+       ctx->pos++;
+
+       /*
+        * Stop new entries from being returned after we return the last
+        * entry.
+        *
+        * New directory entries are assigned a strictly increasing
+        * offset.  This means that new entries created during readdir
+        * are *guaranteed* to be seen in the future by that readdir.
+        * This has broken buggy programs which operate on names as
+        * they're returned by readdir.  Until we re-use freed offsets
+        * we have this hack to stop new entries from being returned
+        * under the assumption that they'll never reach this huge
+        * offset.
+        *
+        * This is being careful not to overflow 32bit loff_t unless the
+        * last entry requires it because doing so has broken 32bit apps
+        * in the past.
+        */
+       if (key_type == BTRFS_DIR_INDEX_KEY) {
+               if (ctx->pos >= INT_MAX)
+                       ctx->pos = LLONG_MAX;
+               else
+                       ctx->pos = INT_MAX;
+       }
 nopos:
        ret = 0;
 err: