gfs2: keep offset when splitting dir leaf blocks
authorBenjamin Marzinski <bmarzins@redhat.com>
Tue, 1 Dec 2015 14:30:34 +0000 (08:30 -0600)
committerBob Peterson <rpeterso@redhat.com>
Mon, 14 Dec 2015 18:19:34 +0000 (12:19 -0600)
Currently, when gfs2 splits a directory leaf block, the dirents that
need to be copied to the new leaf block are packed into the start of it.
This is good for space efficiency. However, if gfs2 were to copy those
dirents into the exact same offset in the new leaf block as they had in
the old block, it would be able to generate a readdir cookie based on
the dirent location, that would be guaranteed to be unique up well past
where the current code is statistically almost guaranteed to have
collisions. So, gfs2 now keeps the dirent's offset in the block the
same when it copies it to the new leaf block.

Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Bob Peterson <rpeterso@redhat.com>
fs/gfs2/dir.c

index c2486598fb8767e585a6474e739c2767927245df..4ee008c6d64b2d54e0b62d8919ae48e7d6608f08 100644 (file)
@@ -443,6 +443,27 @@ static int gfs2_dirent_last(const struct gfs2_dirent *dent,
        return 0;
 }
 
+/* Look for the dirent that contains the offset specified in data. Once we
+ * find that dirent, there must be space available there for the new dirent */
+static int gfs2_dirent_find_offset(const struct gfs2_dirent *dent,
+                                 const struct qstr *name,
+                                 void *ptr)
+{
+       unsigned required = GFS2_DIRENT_SIZE(name->len);
+       unsigned actual = GFS2_DIRENT_SIZE(be16_to_cpu(dent->de_name_len));
+       unsigned totlen = be16_to_cpu(dent->de_rec_len);
+
+       if (ptr < (void *)dent || ptr >= (void *)dent + totlen)
+               return 0;
+       if (gfs2_dirent_sentinel(dent))
+               actual = 0;
+       if (ptr < (void *)dent + actual)
+               return -1;
+       if ((void *)dent + totlen >= ptr + required)
+               return 1;
+       return -1;
+}
+
 static int gfs2_dirent_find_space(const struct gfs2_dirent *dent,
                                  const struct qstr *name,
                                  void *opaque)
@@ -682,6 +703,27 @@ static void dirent_del(struct gfs2_inode *dip, struct buffer_head *bh,
        prev->de_rec_len = cpu_to_be16(prev_rec_len);
 }
 
+
+static struct gfs2_dirent *do_init_dirent(struct inode *inode,
+                                         struct gfs2_dirent *dent,
+                                         const struct qstr *name,
+                                         struct buffer_head *bh,
+                                         unsigned offset)
+{
+       struct gfs2_inode *ip = GFS2_I(inode);
+       struct gfs2_dirent *ndent;
+       unsigned totlen;
+
+       totlen = be16_to_cpu(dent->de_rec_len);
+       BUG_ON(offset + name->len > totlen);
+       gfs2_trans_add_meta(ip->i_gl, bh);
+       ndent = (struct gfs2_dirent *)((char *)dent + offset);
+       dent->de_rec_len = cpu_to_be16(offset);
+       gfs2_qstr2dirent(name, totlen - offset, ndent);
+       return ndent;
+}
+
+
 /*
  * Takes a dent from which to grab space as an argument. Returns the
  * newly created dent.
@@ -691,31 +733,25 @@ static struct gfs2_dirent *gfs2_init_dirent(struct inode *inode,
                                            const struct qstr *name,
                                            struct buffer_head *bh)
 {
-       struct gfs2_inode *ip = GFS2_I(inode);
-       struct gfs2_dirent *ndent;
-       unsigned offset = 0, totlen;
+       unsigned offset = 0;
 
        if (!gfs2_dirent_sentinel(dent))
                offset = GFS2_DIRENT_SIZE(be16_to_cpu(dent->de_name_len));
-       totlen = be16_to_cpu(dent->de_rec_len);
-       BUG_ON(offset + name->len > totlen);
-       gfs2_trans_add_meta(ip->i_gl, bh);
-       ndent = (struct gfs2_dirent *)((char *)dent + offset);
-       dent->de_rec_len = cpu_to_be16(offset);
-       gfs2_qstr2dirent(name, totlen - offset, ndent);
-       return ndent;
+       return do_init_dirent(inode, dent, name, bh, offset);
 }
 
-static struct gfs2_dirent *gfs2_dirent_alloc(struct inode *inode,
-                                            struct buffer_head *bh,
-                                            const struct qstr *name)
+static struct gfs2_dirent *gfs2_dirent_split_alloc(struct inode *inode,
+                                                  struct buffer_head *bh,
+                                                  const struct qstr *name,
+                                                  void *ptr)
 {
        struct gfs2_dirent *dent;
        dent = gfs2_dirent_scan(inode, bh->b_data, bh->b_size,
-                               gfs2_dirent_find_space, name, NULL);
+                               gfs2_dirent_find_offset, name, ptr);
        if (!dent || IS_ERR(dent))
                return dent;
-       return gfs2_init_dirent(inode, dent, name, bh);
+       return do_init_dirent(inode, dent, name, bh,
+                             (unsigned)(ptr - (void *)dent));
 }
 
 static int get_leaf(struct gfs2_inode *dip, u64 leaf_no,
@@ -1051,10 +1087,11 @@ static int dir_split_leaf(struct inode *inode, const struct qstr *name)
                if (!gfs2_dirent_sentinel(dent) &&
                    be32_to_cpu(dent->de_hash) < divider) {
                        struct qstr str;
+                       void *ptr = ((char *)dent - obh->b_data) + nbh->b_data;
                        str.name = (char*)(dent+1);
                        str.len = be16_to_cpu(dent->de_name_len);
                        str.hash = be32_to_cpu(dent->de_hash);
-                       new = gfs2_dirent_alloc(inode, nbh, &str);
+                       new = gfs2_dirent_split_alloc(inode, nbh, &str, ptr);
                        if (IS_ERR(new)) {
                                error = PTR_ERR(new);
                                break;