Whamcloud - gitweb
LU-17711 osd-ldiskfs: do not delete dotdot during rename 23/54723/10
authorLi Dongyang <dongyangli@ddn.com>
Wed, 17 Apr 2024 05:36:55 +0000 (15:36 +1000)
committerOleg Drokin <green@whamcloud.com>
Mon, 10 Jun 2024 06:08:53 +0000 (06:08 +0000)
Since upstream kernel commit v5.12-rc4-32-g6c0912739699
ext4_dir_entry_2 after rec_len will be wiped when deleting
the entry.

This creates a problem with rename, when we delete dotdot
first and if it's a dx dir, kernel will wipe entire dx_root
in the block after dotdot entry.

We can just update the dotdot entry in-place without deleting.

For dx dirs, ext4_update_dotdot() takes care of dotdot and
inserting dotdot is an update, use it for linear dirs also.

Rewrite ext4_update_dotdot() to get a few fixes:
*use ext4_read_dirblock to get the first block.
*do not assert on data read from disk, we check the dot and
dotdot entry and if anything looks wrong, we return -EFSCORRUPTED.
*make sure the change is journalled.
*set metadata_csum correctly for dx dirs.

Update ext4-data-in-dirent.patch, if dotdot entry has no space
for dirdata, try to expand the dotdot entry by moving the
entries behind it, or move the dx_root for dx dirs.

Add conf-sanity/154 to verify that the ".." entry was updated
properly after restore, including with an htree split directory
with dx_root entry.

Signed-off-by: Li Dongyang <dongyangli@ddn.com>
Change-Id: I33e862739fa44f583aaa4369190d6d80271db13b
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/54723
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Shaun Tancheff <shaun.tancheff@hpe.com>
Reviewed-by: Jian Yu <yujian@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
54 files changed:
ldiskfs/kernel_patches/patches/linux-5.10/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/linux-5.10/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/linux-5.4/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/linux-5.4/ext4-hash-indexed-dir-dotdot-update.patch
ldiskfs/kernel_patches/patches/linux-5.4/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/linux-5.8/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/linux-6.0/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/linux-6.0/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/oe2203/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/oe2203sp1/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/rhel7.6/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
ldiskfs/kernel_patches/patches/rhel7.9/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/rhel8.4/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/rhel8.4/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/rhel8.7/ext4-hash-indexed-dir-dotdot-update.patch [new file with mode: 0644]
ldiskfs/kernel_patches/patches/rhel8.7/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/rhel8/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/rhel9.1/ext4-data-in-dirent.patch [moved from ldiskfs/kernel_patches/patches/linux-5.18/ext4-data-in-dirent.patch with 86% similarity]
ldiskfs/kernel_patches/patches/rhel9.1/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/rhel9.2/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/rhel9.2/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/rhel9.3/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/rhel9/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/rhel9/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/sles15sp1/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/sles15sp3/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/sles15sp3/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/sles15sp4/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/sles15sp4/ext4-hash-indexed-dir-dotdot-update.patch
ldiskfs/kernel_patches/patches/sles15sp4/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/ubuntu18/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch [deleted file]
ldiskfs/kernel_patches/patches/ubuntu18/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/ubuntu20.04.5/ext4-data-in-dirent.patch
ldiskfs/kernel_patches/patches/ubuntu20/ext4-pdirop.patch
ldiskfs/kernel_patches/patches/ubuntu2004/ext4-pdirop.patch
ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15-22.series
ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15.series
ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1-7.series
ldiskfs/kernel_patches/series/ldiskfs-4.12-sles15sp1.series
ldiskfs/kernel_patches/series/ldiskfs-4.15.0-20-ubuntu18.series
ldiskfs/kernel_patches/series/ldiskfs-4.15.0-24-ubuntu18.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.4.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.5.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.6.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.7.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.8.series
ldiskfs/kernel_patches/series/ldiskfs-4.18-rhel8.9.series
ldiskfs/kernel_patches/series/ldiskfs-5.0.0-13-ubuntu19.series
ldiskfs/kernel_patches/series/ldiskfs-5.14-rhel9.1.series
lustre/osd-ldiskfs/osd_handler.c
lustre/tests/conf-sanity.sh
lustre/tests/test-framework.sh

index e637a68..4ed99b8 100644 (file)
@@ -173,7 +173,7 @@ diff -ur a/fs/ext4/ext4.h b/fs/ext4/ext4.h
                             void *buf, int buf_size,
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
-+                           struct ext4_dir_entry_2 **dest_de, int *dlen);
++                           struct ext4_dir_entry_2 **dest_de, int dlen);
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -278,7 +278,7 @@ diff -ur a/fs/ext4/inline.c b/fs/ext4/inline.c
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -434,26 +434,20 @@ diff -ur a/fs/ext4/namei.c b/fs/ext4/namei.c
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -1959,14 +1971,16 @@
+@@ -1959,10 +1971,10 @@
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
-+                    struct ext4_dir_entry_2 **dest_de, int *dlen)
++                    struct ext4_dir_entry_2 **dest_de, int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname));
-+      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname)) +
-+                                                (dlen ? *dlen : 0);
++      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname) + dlen);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      dlen ? *dlen = 0 : 0; /* default set to 0 */
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -1975,10 +1989,26 @@
+@@ -1975,7 +1989,7 @@
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -462,25 +456,6 @@ diff -ur a/fs/ext4/namei.c b/fs/ext4/namei.c
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          EXT4_DIR_REC_LEN(fname_len(fname))) {
-+                              /* set dlen=1 to indicate not
-+                               * enough space store fid */
-+                              dlen ? *dlen = 1 : 0;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO. */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -1992,12 +2022,12 @@
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -526,18 +501,15 @@ diff -ur a/fs/ext4/namei.c b/fs/ext4/namei.c
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2047,7 +2087,10 @@
+@@ -2047,7 +2087,7 @@
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(inode, de, blocksize, fname, data);
  
        /*
@@ -552,49 +524,152 @@ diff -ur a/fs/ext4/namei.c b/fs/ext4/namei.c
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2202,6 +2246,8 @@
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+@@ -2187,7 +2210,104 @@ out_frames:
+       return retval;
+ }
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de);
++
++              dx_info = dx_get_dx_info(dot_de);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen) + sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen), blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2196,6 +2316,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2227,11 +2273,16 @@
-                       goto out_journal;
+@@ -2235,6 +2357,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2248,7 +2299,12 @@
-               assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2286,6 +2342,7 @@
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index 077e8cc..6047c64 100644 (file)
@@ -793,9 +793,9 @@ Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2375,9 +2714,10 @@ static int ext4_add_entry(handle_t *hand
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index 6f34417..d87a0fa 100644 (file)
@@ -167,7 +167,7 @@ index cb649f0..fe35251 100644
                             void *buf, int buf_size,
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
-+                           struct ext4_dir_entry_2 **dest_de, int *dlen);
++                           struct ext4_dir_entry_2 **dest_de, int dlen);
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -251,7 +251,7 @@ index 2fec62d..3f35821 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -408,26 +408,20 @@ index 8713671..23bd871 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -1943,14 +1955,16 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -1943,10 +1955,10 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
-+                    struct ext4_dir_entry_2 **dest_de, int *dlen)
++                    struct ext4_dir_entry_2 **dest_de, int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname));
-+      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname)) +
-+                                                (dlen ? *dlen : 0);
++      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname) + dlen);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      dlen ? *dlen = 0 : 0; /* default set to 0 */
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -1959,10 +1973,26 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -1959,7 +1973,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -436,25 +430,6 @@ index 8713671..23bd871 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          EXT4_DIR_REC_LEN(fname_len(fname))) {
-+                              /* set dlen=1 to indicate not
-+                               * enough space store fid */
-+                              dlen ? *dlen = 1 : 0;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO. */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -1976,12 +2006,12 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -500,18 +475,15 @@ index 8713671..23bd871 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2031,7 +2071,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2031,7 +2071,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(inode, de, blocksize, fname, data);
  
        /*
@@ -526,49 +498,152 @@ index 8713671..23bd871 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2186,6 +2230,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+@@ -2197,8 +2220,104 @@ out_frames:
+       brelse(bh2);
+       return retval;
+ }
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de);
++
++              dx_info = dx_get_dx_info(dot_de);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen) + sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen), blocksize);
++      }
++
++      return 0;
++}
+-/* update ".." entry */
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2207,6 +2326,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2211,11 +2257,16 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-                       goto out_journal;
+@@ -2246,6 +2368,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2232,7 +2283,12 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-               assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2271,6 +2327,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index ef35db0..2c057b8 100644 (file)
@@ -11,18 +11,19 @@ diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
 index f54e868..14ff68e 100644
 --- a/fs/ext4/namei.c
 +++ b/fs/ext4/namei.c
-@@ -2174,6 +2174,74 @@ out_frames:
+@@ -2174,6 +2174,67 @@ out_frames:
        return retval;
  }
  
-+/* update ".." for hash-indexed directory, split the item "." if necessary */
++/* update ".." entry */
 +static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
 +                            struct inode *inode)
 +{
 +      struct inode *dir = dentry->d_parent->d_inode;
-+      struct buffer_head *dir_block;
-+      struct ext4_dir_entry_2 *de;
-+      int len, journal = 0, err = 0;
++      struct buffer_head *bh;
++      struct ext4_dir_entry_2 *dot_de, *dotdot_de;
++      unsigned int offset;
++      int retval = 0;
 +
 +      if (IS_ERR(handle))
 +              return PTR_ERR(handle);
@@ -30,72 +31,65 @@ index f54e868..14ff68e 100644
 +      if (IS_DIRSYNC(dir))
 +              handle->h_sync = 1;
 +
-+      dir_block = ext4_bread(handle, dir, 0, 0);
-+      if (IS_ERR(dir_block)) {
-+              err = PTR_ERR(dir_block);
++      bh = ext4_read_dirblock(dir, 0, DIRENT_HTREE);
++      if (IS_ERR(bh))
++              return PTR_ERR(bh);
++
++      dot_de = (struct ext4_dir_entry_2 *) bh->b_data;
++      if (ext4_check_dir_entry(dir, NULL, dot_de, bh, bh->b_data,
++                               bh->b_size, 0) ||
++          le32_to_cpu(dot_de->inode) != dir->i_ino ||
++          strcmp(".", dot_de->name)) {
++              EXT4_ERROR_INODE(dir, "directory missing '.'");
++              retval = -EFSCORRUPTED;
 +              goto out;
 +      }
-+
-+      de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-+      /* the first item must be "." */
-+      assert(de->name_len == 1 && de->name[0] == '.');
-+      len = le16_to_cpu(de->rec_len);
-+      assert(len >= EXT4_DIR_REC_LEN(1));
-+      if (len > EXT4_DIR_REC_LEN(1)) {
-+              BUFFER_TRACE(dir_block, "get_write_access");
-+              err = ext4_journal_get_write_access(handle, dir_block);
-+              if (err)
-+                      goto out_journal;
-+
-+              journal = 1;
-+              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
++      offset = ext4_rec_len_from_disk(dot_de->rec_len,
++                                      dir->i_sb->s_blocksize);
++      dotdot_de = ext4_next_entry(dot_de, dir->i_sb->s_blocksize);
++      if (ext4_check_dir_entry(dir, NULL, dotdot_de, bh, bh->b_data,
++                               bh->b_size, offset) ||
++          le32_to_cpu(dotdot_de->inode) == 0 ||
++          strcmp("..", dotdot_de->name)) {
++              EXT4_ERROR_INODE(dir, "directory missing '..'");
++              retval = -EFSCORRUPTED;
++              goto out;
 +      }
 +
-+      len -= EXT4_DIR_REC_LEN(1);
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      de = (struct ext4_dir_entry_2 *)
-+                      ((char *) de + le16_to_cpu(de->rec_len));
-+      if (!journal) {
-+              BUFFER_TRACE(dir_block, "get_write_access");
-+              err = ext4_journal_get_write_access(handle, dir_block);
-+              if (err)
-+                      goto out_journal;
-+      }
++      BUFFER_TRACE(dir_block, "get_write_access");
++      retval = ext4_journal_get_write_access(handle, bh);
++      if (retval)
++              goto out;
 +
-+      de->inode = cpu_to_le32(inode->i_ino);
-+      if (len > 0)
-+              de->rec_len = cpu_to_le16(len);
-+      else
-+              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+      de->name_len = 2;
-+      strcpy(de->name, "..");
-+      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
++      dotdot_de->inode = cpu_to_le32(inode->i_ino);
 +
-+out_journal:
-+      if (journal) {
-+              BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
-+              err = ext4_handle_dirty_dirblock(handle, dir, dir_block);
-+              ext4_mark_inode_dirty(handle, dir);
++      ext4_mark_inode_dirty(handle, dir);
++      BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
++      if (is_dx(dir)) {
++              retval = ext4_handle_dirty_dx_node(handle, dir, bh);
++      } else {
++              retval = ext4_handle_dirty_dirblock(handle, dir, bh);
 +      }
-+      brelse(dir_block);
 +
 +out:
-+      return err;
++      brelse(bh);
++      return retval;
 +}
 +
  /*
   *    ext4_add_entry()
   *
-@@ -2229,6 +2297,9 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
+@@ -2228,6 +2296,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
+               }
        }
  
++      if (dentry->d_name.len == 2 &&
++                      memcmp(dentry->d_name.name, "..", 2) == 0)
++              return ext4_update_dotdot(handle, dentry, inode);
++
        if (is_dx(dir)) {
-+              if (dentry->d_name.len == 2 &&
-+                   memcmp(dentry->d_name.name, "..", 2) == 0)
-+                       return ext4_update_dotdot(handle, dentry, inode);
                retval = ext4_dx_add_entry(handle, &fname, dir, inode);
                if (!retval || (retval != ERR_BAD_DX_DIR))
-                       goto out;
 -- 
 2.20.1
 
index 89a3825..f849a99 100644 (file)
@@ -800,9 +800,9 @@ Index: linux-stage/fs/ext4/namei.c
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2375,9 +2714,10 @@ static int ext4_add_entry(handle_t *hand
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index 245ed84..98922e4 100644 (file)
@@ -170,7 +170,7 @@ Signed-off-by: Andreas Dilger <andreas.dilger@sun.com>
                             void *buf, int buf_size,
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
-+                           struct ext4_dir_entry_2 **dest_de, int *dlen);
++                           struct ext4_dir_entry_2 **dest_de, int dlen);
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -252,7 +252,7 @@ Signed-off-by: Andreas Dilger <andreas.dilger@sun.com>
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -407,26 +407,20 @@ Signed-off-by: Andreas Dilger <andreas.dilger@sun.com>
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -1950,14 +1962,16 @@ int ext4_find_dest_de(struct inode *dir,
+@@ -1950,10 +1962,10 @@ int ext4_find_dest_de(struct inode *dir,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
-+                    struct ext4_dir_entry_2 **dest_de, int *dlen)
++                    struct ext4_dir_entry_2 **dest_de, int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname));
-+      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname)) +
-+                                                (dlen ? *dlen : 0);
++      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname) + dlen);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      dlen ? *dlen = 0 : 0; /* default set to 0 */
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -1966,10 +1980,26 @@ int ext4_find_dest_de(struct inode *dir,
+@@ -1966,7 +1980,7 @@ int ext4_find_dest_de(struct inode *dir,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -435,25 +429,6 @@ Signed-off-by: Andreas Dilger <andreas.dilger@sun.com>
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          EXT4_DIR_REC_LEN(fname_len(fname))) {
-+                              /* set dlen=1 to indicate not
-+                               * enough space store fid */
-+                              dlen ? *dlen = 1 : 0;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO. */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -1983,12 +2013,12 @@ int ext4_find_dest_de(struct inode *dir,
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -499,18 +474,15 @@ Signed-off-by: Andreas Dilger <andreas.dilger@sun.com>
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2038,7 +2078,10 @@ static int add_dirent_to_buf(handle_t *h
+@@ -2038,7 +2078,7 @@ static int add_dirent_to_buf(handle_t *h
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(inode, de, blocksize, fname, data);
  
        /*
@@ -525,49 +497,152 @@ Signed-off-by: Andreas Dilger <andreas.dilger@sun.com>
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2193,6 +2237,8 @@ static int ext4_update_dotdot(handle_t *
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+@@ -2195,7 +2218,104 @@ out_frames:
+       return retval;
+ }
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de);
++
++              dx_info = dx_get_dx_info(dot_de);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen) + sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen), blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2204,6 +2324,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2218,11 +2264,16 @@ static int ext4_update_dotdot(handle_t *
-                       goto out_journal;
+@@ -2243,6 +2365,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2239,7 +2290,12 @@ static int ext4_update_dotdot(handle_t *
-               assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2280,6 +2336,7 @@ static int ext4_add_entry(handle_t *hand
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index 9c61de5..c6381c8 100644 (file)
@@ -169,7 +169,7 @@ index e84d4a7..5e73b80 100644
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
 +                           struct ext4_dir_entry_2 **dest_de,
-+                           int *dlen);
++                           int dlen);
  void ext4_insert_dentry(struct inode *dir, struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -273,7 +273,7 @@ index a4fbe82..6a424df 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -492,32 +492,21 @@ index 649dc0a..54bbe22 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -2106,14 +2117,22 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2106,10 +2117,11 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
 +                    struct ext4_dir_entry_2 **dest_de,
-+                    int *dlen)
++                    int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      unsigned short reclen;
++      unsigned short reclen = ext4_dir_rec_len(fname_len(fname) + dlen, dir);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      if (dlen) {
-+              reclen = ext4_dir_rec_len(fname_len(fname) + *dlen, dir);
-+              *dlen = 0;
-+      } else {
-+              reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      }
-+
-       de = buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -2122,10 +2141,31 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2122,7 +2141,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -526,30 +515,6 @@ index 649dc0a..54bbe22 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID
-+               */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          ext4_dir_rec_len(fname_len(fname), dir)) {
-+                              /* set dlen = 1 to indicate not
-+                               * enough space store fid
-+                               */
-+                              if (dlen)
-+                                      *dlen = 1;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO.
-+                       */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -2140,12 +2180,13 @@ void ext4_insert_dentry(struct inode *dir,
                        struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -597,18 +562,15 @@ index 649dc0a..54bbe22 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2203,7 +2255,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2203,7 +2255,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(dir, inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(dir, inode, de, blocksize, fname, data);
  
        /*
@@ -632,7 +594,7 @@ index 649dc0a..54bbe22 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2361,7 +2417,7 @@ out_frames:
+@@ -2361,12 +2386,111 @@ out_frames:
         */
        if (retval)
                ext4_mark_inode_dirty(handle, dir);
@@ -641,66 +603,151 @@ index 649dc0a..54bbe22 100644
        brelse(bh2);
        return retval;
  }
-@@ -2374,6 +2430,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de, NULL);
++
++              dx_info = dx_get_dx_info(dot_de, NULL);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen, NULL) +
++                             sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de, dir);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev, dir));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen, NULL),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2395,6 +2519,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2389,21 +2447,26 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+@@ -2435,6 +2561,30 @@ static int ext4_update_dotdot(handle_t *
  
-       de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-       /* the first item must be "." */
--      assert(de->name_len == 1 && de->name[0] == '.');
-+      ASSERT(de->name_len == 1 && de->name[0] == '.');
-       len = le16_to_cpu(de->rec_len);
--      assert(len >= EXT4_DIR_REC_LEN(1));
--      if (len > EXT4_DIR_REC_LEN(1)) {
-+      ASSERT(len >= EXT4_DIR_REC_LEN(1, dir));
-+      if (len > EXT4_DIR_REC_LEN(1, dir)) {
-               BUFFER_TRACE(dir_block, "get_write_access");
-               err = ext4_journal_get_write_access(handle, dir->i_sb, dir_block, EXT4_JTR_NONE);
-               if (err)
-                       goto out_journal;
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de, dir));
-       }
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de, NULL);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      ASSERT(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen, dir));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2417,10 +2480,15 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       if (len > 0)
-               de->rec_len = cpu_to_le16(len);
-       else
--              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+              ASSERT(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2, dir));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen, NULL)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2458,6 +2526,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index 28d4bb8..53499be 100644 (file)
@@ -834,9 +834,9 @@ index 54bbe22..9b20bc5 100644
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2562,9 +2910,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index 9683e64..09d385a 100644 (file)
@@ -825,9 +825,9 @@ index 24e1276..ae94c33 100644
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2443,9 +2792,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index df53a43..ac87a8c 100644 (file)
@@ -175,7 +175,7 @@ index 143ce00..98786d8 100644
                             void *buf, int buf_size,
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
-+                           struct ext4_dir_entry_2 **dest_de, int *dlen);
++                           struct ext4_dir_entry_2 **dest_de, int dlen);
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -282,7 +282,7 @@ index c2c688c..686d14a 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -439,26 +439,20 @@ index 1537a76..24e1276 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -2023,14 +2035,16 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2023,10 +2035,10 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
-+                    struct ext4_dir_entry_2 **dest_de, int *dlen)
++                    struct ext4_dir_entry_2 **dest_de, int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname));
-+      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname)) +
-+                                                (dlen ? *dlen : 0);
++      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname) + dlen);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      dlen ? *dlen = 0 : 0; /* default set to 0 */
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -2039,10 +2053,26 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2039,7 +2053,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -467,25 +461,6 @@ index 1537a76..24e1276 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          EXT4_DIR_REC_LEN(fname_len(fname))) {
-+                              /* set dlen=1 to indicate not
-+                               * enough space store fid */
-+                              dlen ? *dlen = 1 : 0;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO. */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -2056,12 +2086,12 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -531,18 +506,15 @@ index 1537a76..24e1276 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2111,7 +2151,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2111,7 +2151,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(inode, de, blocksize, fname, data);
  
        /*
@@ -557,49 +529,152 @@ index 1537a76..24e1276 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2267,6 +2311,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+@@ -2259,7 +2282,104 @@ out_frames:
+       return retval;
+ }
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de);
++
++              dx_info = dx_get_dx_info(dot_de);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen) + sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen), blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2268,6 +2388,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2292,11 +2338,16 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-                       goto out_journal;
+@@ -2307,6 +2429,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2313,7 +2364,12 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-               assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2351,6 +2407,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index 89ef4ec..184ff0f 100644 (file)
@@ -168,7 +168,7 @@ Index: linux-stage/fs/ext4/ext4.h
                             void *buf, int buf_size,
                             const char *name, int namelen,
 -                           struct ext4_dir_entry_2 **dest_de);
-+                           struct ext4_dir_entry_2 **dest_de, int *dlen);
++                           struct ext4_dir_entry_2 **dest_de, int dlen);
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -389,26 +389,20 @@ Index: linux-stage/fs/ext4/namei.c
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -1675,14 +1686,16 @@ int ext4_find_dest_de(struct inode *dir,
+@@ -1675,10 +1686,10 @@ int ext4_find_dest_de(struct inode *dir,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      const char *name, int namelen,
 -                    struct ext4_dir_entry_2 **dest_de)
-+                    struct ext4_dir_entry_2 **dest_de, int *dlen)
++                    struct ext4_dir_entry_2 **dest_de, int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = EXT4_DIR_REC_LEN(namelen);
-+      unsigned short reclen = __EXT4_DIR_REC_LEN(namelen) +
-+                                                      (dlen ? *dlen : 0);
++      unsigned short reclen = __EXT4_DIR_REC_LEN(namelen + dlen);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      dlen ? *dlen = 0 : 0; /* default set to 0 */
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -1691,10 +1704,26 @@ int ext4_find_dest_de(struct inode *dir,
+@@ -1691,7 +1704,7 @@ int ext4_find_dest_de(struct inode *dir,
                        return -EIO;
                if (ext4_match(namelen, name, de))
                        return -EEXIST;
@@ -417,25 +411,6 @@ Index: linux-stage/fs/ext4/namei.c
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID */
-+              if (namelen == 2 && memcmp(name, "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          __EXT4_DIR_REC_LEN(namelen)) {
-+                              /* set dlen=1 to indicate not
-+                               * enough space store fid */
-+                              dlen ? *dlen = 1 : 0;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO. */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -1708,12 +1737,12 @@ int ext4_find_dest_de(struct inode *dir,
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -482,18 +457,15 @@ Index: linux-stage/fs/ext4/namei.c
                err = ext4_find_dest_de(dir, inode,
                                        bh, bh->b_data, blocksize - csum_size,
 -                                      name, namelen, &de);
-+                                      name, namelen, &de, &dlen);
++                                      name, namelen, &de, dlen);
                if (err)
                        return err;
        }
-@@ -1765,7 +1804,10 @@ static int add_dirent_to_buf(handle_t *h
+@@ -1765,7 +1804,7 @@ static int add_dirent_to_buf(handle_t *h
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(inode, de, blocksize, name, namelen);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(inode, de, blocksize, name, namelen, data);
  
        /*
@@ -508,63 +480,153 @@ Index: linux-stage/fs/ext4/namei.c
  
        /* Initialize as for dx_probe */
        hinfo.hash_version = dx_info->hash_version;
-@@ -1927,6 +1970,8 @@ static int ext4_update_dotdot(handle_t *
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+@@ -1933,7 +1956,105 @@ out_frames:
+       return retval;
+ }
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = __EXT4_DIR_REC_LEN(2 + dlen) -
++                      EXT4_DIR_REC_LEN(dotdot_de);
++
++              dx_info = dx_get_dx_info(dot_de);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             __EXT4_DIR_REC_LEN(2 + dlen) + sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = __EXT4_DIR_REC_LEN(2 + dlen) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_REC_LEN(de);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_REC_LEN(de);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_REC_LEN(prev));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      __EXT4_DIR_REC_LEN(2 + dlen),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -1942,6 +2063,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -1942,19 +1987,24 @@ static int ext4_update_dotdot(handle_t *
-       /* the first item must be "." */
-       assert(de->name_len == 1 && de->name[0] == '.');
-       len = le16_to_cpu(de->rec_len);
--      assert(len >= EXT4_DIR_REC_LEN(1));
--      if (len > EXT4_DIR_REC_LEN(1)) {
-+      assert(len >= __EXT4_DIR_REC_LEN(1));
-+      if (len > __EXT4_DIR_REC_LEN(1)) {
-               BUFFER_TRACE(dir_block, "get_write_access");
-               err = ext4_journal_get_write_access(handle, dir_block);
-               if (err)
-                       goto out_journal;
+@@ -1981,6 +2104,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(de));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_REC_LEN(de);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      assert(len == 0 || len >= __EXT4_DIR_REC_LEN(2 + dlen));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -1968,10 +2018,15 @@ static int ext4_update_dotdot(handle_t *
-       if (len > 0)
-               de->rec_len = cpu_to_le16(len);
-       else
--              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+              assert(le16_to_cpu(de->rec_len) >= __EXT4_DIR_REC_LEN(2));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          __EXT4_DIR_REC_LEN(2 + dlen)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2445,37 +2500,70 @@ retry:
        return err;
  }
@@ -713,7 +775,7 @@ Index: linux-stage/fs/ext4/inline.c
        err = ext4_find_dest_de(dir, inode, iloc->bh,
                                inline_start, inline_size,
 -                              name, namelen, &de);
-+                              name, namelen, &de, NULL);
++                              name, namelen, &de, 0);
        if (err)
                return err;
  
index ff3ab9c..8260c93 100644 (file)
@@ -2,18 +2,19 @@ Index: linux-3.10.0-123.9.3.el7.x86_64/fs/ext4/namei.c
 ===================================================================
 --- linux-3.10.0-123.9.3.el7.x86_64.orig/fs/ext4/namei.c
 +++ linux-3.10.0-123.9.3.el7.x86_64/fs/ext4/namei.c
-@@ -1894,6 +1894,72 @@ static int make_indexed_dir(handle_t *ha
+@@ -1894,6 +1894,67 @@ out_frames:
        return retval;
  }
  
-+/* update ".." for hash-indexed directory, split the item "." if necessary */
++/* update ".." entry */
 +static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
 +                            struct inode *inode)
 +{
 +      struct inode *dir = dentry->d_parent->d_inode;
-+      struct buffer_head *dir_block;
-+      struct ext4_dir_entry_2 *de;
-+      int len, journal = 0, err = 0;
++      struct buffer_head *bh;
++      struct ext4_dir_entry_2 *dot_de, *dotdot_de;
++      unsigned int offset;
++      int retval = 0;
 +
 +      if (IS_ERR(handle))
 +              return PTR_ERR(handle);
@@ -21,67 +22,62 @@ Index: linux-3.10.0-123.9.3.el7.x86_64/fs/ext4/namei.c
 +      if (IS_DIRSYNC(dir))
 +              handle->h_sync = 1;
 +
-+      dir_block = ext4_bread(handle, dir, 0, 0, &err);
-+      if (!dir_block)
-+              goto out;
-+
-+      de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-+      /* the first item must be "." */
-+      assert(de->name_len == 1 && de->name[0] == '.');
-+      len = le16_to_cpu(de->rec_len);
-+      assert(len >= EXT4_DIR_REC_LEN(1));
-+      if (len > EXT4_DIR_REC_LEN(1)) {
-+              BUFFER_TRACE(dir_block, "get_write_access");
-+              err = ext4_journal_get_write_access(handle, dir_block);
-+              if (err)
-+                      goto out_journal;
++      bh = ext4_read_dirblock(dir, 0, EITHER);
++      if (IS_ERR(bh))
++              return PTR_ERR(bh);
 +
-+              journal = 1;
-+              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
++      dot_de = (struct ext4_dir_entry_2 *) bh->b_data;
++      if (ext4_check_dir_entry(dir, NULL, dot_de, bh, bh->b_data,
++                               bh->b_size, 0) ||
++          le32_to_cpu(dot_de->inode) != dir->i_ino ||
++          strcmp(".", dot_de->name)) {
++              EXT4_ERROR_INODE(dir, "directory missing '.'");
++              retval = -EFSCORRUPTED;
++              goto out;
 +      }
-+
-+      len -= EXT4_DIR_REC_LEN(1);
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      de = (struct ext4_dir_entry_2 *)
-+                      ((char *) de + le16_to_cpu(de->rec_len));
-+      if (!journal) {
-+              BUFFER_TRACE(dir_block, "get_write_access");
-+              err = ext4_journal_get_write_access(handle, dir_block);
-+              if (err)
-+                      goto out_journal;
++      offset = ext4_rec_len_from_disk(dot_de->rec_len,
++                                      dir->i_sb->s_blocksize);
++      dotdot_de = ext4_next_entry(dot_de, dir->i_sb->s_blocksize);
++      if (ext4_check_dir_entry(dir, NULL, dotdot_de, bh, bh->b_data,
++                               bh->b_size, offset) ||
++          le32_to_cpu(dotdot_de->inode) == 0 ||
++          strcmp("..", dotdot_de->name)) {
++              EXT4_ERROR_INODE(dir, "directory missing '..'");
++              retval = -EFSCORRUPTED;
++              goto out;
 +      }
 +
-+      de->inode = cpu_to_le32(inode->i_ino);
-+      if (len > 0)
-+              de->rec_len = cpu_to_le16(len);
-+      else
-+              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+      de->name_len = 2;
-+      strcpy(de->name, "..");
-+      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
++      BUFFER_TRACE(dir_block, "get_write_access");
++      retval = ext4_journal_get_write_access(handle, bh);
++      if (retval)
++              goto out;
++
++      dotdot_de->inode = cpu_to_le32(inode->i_ino);
 +
-+out_journal:
-+      if (journal) {
-+              BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
-+              err = ext4_handle_dirty_dirent_node(handle, dir, dir_block);
-+              ext4_mark_inode_dirty(handle, dir);
++      ext4_mark_inode_dirty(handle, dir);
++      BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
++      if (is_dx(dir)) {
++              retval = ext4_handle_dirty_dx_node(handle, dir, bh);
++      } else {
++              retval = ext4_handle_dirty_dirent_node(handle, dir, bh);
 +      }
-+      brelse(dir_block);
 +
 +out:
-+      return err;
++      brelse(bh);
++      return retval;
 +}
 +
  /*
   *    ext4_add_entry()
   *
-@@ -1938,6 +2004,9 @@ int ext4_add_entry(handle_t *handle, str
+@@ -1937,6 +2003,10 @@ static int ext4_add_entry(handle_t *hand
+               }
        }
  
++      if (dentry->d_name.len == 2 &&
++                      memcmp(dentry->d_name.name, "..", 2) == 0)
++              return ext4_update_dotdot(handle, dentry, inode);
++
        if (is_dx(dir)) {
-+              if (dentry->d_name.len == 2 &&
-+                  memcmp(dentry->d_name.name, "..", 2) == 0)
-+                      return ext4_update_dotdot(handle, dentry, inode);
                retval = ext4_dx_add_entry(handle, dentry, inode);
                if (!retval || (retval != ERR_BAD_DX_DIR))
-                       return retval;
index 5df59bf..525157a 100644 (file)
@@ -757,9 +757,9 @@ Index: linux-3.10.0-1160.2.1.el7.x86_64/fs/ext4/namei.c
        struct inode *dir = dentry->d_parent->d_inode;
        struct buffer_head *bh = NULL;
 @@ -2108,9 +2446,10 @@ static int ext4_add_entry(handle_t *hand
-               if (dentry->d_name.len == 2 &&
-                   memcmp(dentry->d_name.name, "..", 2) == 0)
-                       return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, dentry, inode);
 +              retval = ext4_dx_add_entry(handle, dentry, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index a705ec1..20386af 100644 (file)
@@ -167,7 +167,7 @@ index cb649f0..fe35251 100644
                             void *buf, int buf_size,
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
-+                           struct ext4_dir_entry_2 **dest_de, int *dlen);
++                           struct ext4_dir_entry_2 **dest_de, int dlen);
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -251,7 +251,7 @@ index 2fec62d..3f35821 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -408,26 +408,20 @@ index 8713671..23bd871 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -1943,14 +1955,16 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -1943,10 +1955,10 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
-+                    struct ext4_dir_entry_2 **dest_de, int *dlen)
++                    struct ext4_dir_entry_2 **dest_de, int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname));
-+      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname)) +
-+                                                (dlen ? *dlen : 0);
++      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname) + dlen);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      dlen ? *dlen = 0 : 0; /* default set to 0 */
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -1959,10 +1973,26 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -1959,7 +1973,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -436,25 +430,6 @@ index 8713671..23bd871 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          EXT4_DIR_REC_LEN(fname_len(fname))) {
-+                              /* set dlen=1 to indicate not
-+                               * enough space store fid */
-+                              dlen ? *dlen = 1 : 0;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO. */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -1976,12 +2006,12 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -500,18 +475,15 @@ index 8713671..23bd871 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2031,7 +2071,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2031,7 +2071,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(inode, de, blocksize, fname, data);
  
        /*
@@ -526,49 +498,152 @@ index 8713671..23bd871 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2186,6 +2230,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+@@ -2068,7 +2091,104 @@ out_frames:
+       return retval;
+ }
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de);
++
++              dx_info = dx_get_dx_info(dot_de);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen) + sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen), blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2077,6 +2203,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2211,11 +2257,16 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-                       goto out_journal;
+@@ -2116,6 +2244,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2232,7 +2283,12 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-               assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2271,6 +2327,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index ad22991..4894196 100644 (file)
@@ -761,9 +761,9 @@ Index: linux-4.18.0-240.1.1.el8/fs/ext4/namei.c
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2234,9 +2572,10 @@ static int ext4_add_entry(handle_t *hand
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
diff --git a/ldiskfs/kernel_patches/patches/rhel8.7/ext4-hash-indexed-dir-dotdot-update.patch b/ldiskfs/kernel_patches/patches/rhel8.7/ext4-hash-indexed-dir-dotdot-update.patch
new file mode 100644 (file)
index 0000000..68d4aa2
--- /dev/null
@@ -0,0 +1,83 @@
+Index: linux-4.15.0/fs/ext4/namei.c
+===================================================================
+--- linux-4.15.0.orig/fs/ext4/namei.c
++++ linux-4.15.0/fs/ext4/namei.c
+@@ -2043,6 +2043,67 @@ out_frames:
+       return retval;
+ }
++/* update ".." entry */
++static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
++                            struct inode *inode)
++{
++      struct inode *dir = dentry->d_parent->d_inode;
++      struct buffer_head *bh;
++      struct ext4_dir_entry_2 *dot_de, *dotdot_de;
++      unsigned int offset;
++      int retval = 0;
++
++      if (IS_ERR(handle))
++              return PTR_ERR(handle);
++
++      if (IS_DIRSYNC(dir))
++              handle->h_sync = 1;
++
++      bh = ext4_read_dirblock(dir, 0, DIRENT_HTREE);
++      if (IS_ERR(bh))
++              return PTR_ERR(bh);
++
++      dot_de = (struct ext4_dir_entry_2 *) bh->b_data;
++      if (ext4_check_dir_entry(dir, NULL, dot_de, bh, bh->b_data,
++                               bh->b_size, 0) ||
++          le32_to_cpu(dot_de->inode) != dir->i_ino ||
++          strcmp(".", dot_de->name)) {
++              EXT4_ERROR_INODE(dir, "directory missing '.'");
++              retval = -EFSCORRUPTED;
++              goto out;
++      }
++      offset = ext4_rec_len_from_disk(dot_de->rec_len,
++                                      dir->i_sb->s_blocksize);
++      dotdot_de = ext4_next_entry(dot_de, dir->i_sb->s_blocksize);
++      if (ext4_check_dir_entry(dir, NULL, dotdot_de, bh, bh->b_data,
++                               bh->b_size, offset) ||
++          le32_to_cpu(dotdot_de->inode) == 0 ||
++          strcmp("..", dotdot_de->name)) {
++              EXT4_ERROR_INODE(dir, "directory missing '..'");
++              retval = -EFSCORRUPTED;
++              goto out;
++      }
++
++      BUFFER_TRACE(dir_block, "get_write_access");
++      retval = ext4_journal_get_write_access(handle, bh);
++      if (retval)
++              goto out;
++
++      dotdot_de->inode = cpu_to_le32(inode->i_ino);
++
++      ext4_mark_inode_dirty(handle, dir);
++      BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
++      if (is_dx(dir)) {
++              retval = ext4_handle_dirty_dx_node(handle, dir, bh);
++      } else {
++              retval = ext4_handle_dirty_dirent_node(handle, dir, bh);
++      }
++
++out:
++      brelse(bh);
++      return retval;
++}
++
+ /*
+  *    ext4_add_entry()
+  *
+@@ -2090,6 +2158,10 @@ static int ext4_add_entry(handle_t *hand
+               }
+       }
++      if (dentry->d_name.len == 2 &&
++                      memcmp(dentry->d_name.name, "..", 2) == 0)
++              return ext4_update_dotdot(handle, dentry, inode);
++
+       if (is_dx(dir)) {
+               retval = ext4_dx_add_entry(handle, &fname, dir, inode);
+               if (!retval || (retval != ERR_BAD_DX_DIR))
index 2da3d4d..450e821 100644 (file)
@@ -775,9 +775,9 @@ Index: linux-4.18.0-423.el8/fs/ext4/namei.c
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2307,9 +2648,10 @@ static int ext4_add_entry(handle_t *hand
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index 35d8c03..c25b777 100644 (file)
@@ -757,9 +757,9 @@ Index: linux-4.18.0-80.1.2.el8_0/fs/ext4/namei.c
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2226,9 +2564,10 @@ static int ext4_add_entry(handle_t *hand
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
@@ -170,7 +170,7 @@ index f4a1557..85556ce 100644
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
 +                           struct ext4_dir_entry_2 **dest_de,
-+                           int *dlen);
++                           int dlen);
  void ext4_insert_dentry(struct inode *dir, struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -274,7 +274,7 @@ index e9ef5cf..a23fd25 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -493,31 +493,21 @@ index 4d932a2..ee1a058 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -2078,14 +2089,21 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2078,10 +2089,11 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
 +                    struct ext4_dir_entry_2 **dest_de,
-+                    int *dlen)
++                    int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      unsigned short reclen;
++      unsigned short reclen = ext4_dir_rec_len(fname_len(fname) + dlen, dir);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      if (dlen) {
-+              reclen = ext4_dir_rec_len(fname_len(fname) + *dlen, dir);
-+              *dlen = 0;
-+      } else {
-+              reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      }
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -2094,10 +2112,31 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2094,7 +2112,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -526,30 +516,6 @@ index 4d932a2..ee1a058 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID
-+               */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          ext4_dir_rec_len(fname_len(fname), dir)) {
-+                              /* set dlen = 1 to indicate not
-+                               * enough space store fid
-+                               */
-+                              if (dlen)
-+                                      *dlen = 1;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO.
-+                       */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -2112,12 +2151,13 @@ void ext4_insert_dentry(struct inode *dir,
                        struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -597,18 +563,15 @@ index 4d932a2..ee1a058 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2175,7 +2226,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2175,7 +2226,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(dir, inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(dir, inode, de, blocksize, fname, data);
  
        /*
@@ -632,7 +595,7 @@ index 4d932a2..ee1a058 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2325,7 +2380,7 @@ out_frames:
+@@ -2348,12 +2373,111 @@ out_frames:
         */
        if (retval)
                ext4_mark_inode_dirty(handle, dir);
@@ -641,66 +604,151 @@ index 4d932a2..ee1a058 100644
        brelse(bh2);
        return retval;
  }
-@@ -2338,6 +2393,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de, NULL);
++
++              dx_info = dx_get_dx_info(dot_de, NULL);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen, NULL) +
++                             sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de, dir);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev, dir));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen, NULL),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2362,6 +2486,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2353,21 +2410,26 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-       /* the first item must be "." */
--      assert(de->name_len == 1 && de->name[0] == '.');
-+      ASSERT(de->name_len == 1 && de->name[0] == '.');
-       len = le16_to_cpu(de->rec_len);
--      assert(len >= EXT4_DIR_REC_LEN(1));
--      if (len > EXT4_DIR_REC_LEN(1)) {
-+      ASSERT(len >= EXT4_DIR_REC_LEN(1, dir));
-+      if (len > EXT4_DIR_REC_LEN(1, dir)) {
-               BUFFER_TRACE(dir_block, "get_write_access");
-               err = ext4_journal_get_write_access(handle, dir->i_sb, dir_block, EXT4_JTR_NONE);
-               if (err)
-                       goto out_journal;
+@@ -2402,6 +2528,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de, dir));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de, NULL);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      ASSERT(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen, dir));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2381,10 +2443,15 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       if (len > 0)
-               de->rec_len = cpu_to_le16(len);
-       else
--              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+              ASSERT(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2, dir));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen, NULL)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2422,6 +2489,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index ed4242c..3662f8d 100644 (file)
@@ -827,9 +827,9 @@ index 2760dc6..2d14bd2 100644
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2548,9 +2896,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index 1d269df..5e11425 100644 (file)
@@ -170,7 +170,7 @@ index e84d4a7..5e73b80 100644
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
 +                           struct ext4_dir_entry_2 **dest_de,
-+                           int *dlen);
++                           int dlen);
  void ext4_insert_dentry(struct inode *dir, struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -274,7 +274,7 @@ index a4fbe82..6a424df 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -493,32 +493,21 @@ index 649dc0a..54bbe22 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -2106,14 +2117,22 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2106,10 +2117,11 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
 +                    struct ext4_dir_entry_2 **dest_de,
-+                    int *dlen)
++                    int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      unsigned short reclen;
++      unsigned short reclen = ext4_dir_rec_len(fname_len(fname) + dlen, dir);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      if (dlen) {
-+              reclen = ext4_dir_rec_len(fname_len(fname) + *dlen, dir);
-+              *dlen = 0;
-+      } else {
-+              reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      }
-+
-       de = buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -2122,10 +2141,31 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2122,7 +2141,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -527,30 +516,6 @@ index 649dc0a..54bbe22 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID
-+               */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          ext4_dir_rec_len(fname_len(fname), dir)) {
-+                              /* set dlen = 1 to indicate not
-+                               * enough space store fid
-+                               */
-+                              if (dlen)
-+                                      *dlen = 1;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO.
-+                       */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -2140,12 +2180,13 @@ void ext4_insert_dentry(struct inode *dir,
                        struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -598,18 +563,15 @@ index 649dc0a..54bbe22 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2203,7 +2255,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2203,7 +2255,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(dir, inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(dir, inode, de, blocksize, fname, data);
  
        /*
@@ -633,7 +595,7 @@ index 649dc0a..54bbe22 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2361,7 +2417,7 @@ out_frames:
+@@ -2362,12 +2387,111 @@ out_frames:
         */
        if (retval)
                ext4_mark_inode_dirty(handle, dir);
@@ -642,66 +604,151 @@ index 649dc0a..54bbe22 100644
        brelse(bh2);
        return retval;
  }
-@@ -2374,6 +2430,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de, NULL);
++
++              dx_info = dx_get_dx_info(dot_de, NULL);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen, NULL) +
++                             sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de, dir);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev, dir));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen, NULL),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2376,6 +2500,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2389,21 +2447,26 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+@@ -2416,6 +2542,30 @@ static int ext4_update_dotdot(handle_t *
  
-       de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-       /* the first item must be "." */
--      assert(de->name_len == 1 && de->name[0] == '.');
-+      ASSERT(de->name_len == 1 && de->name[0] == '.');
-       len = le16_to_cpu(de->rec_len);
--      assert(len >= EXT4_DIR_REC_LEN(1));
--      if (len > EXT4_DIR_REC_LEN(1)) {
-+      ASSERT(len >= EXT4_DIR_REC_LEN(1, dir));
-+      if (len > EXT4_DIR_REC_LEN(1, dir)) {
-               BUFFER_TRACE(dir_block, "get_write_access");
-               err = ext4_journal_get_write_access(handle, dir->i_sb, dir_block, EXT4_JTR_NONE);
-               if (err)
-                       goto out_journal;
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de, dir));
-       }
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de, NULL);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      ASSERT(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen, dir));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2417,10 +2480,15 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       if (len > 0)
-               de->rec_len = cpu_to_le16(len);
-       else
--              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+              ASSERT(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2, dir));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen, NULL)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2458,6 +2526,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index 28d4bb8..53499be 100644 (file)
@@ -834,9 +834,9 @@ index 54bbe22..9b20bc5 100644
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2562,9 +2910,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index 4025ae6..3df382c 100644 (file)
@@ -170,7 +170,7 @@ index a4af3ec8..930ca3a5 100644
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
 +                           struct ext4_dir_entry_2 **dest_de,
-+                           int *dlen);
++                           int dlen);
  void ext4_insert_dentry(struct inode *dir, struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -274,7 +274,7 @@ index c4475a74..3fc75d80 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -493,32 +493,21 @@ index d0afa8f2..839d51ba 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -2106,14 +2117,22 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2106,10 +2117,11 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
 +                    struct ext4_dir_entry_2 **dest_de,
-+                    int *dlen)
++                    int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      unsigned short reclen;
++      unsigned short reclen = ext4_dir_rec_len(fname_len(fname) + dlen, dir);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      if (dlen) {
-+              reclen = ext4_dir_rec_len(fname_len(fname) + *dlen, dir);
-+              *dlen = 0;
-+      } else {
-+              reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      }
-+
-       de = buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -2122,10 +2141,31 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2122,7 +2141,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -527,30 +516,6 @@ index d0afa8f2..839d51ba 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID
-+               */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          ext4_dir_rec_len(fname_len(fname), dir)) {
-+                              /* set dlen = 1 to indicate not
-+                               * enough space store fid
-+                               */
-+                              if (dlen)
-+                                      *dlen = 1;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO.
-+                       */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -2140,12 +2180,13 @@ void ext4_insert_dentry(struct inode *dir,
                        struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -598,18 +563,15 @@ index d0afa8f2..839d51ba 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2203,7 +2255,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2203,7 +2255,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(dir, inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(dir, inode, de, blocksize, fname, data);
  
        /*
@@ -633,7 +595,7 @@ index d0afa8f2..839d51ba 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2361,7 +2417,7 @@ out_frames:
+@@ -2361,12 +2386,111 @@ out_frames:
         */
        if (retval)
                ext4_mark_inode_dirty(handle, dir);
@@ -642,66 +604,151 @@ index d0afa8f2..839d51ba 100644
        brelse(bh2);
        return retval;
  }
-@@ -2374,6 +2430,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de, NULL);
++
++              dx_info = dx_get_dx_info(dot_de, NULL);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen, NULL) +
++                             sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de, dir);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev, dir));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen, NULL),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2375,6 +2504,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2389,21 +2447,26 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+@@ -2415,6 +2546,30 @@ static int ext4_update_dotdot(handle_t *
  
-       de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-       /* the first item must be "." */
--      assert(de->name_len == 1 && de->name[0] == '.');
-+      ASSERT(de->name_len == 1 && de->name[0] == '.');
-       len = le16_to_cpu(de->rec_len);
--      assert(len >= EXT4_DIR_REC_LEN(1));
--      if (len > EXT4_DIR_REC_LEN(1)) {
-+      ASSERT(len >= EXT4_DIR_REC_LEN(1, dir));
-+      if (len > EXT4_DIR_REC_LEN(1, dir)) {
-               BUFFER_TRACE(dir_block, "get_write_access");
-               err = ext4_journal_get_write_access(handle, dir->i_sb, dir_block, EXT4_JTR_NONE);
-               if (err)
-                       goto out_journal;
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de, dir));
-       }
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de, NULL);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      ASSERT(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen, dir));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2417,10 +2480,15 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       if (len > 0)
-               de->rec_len = cpu_to_le16(len);
-       else
--              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+              ASSERT(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2, dir));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen, NULL)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2458,6 +2526,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index 31ebd76..d2eb65d 100644 (file)
@@ -170,7 +170,7 @@ index 58645be..8c6864c 100644
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
 +                           struct ext4_dir_entry_2 **dest_de,
-+                           int *dlen);
++                           int dlen);
  void ext4_insert_dentry(struct inode *dir, struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -274,7 +274,7 @@ index 39a1ab1..46f2e1e 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -493,31 +493,21 @@ index 1f95773..9edb487 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -2051,14 +2062,21 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2051,10 +2062,11 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
 +                    struct ext4_dir_entry_2 **dest_de,
-+                    int *dlen)
++                    int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      unsigned short reclen;
++      unsigned short reclen = ext4_dir_rec_len(fname_len(fname) + dlen, dir);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      if (dlen) {
-+              reclen = ext4_dir_rec_len(fname_len(fname) + *dlen, dir);
-+              *dlen = 0;
-+      } else {
-+              reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      }
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -2067,10 +2085,31 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2067,7 +2085,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -526,30 +516,6 @@ index 1f95773..9edb487 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID
-+               */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          ext4_dir_rec_len(fname_len(fname), dir)) {
-+                              /* set dlen = 1 to indicate not
-+                               * enough space store fid
-+                               */
-+                              if (dlen)
-+                                      *dlen = 1;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO.
-+                       */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -2085,12 +2124,13 @@ void ext4_insert_dentry(struct inode *dir,
                        struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -597,18 +563,15 @@ index 1f95773..9edb487 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2148,7 +2199,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2148,7 +2199,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(dir, inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(dir, inode, de, blocksize, fname, data);
  
        /*
@@ -632,7 +595,7 @@ index 1f95773..9edb487 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2298,7 +2353,7 @@ out_frames:
+@@ -2298,12 +2323,111 @@ out_frames:
         */
        if (retval)
                ext4_mark_inode_dirty(handle, dir);
@@ -641,66 +604,151 @@ index 1f95773..9edb487 100644
        brelse(bh2);
        return retval;
  }
-@@ -2311,6 +2366,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de, NULL);
++
++              dx_info = dx_get_dx_info(dot_de, NULL);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen, NULL) +
++                             sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de, dir);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev, dir));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen, NULL),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2312,6 +2436,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2326,21 +2383,26 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-       /* the first item must be "." */
--      assert(de->name_len == 1 && de->name[0] == '.');
-+      ASSERT(de->name_len == 1 && de->name[0] == '.');
-       len = le16_to_cpu(de->rec_len);
--      assert(len >= EXT4_DIR_REC_LEN(1));
--      if (len > EXT4_DIR_REC_LEN(1)) {
-+      ASSERT(len >= EXT4_DIR_REC_LEN(1, dir));
-+      if (len > EXT4_DIR_REC_LEN(1, dir)) {
-               BUFFER_TRACE(dir_block, "get_write_access");
-               err = ext4_journal_get_write_access(handle, dir->i_sb, dir_block, EXT4_JTR_NONE);
-               if (err)
-                       goto out_journal;
+@@ -2352,6 +2478,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de, dir));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de, NULL);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      ASSERT(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen, dir));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2354,10 +2416,15 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       if (len > 0)
-               de->rec_len = cpu_to_le16(len);
-       else
--              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+              ASSERT(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2, dir));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen, NULL)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2395,6 +2462,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index 22012f9..1a2791f 100644 (file)
@@ -798,9 +798,9 @@ index 059bc08..7d25879 100644
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2493,9 +2832,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index eb07fed..30f6bd5 100644 (file)
@@ -760,9 +760,9 @@ Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2251,7 +2589,7 @@ static int ext4_add_entry(handle_t *hand
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index c016423..6fbbcdc 100644 (file)
@@ -182,7 +182,7 @@ index 8577b51..dab4486 100644
                             void *buf, int buf_size,
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
-+                           struct ext4_dir_entry_2 **dest_de, int *dlen);
++                           struct ext4_dir_entry_2 **dest_de, int dlen);
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -266,7 +266,7 @@ index 46151bd..316892b 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -423,26 +423,20 @@ index 54cca61..2bc4682 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -1954,14 +1966,16 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -1954,10 +1966,10 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
-+                    struct ext4_dir_entry_2 **dest_de, int *dlen)
++                    struct ext4_dir_entry_2 **dest_de, int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname));
-+      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname)) +
-+                                                (dlen ? *dlen : 0);
++      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname) + dlen);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      dlen ? *dlen = 0 : 0; /* default set to 0 */
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -1970,10 +1984,26 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -1970,7 +1984,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -451,25 +445,6 @@ index 54cca61..2bc4682 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          EXT4_DIR_REC_LEN(fname_len(fname))) {
-+                              /* set dlen=1 to indicate not
-+                               * enough space store fid */
-+                              dlen ? *dlen = 1 : 0;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO. */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -1987,12 +2017,12 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -516,18 +491,15 @@ index 54cca61..2bc4682 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2042,7 +2083,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2042,7 +2083,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(inode, de, blocksize, fname, data);
  
        /*
@@ -542,49 +514,152 @@ index 54cca61..2bc4682 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2197,6 +2242,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+@@ -2189,7 +2213,104 @@ out_frames:
+       return retval;
+ }
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de);
++
++              dx_info = dx_get_dx_info(dot_de);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen) + sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen), blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2198,6 +2319,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2222,11 +2269,16 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-                       goto out_journal;
+@@ -2237,6 +2360,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2243,7 +2295,12 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-               assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2282,6 +2339,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index 8e1ff81..6c9e1be 100644 (file)
@@ -826,9 +826,9 @@ index a7dd2f2..046fc45 100644
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2426,9 +2774,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index c492420..7928ae2 100644 (file)
@@ -171,7 +171,7 @@ index 0791a8b..f1bc21d 100644
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
 +                           struct ext4_dir_entry_2 **dest_de,
-+                           int *dlen);
++                           int dlen);
  void ext4_insert_dentry(struct inode *dir, struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -279,7 +279,7 @@ index 9626c31..ed31b5c 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -498,31 +498,21 @@ index 7f00dc3..51c950b 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -2101,14 +2112,21 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2101,10 +2112,11 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
 +                    struct ext4_dir_entry_2 **dest_de,
-+                    int *dlen)
++                    int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      unsigned short reclen;
++      unsigned short reclen = ext4_dir_rec_len(fname_len(fname) + dlen, dir);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      if (dlen) {
-+              reclen = ext4_dir_rec_len(fname_len(fname) + *dlen, dir);
-+              *dlen = 0;
-+      } else {
-+              reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      }
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -2117,10 +2135,31 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2117,7 +2135,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -531,30 +521,6 @@ index 7f00dc3..51c950b 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID
-+               */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          ext4_dir_rec_len(fname_len(fname), dir)) {
-+                              /* set dlen = 1 to indicate not
-+                               * enough space store fid
-+                               */
-+                              if (dlen)
-+                                      *dlen = 1;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO.
-+                       */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -2135,12 +2174,13 @@ void ext4_insert_dentry(struct inode *dir,
                        struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -602,18 +568,15 @@ index 7f00dc3..51c950b 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2198,7 +2249,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2198,7 +2249,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(dir, inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(dir, inode, de, blocksize, fname, data);
  
        /*
@@ -637,7 +600,7 @@ index 7f00dc3..51c950b 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2348,7 +2403,7 @@ out_frames:
+@@ -2348,12 +2373,111 @@ out_frames:
         */
        if (retval)
                ext4_mark_inode_dirty(handle, dir);
@@ -646,65 +609,151 @@ index 7f00dc3..51c950b 100644
        brelse(bh2);
        return retval;
  }
-@@ -2361,6 +2416,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de, NULL);
++
++              dx_info = dx_get_dx_info(dot_de, NULL);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen, NULL) +
++                             sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de, dir);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev, dir));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen, NULL),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2362,6 +2486,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2376,21 +2433,25 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-       /* the first item must be "." */
--      assert(de->name_len == 1 && de->name[0] == '.');
-+      ASSERT(de->name_len == 1 && de->name[0] == '.');
-       len = le16_to_cpu(de->rec_len);
--      assert(len >= EXT4_DIR_REC_LEN(1));
--      if (len > EXT4_DIR_REC_LEN(1)) {
-+      ASSERT(len >= EXT4_DIR_REC_LEN(1, dir));
-+      if (len > EXT4_DIR_REC_LEN(1, dir)) {
-               BUFFER_TRACE(dir_block, "get_write_access");
-               err = ext4_journal_get_write_access(handle, dir->i_sb, dir_block, EXT4_JTR_NONE);
-               if (err)
-                       goto out_journal;
+@@ -2402,6 +2528,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1, dir));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_REC_LEN(1, dir);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      ASSERT(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen, dir));
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2404,10 +2465,15 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       if (len > 0)
-               de->rec_len = cpu_to_le16(len);
-       else
--              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+              ASSERT(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2, dir));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen, NULL)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2445,6 +2511,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index efc330f..9834755 100644 (file)
@@ -8,18 +8,19 @@ diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
 index 95b21f5..e4514c9 100644
 --- a/fs/ext4/namei.c
 +++ b/fs/ext4/namei.c
-@@ -2301,6 +2301,74 @@ out_frames:
+@@ -2364,6 +2364,68 @@ out_frames:
        return retval;
  }
  
-+/* update ".." for hash-indexed directory, split the item "." if necessary */
++/* update ".." entry */
 +static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
 +                            struct inode *inode)
 +{
 +      struct inode *dir = dentry->d_parent->d_inode;
-+      struct buffer_head *dir_block;
-+      struct ext4_dir_entry_2 *de;
-+      int len, journal = 0, err = 0;
++      struct buffer_head *bh;
++      struct ext4_dir_entry_2 *dot_de, *dotdot_de;
++      unsigned int offset;
++      int retval = 0;
 +
 +      if (IS_ERR(handle))
 +              return PTR_ERR(handle);
@@ -27,72 +28,66 @@ index 95b21f5..e4514c9 100644
 +      if (IS_DIRSYNC(dir))
 +              handle->h_sync = 1;
 +
-+      dir_block = ext4_bread(handle, dir, 0, 0);
-+      if (IS_ERR(dir_block)) {
-+              err = PTR_ERR(dir_block);
++      bh = ext4_read_dirblock(dir, 0, DIRENT_HTREE);
++      if (IS_ERR(bh))
++              return PTR_ERR(bh);
++
++      dot_de = (struct ext4_dir_entry_2 *) bh->b_data;
++      if (ext4_check_dir_entry(dir, NULL, dot_de, bh, bh->b_data,
++                               bh->b_size, 0) ||
++          le32_to_cpu(dot_de->inode) != dir->i_ino ||
++          strcmp(".", dot_de->name)) {
++              EXT4_ERROR_INODE(dir, "directory missing '.'");
++              retval = -EFSCORRUPTED;
 +              goto out;
 +      }
-+
-+      de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-+      /* the first item must be "." */
-+      assert(de->name_len == 1 && de->name[0] == '.');
-+      len = le16_to_cpu(de->rec_len);
-+      assert(len >= EXT4_DIR_REC_LEN(1));
-+      if (len > EXT4_DIR_REC_LEN(1)) {
-+              BUFFER_TRACE(dir_block, "get_write_access");
-+              err = ext4_journal_get_write_access(handle, dir->i_sb, dir_block, EXT4_JTR_NONE);
-+              if (err)
-+                      goto out_journal;
-+
-+              journal = 1;
-+              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
++      offset = ext4_rec_len_from_disk(dot_de->rec_len,
++                                      dir->i_sb->s_blocksize);
++      dotdot_de = ext4_next_entry(dot_de, dir->i_sb->s_blocksize);
++      if (ext4_check_dir_entry(dir, NULL, dotdot_de, bh, bh->b_data,
++                               bh->b_size, offset) ||
++          le32_to_cpu(dotdot_de->inode) == 0 ||
++          strcmp("..", dotdot_de->name)) {
++              EXT4_ERROR_INODE(dir, "directory missing '..'");
++              retval = -EFSCORRUPTED;
++              goto out;
 +      }
 +
-+      len -= EXT4_DIR_REC_LEN(1);
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      de = (struct ext4_dir_entry_2 *)
-+                      ((char *) de + le16_to_cpu(de->rec_len));
-+      if (!journal) {
-+              BUFFER_TRACE(dir_block, "get_write_access");
-+              err = ext4_journal_get_write_access(handle, dir->i_sb, dir_block, EXT4_JTR_NONE);
-+              if (err)
-+                      goto out_journal;
-+      }
++      BUFFER_TRACE(dir_block, "get_write_access");
++      retval = ext4_journal_get_write_access(handle, dir->i_sb, bh,
++                                             EXT4_JTR_NONE);
++      if (retval)
++              goto out;
 +
-+      de->inode = cpu_to_le32(inode->i_ino);
-+      if (len > 0)
-+              de->rec_len = cpu_to_le16(len);
-+      else
-+              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+      de->name_len = 2;
-+      strcpy(de->name, "..");
-+      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
++      dotdot_de->inode = cpu_to_le32(inode->i_ino);
 +
-+out_journal:
-+      if (journal) {
-+              BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
-+              err = ext4_handle_dirty_dirblock(handle, dir, dir_block);
-+              ext4_mark_inode_dirty(handle, dir);
++      ext4_mark_inode_dirty(handle, dir);
++      BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
++      if (is_dx(dir)) {
++              retval = ext4_handle_dirty_dx_node(handle, dir, bh);
++      } else {
++              retval = ext4_handle_dirty_dirblock(handle, dir, bh);
 +      }
-+      brelse(dir_block);
 +
 +out:
-+      return err;
++      brelse(bh);
++      return retval;
 +}
 +
  /*
   *    ext4_add_entry()
   *
-@@ -2357,6 +2425,9 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
+@@ -2356,6 +2424,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
+               }
        }
  
++      if (dentry->d_name.len == 2 &&
++                      memcmp(dentry->d_name.name, "..", 2) == 0)
++              return ext4_update_dotdot(handle, dentry, inode);
++
        if (is_dx(dir)) {
-+              if (dentry->d_name.len == 2 &&
-+                   memcmp(dentry->d_name.name, "..", 2) == 0)
-+                       return ext4_update_dotdot(handle, dentry, inode);
                retval = ext4_dx_add_entry(handle, &fname, dir, inode);
                if (!retval || (retval != ERR_BAD_DX_DIR))
-                       goto out;
 -- 
 2.31.1
 
index ea3b185..e12ffce 100644 (file)
@@ -827,9 +827,9 @@ index 51c950b..1b8c80e 100644
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2547,9 +2895,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index 978c3b6..7de7ac4 100644 (file)
@@ -167,7 +167,7 @@ Index: linux-4.15.0/fs/ext4/ext4.h
                             void *buf, int buf_size,
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
-+                           struct ext4_dir_entry_2 **dest_de, int *dlen);
++                           struct ext4_dir_entry_2 **dest_de, int dlen);
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -386,26 +386,20 @@ Index: linux-4.15.0/fs/ext4/namei.c
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -1808,14 +1819,16 @@ int ext4_find_dest_de(struct inode *dir,
+@@ -1808,10 +1819,10 @@ int ext4_find_dest_de(struct inode *dir,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
-+                    struct ext4_dir_entry_2 **dest_de, int *dlen)
++                    struct ext4_dir_entry_2 **dest_de, int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname));
-+      unsigned short reclen = __EXT4_DIR_REC_LEN(fname_len(fname)) +
-+                                                (dlen ? *dlen : 0);
++      unsigned short reclen = __EXT4_DIR_REC_LEN(fname_len(fname) + dlen);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      dlen ? *dlen = 0 : 0; /* default set to 0 */
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -1824,10 +1837,26 @@ int ext4_find_dest_de(struct inode *dir,
+@@ -1824,7 +1837,7 @@ int ext4_find_dest_de(struct inode *dir,
                        return -EFSCORRUPTED;
                if (ext4_match(fname, de))
                        return -EEXIST;
@@ -414,25 +408,6 @@ Index: linux-4.15.0/fs/ext4/namei.c
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          __EXT4_DIR_REC_LEN(fname_len(fname))) {
-+                              /* set dlen=1 to indicate not
-+                               * enough space store fid */
-+                              dlen ? *dlen = 1 : 0;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO. */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -1841,12 +1870,12 @@ int ext4_find_dest_de(struct inode *dir,
  void ext4_insert_dentry(struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -478,18 +453,15 @@ Index: linux-4.15.0/fs/ext4/namei.c
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -1896,7 +1935,10 @@ static int add_dirent_to_buf(handle_t *h
+@@ -1896,7 +1935,7 @@ static int add_dirent_to_buf(handle_t *h
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(inode, de, blocksize, fname, data);
  
        /*
@@ -504,63 +476,153 @@ Index: linux-4.15.0/fs/ext4/namei.c
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2055,6 +2098,8 @@ static int ext4_update_dotdot(handle_t *
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+@@ -2047,7 +2070,105 @@ out_frames:
+       return retval;
+ }
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = __EXT4_DIR_REC_LEN(2 + dlen) -
++                      EXT4_DIR_REC_LEN(dotdot_de);
++
++              dx_info = dx_get_dx_info(dot_de);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             __EXT4_DIR_REC_LEN(2 + dlen) + sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = __EXT4_DIR_REC_LEN(2 + dlen) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_REC_LEN(de);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_REC_LEN(de);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_REC_LEN(prev));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      __EXT4_DIR_REC_LEN(2 + dlen),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2056,6 +2177,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2072,19 +2117,24 @@ static int ext4_update_dotdot(handle_t *
-       /* the first item must be "." */
-       assert(de->name_len == 1 && de->name[0] == '.');
-       len = le16_to_cpu(de->rec_len);
--      assert(len >= EXT4_DIR_REC_LEN(1));
--      if (len > EXT4_DIR_REC_LEN(1)) {
-+      assert(len >= __EXT4_DIR_REC_LEN(1));
-+      if (len > __EXT4_DIR_REC_LEN(1)) {
-               BUFFER_TRACE(dir_block, "get_write_access");
-               err = ext4_journal_get_write_access(handle, dir_block);
-               if (err)
-                       goto out_journal;
+@@ -2095,6 +2218,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(de));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_REC_LEN(de);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      assert(len == 0 || len >= __EXT4_DIR_REC_LEN(2 + dlen));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2098,10 +2148,15 @@ static int ext4_update_dotdot(handle_t *
-       if (len > 0)
-               de->rec_len = cpu_to_le16(len);
-       else
--              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+              assert(le16_to_cpu(de->rec_len) >= __EXT4_DIR_REC_LEN(2));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          __EXT4_DIR_REC_LEN(2 + dlen)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2140,6 +2195,7 @@ static int ext4_add_entry(handle_t *hand
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
@@ -717,7 +779,7 @@ Index: linux-4.15.0/fs/ext4/inline.c
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
diff --git a/ldiskfs/kernel_patches/patches/ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch b/ldiskfs/kernel_patches/patches/ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
deleted file mode 100644 (file)
index 7f0b0e5..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-Index: linux-4.15.0/fs/ext4/namei.c
-===================================================================
---- linux-4.15.0.orig/fs/ext4/namei.c
-+++ linux-4.15.0/fs/ext4/namei.c
-@@ -2043,6 +2043,74 @@ out_frames:
-       return retval;
- }
-+/* update ".." for hash-indexed directory, split the item "." if necessary */
-+static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-+                            struct inode *inode)
-+{
-+      struct inode *dir = dentry->d_parent->d_inode;
-+      struct buffer_head *dir_block;
-+      struct ext4_dir_entry_2 *de;
-+      int len, journal = 0, err = 0;
-+
-+      if (IS_ERR(handle))
-+              return PTR_ERR(handle);
-+
-+      if (IS_DIRSYNC(dir))
-+              handle->h_sync = 1;
-+
-+      dir_block = ext4_bread(handle, dir, 0, 0);
-+      if (IS_ERR(dir_block)) {
-+              err = PTR_ERR(dir_block);
-+              goto out;
-+      }
-+
-+      de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-+      /* the first item must be "." */
-+      assert(de->name_len == 1 && de->name[0] == '.');
-+      len = le16_to_cpu(de->rec_len);
-+      assert(len >= EXT4_DIR_REC_LEN(1));
-+      if (len > EXT4_DIR_REC_LEN(1)) {
-+              BUFFER_TRACE(dir_block, "get_write_access");
-+              err = ext4_journal_get_write_access(handle, dir_block);
-+              if (err)
-+                      goto out_journal;
-+
-+              journal = 1;
-+              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+      }
-+
-+      len -= EXT4_DIR_REC_LEN(1);
-+      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      de = (struct ext4_dir_entry_2 *)
-+                      ((char *) de + le16_to_cpu(de->rec_len));
-+      if (!journal) {
-+              BUFFER_TRACE(dir_block, "get_write_access");
-+              err = ext4_journal_get_write_access(handle, dir_block);
-+              if (err)
-+                      goto out_journal;
-+      }
-+
-+      de->inode = cpu_to_le32(inode->i_ino);
-+      if (len > 0)
-+              de->rec_len = cpu_to_le16(len);
-+      else
-+              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+      de->name_len = 2;
-+      strcpy(de->name, "..");
-+      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+
-+out_journal:
-+      if (journal) {
-+              BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
-+              err = ext4_handle_dirty_dirent_node(handle, dir, dir_block);
-+              ext4_mark_inode_dirty(handle, dir);
-+      }
-+      brelse(dir_block);
-+
-+out:
-+      return err;
-+}
-+
- /*
-  *    ext4_add_entry()
-  *
-@@ -2091,6 +2159,9 @@ static int ext4_add_entry(handle_t *hand
-       }
-       if (is_dx(dir)) {
-+              if (dentry->d_name.len == 2 &&
-+                   memcmp(dentry->d_name.name, "..", 2) == 0)
-+                       return ext4_update_dotdot(handle, dentry, inode);
-               retval = ext4_dx_add_entry(handle, &fname, dir, inode);
-               if (!retval || (retval != ERR_BAD_DX_DIR))
-                       goto out;
index e2351b6..90fb914 100644 (file)
@@ -757,9 +757,9 @@ Index: linux-4.15.0/fs/ext4/namei.c
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2222,9 +2560,10 @@ static int ext4_add_entry(handle_t *hand
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index 0430b27..674e91f 100644 (file)
@@ -162,7 +162,7 @@ index 29db9e17..cf1c0732 100644
                             struct ext4_filename *fname,
 -                           struct ext4_dir_entry_2 **dest_de);
 +                           struct ext4_dir_entry_2 **dest_de,
-+                           int *dlen);
++                           int dlen);
  void ext4_insert_dentry(struct inode *dir, struct inode *inode,
                        struct ext4_dir_entry_2 *de,
                        int buf_size,
@@ -266,7 +266,7 @@ index 6fe665de..e20e7894 100644
  
        err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
 -                              inline_size, fname, &de);
-+                              inline_size, fname, &de, NULL);
++                              inline_size, fname, &de, 0);
        if (err)
                return err;
  
@@ -485,31 +485,21 @@ index 25e84f0e..5c068e3a 100644
                        if (de > to)
                                memmove(to, de, rec_len);
                        to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
-@@ -2121,14 +2132,21 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2121,10 +2132,11 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
                      struct ext4_filename *fname,
 -                    struct ext4_dir_entry_2 **dest_de)
 +                    struct ext4_dir_entry_2 **dest_de,
-+                    int *dlen)
++                    int dlen)
  {
        struct ext4_dir_entry_2 *de;
 -      unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      unsigned short reclen;
++      unsigned short reclen = ext4_dir_rec_len(fname_len(fname) + dlen, dir);
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-+      if (dlen) {
-+              reclen = ext4_dir_rec_len(fname_len(fname) + *dlen, dir);
-+              *dlen = 0;
-+      } else {
-+              reclen = ext4_dir_rec_len(fname_len(fname), dir);
-+      }
-       de = (struct ext4_dir_entry_2 *)buf;
-       top = buf + buf_size - reclen;
-       while ((char *) de <= top) {
-@@ -2137,10 +2155,31 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+@@ -2137,7 +2155,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        return -EFSCORRUPTED;
                if (ext4_match(dir, fname, de))
                        return -EEXIST;
@@ -518,30 +508,6 @@ index 25e84f0e..5c068e3a 100644
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
                if ((de->inode ? rlen - nlen : rlen) >= reclen)
                        break;
-+
-+              /* Then for dotdot entries, check for the smaller space
-+               * required for just the entry, no FID
-+               */
-+              if (fname_len(fname) == 2 && memcmp(fname_name(fname), "..", 2) == 0) {
-+                      if ((de->inode ? rlen - nlen : rlen) >=
-+                          ext4_dir_rec_len(fname_len(fname), dir)) {
-+                              /* set dlen = 1 to indicate not
-+                               * enough space store fid
-+                               */
-+                              if (dlen)
-+                                      *dlen = 1;
-+                              break;
-+                      }
-+                      /* The new ".." entry must be written over the
-+                       * previous ".." entry, which is the first
-+                       * entry traversed by this scan. If it doesn't
-+                       * fit, something is badly wrong, so -EIO.
-+                       */
-+                      return -EIO;
-+              }
-               de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
-               offset += rlen;
-       }
 @@ -2155,12 +2194,13 @@ void ext4_insert_dentry(struct inode *dir,
                        struct inode *inode,
                        struct ext4_dir_entry_2 *de,
@@ -589,18 +555,15 @@ index 25e84f0e..5c068e3a 100644
 +                      dlen = (*data) + 1;
                err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
 -                                      blocksize - csum_size, fname, &de);
-+                                      blocksize - csum_size, fname, &de, &dlen);
++                                      blocksize - csum_size, fname, &de, dlen);
                if (err)
                        return err;
        }
-@@ -2218,7 +2269,10 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+@@ -2218,7 +2269,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
        }
  
        /* By now the buffer is marked for journaling */
 -      ext4_insert_dentry(dir, inode, de, blocksize, fname);
-+      /* If writing the short form of "dotdot", don't add the data section */
-+      if (dlen == 1)
-+              data = NULL;
 +      ext4_insert_dentry(dir, inode, de, blocksize, fname, data);
  
        /*
@@ -624,7 +587,7 @@ index 25e84f0e..5c068e3a 100644
  
        /* Initialize as for dx_probe */
        fname->hinfo.hash_version = dx_info->hash_version;
-@@ -2381,7 +2436,7 @@ out_frames:
+@@ -2381,12 +2406,111 @@ out_frames:
         */
        if (retval)
                ext4_mark_inode_dirty(handle, dir);
@@ -633,66 +596,151 @@ index 25e84f0e..5c068e3a 100644
        brelse(bh2);
        return retval;
  }
-@@ -2394,6 +2449,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       struct buffer_head *dir_block;
-       struct ext4_dir_entry_2 *de;
-       int len, journal = 0, err = 0;
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de, NULL);
++
++              dx_info = dx_get_dx_info(dot_de, NULL);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen, NULL) +
++                             sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de, dir);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev, dir));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen, NULL),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2395,6 +2519,8 @@ static int ext4_update_dotdot(handle_t *
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
 +      int dlen = 0;
 +      char *data;
  
        if (IS_ERR(handle))
                return PTR_ERR(handle);
-@@ -2409,21 +2466,26 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       de = (struct ext4_dir_entry_2 *)dir_block->b_data;
-       /* the first item must be "." */
--      assert(de->name_len == 1 && de->name[0] == '.');
-+      ASSERT(de->name_len == 1 && de->name[0] == '.');
-       len = le16_to_cpu(de->rec_len);
--      assert(len >= EXT4_DIR_REC_LEN(1));
--      if (len > EXT4_DIR_REC_LEN(1)) {
-+      ASSERT(len >= EXT4_DIR_REC_LEN(1, dir));
-+      if (len > EXT4_DIR_REC_LEN(1, dir)) {
-               BUFFER_TRACE(dir_block, "get_write_access");
-               err = ext4_journal_get_write_access(handle, dir->i_sb, dir_block, EXT4_JTR_NONE);
-               if (err)
-                       goto out_journal;
+@@ -2435,6 +2561,30 @@ static int ext4_update_dotdot(handle_t *
  
-               journal = 1;
--              de->rec_len = cpu_to_le16(EXT4_DIR_REC_LEN(1));
-+              de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de, dir));
-       }
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
  
--      len -= EXT4_DIR_REC_LEN(1);
--      assert(len == 0 || len >= EXT4_DIR_REC_LEN(2));
-+      len -= EXT4_DIR_ENTRY_LEN(de, NULL);
 +      data = ext4_dentry_get_data(dir->i_sb,
 +                      (struct ext4_dentry_param *)dentry->d_fsdata);
-+      if (data)
++      if (data != NULL) {
 +              dlen = *data + 1;
-+      ASSERT(len == 0 || len >= EXT4_DIR_REC_LEN(2 + dlen, dir));
-+
-       de = (struct ext4_dir_entry_2 *)
-                       ((char *) de + le16_to_cpu(de->rec_len));
-       if (!journal) {
-@@ -2437,10 +2499,15 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
-       if (len > 0)
-               de->rec_len = cpu_to_le16(len);
-       else
--              assert(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2));
-+              ASSERT(le16_to_cpu(de->rec_len) >= EXT4_DIR_REC_LEN(2, dir));
-       de->name_len = 2;
-       strcpy(de->name, "..");
--      ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+      if (data != NULL && ext4_get_dirent_data_len(de) >= dlen) {
-+              de->name[2] = 0;
-+              memcpy(&de->name[2 + 1], data, *data);
-+              ext4_set_de_type(dir->i_sb, de, S_IFDIR);
-+              de->file_type |= EXT4_DIRENT_LUFID;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen, NULL)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
 +      }
- out_journal:
-       if (journal) {
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
 @@ -2478,6 +2545,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        ext4_lblk_t block, blocks;
        int     csum_size = 0;
index 4a9b87e..73eca35 100644 (file)
@@ -796,9 +796,9 @@ diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2362,9 +2701,10 @@
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index 13c3608..d12b2b3 100644 (file)
@@ -797,9 +797,9 @@ diff -wur a/fs/ext4/namei.c b/fs/ext4/namei.c
        struct inode *dir = d_inode(dentry->d_parent);
        struct buffer_head *bh = NULL;
 @@ -2362,9 +2701,10 @@
-               if (dentry->d_name.len == 2 &&
-                    memcmp(dentry->d_name.name, "..", 2) == 0)
-                        return ext4_update_dotdot(handle, dentry, inode);
+               return ext4_update_dotdot(handle, dentry, inode);
+       if (is_dx(dir)) {
 -              retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 +              retval = ext4_dx_add_entry(handle, &fname, dir, inode, lck);
                if (!retval || (retval != ERR_BAD_DX_DIR))
index e8a8420..41e4762 100644 (file)
@@ -5,7 +5,7 @@ suse15/ext4-prealloc.patch
 suse15/ext4-osd-iop-common.patch
 suse15/ext4-misc.patch
 suse15/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 suse15/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 linux-5.4/ext4-data-in-dirent.patch
index c731d36..0a4eb11 100644 (file)
@@ -5,7 +5,7 @@ suse15/ext4-prealloc.patch
 suse15/ext4-osd-iop-common.patch
 suse15/ext4-misc.patch
 suse15/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 suse15/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 linux-5.4/ext4-data-in-dirent.patch
index a497b84..0a94c42 100644 (file)
@@ -5,7 +5,7 @@ suse15/ext4-prealloc.patch
 suse15/ext4-osd-iop-common.patch
 suse15/ext4-misc.patch
 suse15/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 suse15/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 linux-5.4/ext4-data-in-dirent.patch
index 124d2cc..41981bf 100644 (file)
@@ -5,7 +5,7 @@ suse15/ext4-prealloc.patch
 suse15/ext4-osd-iop-common.patch
 sles15sp1/ext4-misc.patch
 suse15/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 suse15/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 linux-5.4/ext4-data-in-dirent.patch
index 0d4b524..ed6e4fb 100644 (file)
@@ -5,7 +5,7 @@ suse15/ext4-prealloc.patch
 ubuntu18/ext4-osd-iop-common.patch
 ubuntu18/ext4-misc.patch
 ubuntu18/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 ubuntu18/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 ubuntu18/ext4-data-in-dirent.patch
index 6a19aa3..e0a935f 100644 (file)
@@ -5,7 +5,7 @@ suse15/ext4-prealloc.patch
 ubuntu18/ext4-osd-iop-common.patch
 ubuntu18/ext4-misc.patch
 ubuntu18/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 ubuntu18/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 ubuntu18/ext4-data-in-dirent.patch
index 20172f5..bf2aed1 100644 (file)
@@ -5,7 +5,7 @@ rhel8/ext4-prealloc.patch
 ubuntu18/ext4-osd-iop-common.patch
 rhel8.3/ext4-misc.patch
 rhel8.3/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 rhel8.1/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 rhel8.4/ext4-data-in-dirent.patch
index 260b1a9..f228bb1 100644 (file)
@@ -5,7 +5,7 @@ rhel8/ext4-prealloc.patch
 ubuntu18/ext4-osd-iop-common.patch
 rhel8.3/ext4-misc.patch
 rhel8.3/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 rhel8.1/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 rhel8.4/ext4-data-in-dirent.patch
index 47c579e..fd78795 100644 (file)
@@ -5,7 +5,7 @@ rhel8/ext4-prealloc.patch
 ubuntu18/ext4-osd-iop-common.patch
 rhel8.3/ext4-misc.patch
 rhel8.3/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 rhel8.1/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 rhel8.4/ext4-data-in-dirent.patch
index 6630dd5..e07994d 100644 (file)
@@ -5,7 +5,7 @@ rhel8/ext4-prealloc.patch
 ubuntu18/ext4-osd-iop-common.patch
 rhel8.7/ext4-misc.patch
 rhel8.7/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel8.7/ext4-hash-indexed-dir-dotdot-update.patch
 rhel8.1/ext4-kill-dx-root.patch
 rhel8.7/ext4-mballoc-pa-free-mismatch.patch
 rhel8.4/ext4-data-in-dirent.patch
index 6630dd5..e07994d 100644 (file)
@@ -5,7 +5,7 @@ rhel8/ext4-prealloc.patch
 ubuntu18/ext4-osd-iop-common.patch
 rhel8.7/ext4-misc.patch
 rhel8.7/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel8.7/ext4-hash-indexed-dir-dotdot-update.patch
 rhel8.1/ext4-kill-dx-root.patch
 rhel8.7/ext4-mballoc-pa-free-mismatch.patch
 rhel8.4/ext4-data-in-dirent.patch
index 6630dd5..e07994d 100644 (file)
@@ -5,7 +5,7 @@ rhel8/ext4-prealloc.patch
 ubuntu18/ext4-osd-iop-common.patch
 rhel8.7/ext4-misc.patch
 rhel8.7/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel8.7/ext4-hash-indexed-dir-dotdot-update.patch
 rhel8.1/ext4-kill-dx-root.patch
 rhel8.7/ext4-mballoc-pa-free-mismatch.patch
 rhel8.4/ext4-data-in-dirent.patch
index 78ce11d..8265f95 100644 (file)
@@ -5,7 +5,7 @@ rhel8/ext4-prealloc.patch
 ubuntu18/ext4-osd-iop-common.patch
 ubuntu19/ext4-misc.patch
 rhel8/ext4-mballoc-extra-checks.patch
-ubuntu18/ext4-hash-indexed-dir-dotdot-update.patch
+rhel7.6/ext4-hash-indexed-dir-dotdot-update.patch
 ubuntu18/ext4-kill-dx-root.patch
 rhel7.6/ext4-mballoc-pa-free-mismatch.patch
 ubuntu18/ext4-data-in-dirent.patch
index 4f9a877..e06b52b 100644 (file)
@@ -8,7 +8,7 @@ linux-5.18/ext4-mballoc-extra-checks.patch
 sles15sp4/ext4-hash-indexed-dir-dotdot-update.patch
 linux-5.14/ext4-kill-dx-root.patch
 linux-5.18/ext4-mballoc-pa-free-mismatch.patch
-linux-5.18/ext4-data-in-dirent.patch
+rhel9.1/ext4-data-in-dirent.patch
 rhel8/ext4-nocmtime.patch
 base/ext4-htree-lock.patch
 rhel9.1/ext4-pdirop.patch
index a292a4c..be48ac6 100644 (file)
@@ -5806,9 +5806,10 @@ static int osd_index_ea_delete(const struct lu_env *env, struct dt_object *dt,
                 * the entry after the agent had been removed, or leave a
                 * dangling entry pointing at a random inode.
                 */
-               if (strcmp((char *)key, dotdot) != 0)
+               if (strcmp((char *)key, dotdot) != 0) {
                        osd_take_care_of_agent(env, osd, oh, de);
-               rc = ldiskfs_delete_entry(oh->ot_handle, dir, de, bh);
+                       rc = ldiskfs_delete_entry(oh->ot_handle, dir, de, bh);
+               }
                brelse(bh);
        } else {
                rc = PTR_ERR(bh);
index 2d42f25..8b5c432 100755 (executable)
@@ -11187,6 +11187,111 @@ test_153a() {
 }
 run_test 153a "bypass invalid NIDs quickly"
 
+test_154() {
+       [ "$mds1_FSTYPE" == "ldiskfs" ] || skip "ldiskfs only test"
+       (( $MDS1_VERSION >= $(version_code 2.15.63.1) )) ||
+               skip "Need MDS version at least 2.15.63.1"
+       local mdt1=$(mdsdevname 1)
+       local cmd
+       local parentfid
+       local dotdotfid
+       local ino
+
+       reformat
+       setupall
+
+       # create rename test dir on MDT0000 to simplify later debugfs checks
+       test_mkdir -c 1 -i 0 $DIR/$tdir || error "mkdir $tdir failed"
+       test_mkdir -c 1 -i 0 $DIR/$tdir.tgt || error "mkdir $tdir.tgt failed"
+
+       for name in dx_dir dirent empty; do
+               test_mkdir -c 1 -i 0 $DIR/$tdir.$name ||
+                       error "mkdir $tdir.$name failed"
+       done
+
+       # make this directory large enough to htree split
+       createmany -o $DIR/$tdir.dx_dir/$tfile.longfilename 128
+       # put 2 entries after dotdot in this directory
+       createmany -o $DIR/$tdir.dirent/$tfile.longfilename 2
+
+       for name in dx_dir dirent empty; do
+               mv $DIR/$tdir.$name $DIR/$tdir || error "mv $tdir.$name failed"
+       done
+
+       parentfid="fid:$($LFS path2fid $DIR/$tdir)"
+       echo "==target parent FID: $parentfid=="
+
+       stopall
+
+       # check that ".." FID is updated during normal operation
+       echo "==debugfs before backup=="
+       for name in dx_dir dirent empty; do
+               cmd="debugfs -c -R 'stat ROOT/$tdir/$tdir.$name' $mdt1"
+               do_facet mds1 "$cmd" ||
+                       error "debugfs stat before backup failed"
+               cmd="debugfs -c -R 'ls -lD ROOT/$tdir/$tdir.$name' $mdt1"
+               do_facet mds1 "$cmd" ||
+                       error "debugfs before backup $name failed"
+               dotdotfid=$(do_facet mds1 "$cmd" |& awk '/fid:.+\.\./ { print $9 }')
+               [[ "$dotdotfid" == "$parentfid" ]] ||
+                       error "parent '$parentfid' != dotdot '$dotdotfid' on $name"
+       done
+
+       for ((i = 1; i <= $MDSCOUNT; i++ )); do
+               mds_backup_restore mds$i ||
+                       error "Backup/restore on mds$i failed"
+       done
+
+       setupall
+
+       # verify that rename of the restored directory updates the
+       # ".." entry in the directory with the parent FID
+       echo "==pre-rename parent/.. inodes=="
+       for name in dx_dir dirent empty; do
+               ls -dial $DIR/$tdir $DIR/$tdir/$tdir.$name/.. ||
+                       error "ls on original dirs failed"
+               ino=($(stat -c "%i" $DIR/$tdir $DIR/$tdir/$tdir.$name/..))
+               (( ${ino[0]} == ${ino[1]} )) ||
+                       error "ino $DIR/$tdir ${ino[0]} != $tdir.$name/.. ${ino[1]}"
+       done
+
+       for name in dx_dir dirent empty; do
+               mv $DIR/$tdir/$tdir.$name $DIR/$tdir.tgt ||
+                       error "mv $tdir.$name failed"
+       done
+
+       echo "==post-rename parent/.. inodes=="
+       for name in dx_dir dirent empty; do
+               ls -dial $DIR/$tdir.tgt $DIR/$tdir.tgt/$tdir.$name/.. ||
+                       error "ls on renamed dirs failed"
+               ino=($(stat -c "%i" $DIR/$tdir.tgt $DIR/$tdir.tgt/$tdir.$name/..))
+               (( ${ino[0]} == ${ino[1]} )) ||
+                       error "ino $DIR/$tdir.tgt ${ino[0]} != $tdir.$name/.. ${ino[1]}"
+       done
+
+       parentfid="fid:$($LFS path2fid $DIR/$tdir.tgt)"
+       echo "==target parent FID: $parentfid=="
+
+       stopall
+
+       for name in dx_dir dirent empty; do
+               echo "==post-rename .$name should have '$parentfid ..'=="
+               cmd="debugfs -c -R 'stat ROOT/$tdir.tgt/$tdir.$name' $mdt1"
+               do_facet mds1 "$cmd" ||
+                       error "debugfs stat after rename failed"
+               cmd="debugfs -c -R 'ls -lD ROOT/$tdir.tgt/$tdir.$name' $mdt1"
+               do_facet mds1 "$cmd" ||
+                       error "debugfs ls .$name after rename failed"
+               dotdotfid=$(do_facet mds1 "$cmd" |& awk '/fid:.+\.\./ { print $9 }')
+               [[ "$dotdotfid" == "$parentfid" ]] ||
+                       error "parent '$parentfid' != dotdot '$dotdotfid' on $name"
+       done
+
+       FSCK_MAX_ERR=0 run_e2fsck $(facet_active_host mds1) $mdt1 -n ||
+               error "e2fsck returned $?"
+}
+run_test 154 "expand .. on rename after MDT backup restore"
+
 #
 # (This was sanity/802a)
 #
index 655f06d..44d92cf 100755 (executable)
@@ -6929,13 +6929,13 @@ debugsave() {
 
 debugrestore() {
        [ -n "$DEBUGSAVE" ] &&
-               do_nodes $CLIENTS "$LCTL set_param debug=\\\"${DEBUGSAVE}\\\""||
+               do_nodes $CLIENTS $LCTL set_param -n debug=${DEBUGSAVE// /+} ||
                true
        DEBUGSAVE=""
 
        [ -n "$DEBUGSAVE_SERVER" ] &&
                do_nodes $(comma_list $(all_server_nodes)) \
-                        "$LCTL set_param debug=\\\"${DEBUGSAVE_SERVER}\\\"" ||
+                        $LCTL set_param -n debug=${DEBUGSAVE_SERVER// /+} ||
                         true
        DEBUGSAVE_SERVER=""
 }