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;