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,
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;
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;
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,
+ 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);
/*
/* 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;
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))
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,
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;
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;
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,
+ 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);
/*
/* 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 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);
+ 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
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))
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,
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;
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;
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,
+ 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);
/*
/* 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;
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,
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;
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;
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,
+ 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);
/*
/* 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);
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;
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))
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))
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,
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;
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;
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,
+ 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);
/*
/* 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;
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,
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;
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,
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);
/*
/* 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;
}
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;
===================================================================
--- 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);
+ 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;
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))
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,
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;
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;
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,
+ 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);
/*
/* 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;
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))
--- /dev/null
+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))
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))
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))
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,
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;
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;
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,
+ 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);
/*
/* 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);
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;
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))
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,
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;
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;
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,
+ 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);
/*
/* 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);
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;
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))
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,
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;
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;
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,
+ 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);
/*
/* 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);
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;
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,
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;
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;
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,
+ 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);
/*
/* 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);
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;
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))
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))
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,
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;
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;
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,
+ 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);
/*
/* 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;
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))
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,
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;
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;
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,
+ 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);
/*
/* 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);
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 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);
+ 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
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))
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,
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;
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,
+ 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);
/*
/* 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;
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;
+++ /dev/null
-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;
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))
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,
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;
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;
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,
+ 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);
/*
/* 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);
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;
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))
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))
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
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
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
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
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
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
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
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
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
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
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
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
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
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
* 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);
}
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)
#
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=""
}