Viewing: ext4-dir-entry-len.patch

Index: linux-stage/fs/ext4/ext4.h
===================================================================
--- linux-stage.orig/fs/ext4/ext4.h
+++ linux-stage/fs/ext4/ext4.h
@@ -1191,6 +1191,7 @@ struct ext4_inode_info {
  * Mount flags set via mount options or defaults
  */
 #define EXT4_MOUNT_NO_MBCACHE		0x00001 /* Do not use mbcache */
+#define EXT4_MOUNT_DIRDATA             0x00002 /* Data in directory entries */
 #define EXT4_MOUNT_GRPID		0x00004	/* Create files with directory's group */
 #define EXT4_MOUNT_DEBUG		0x00008	/* Some debugging messages */
 #define EXT4_MOUNT_ERRORS_CONT		0x00010	/* Continue on errors */
@@ -2193,6 +2194,7 @@ static inline bool ext4_has_feature_encr
 					 EXT4_FEATURE_INCOMPAT_FLEX_BG| \
 					 EXT4_FEATURE_INCOMPAT_EA_INODE| \
 					 EXT4_FEATURE_INCOMPAT_MMP | \
+					 EXT4_FEATURE_INCOMPAT_DIRDATA | \
 					 EXT4_FEATURE_INCOMPAT_INLINE_DATA | \
 					 EXT4_FEATURE_INCOMPAT_ENCRYPT | \
 					 EXT4_FEATURE_INCOMPAT_CASEFOLD | \
@@ -2404,6 +2406,35 @@ struct ext4_dir_entry_tail {
 #define EXT4_FT_SYMLINK		7
 
 #define EXT4_FT_MAX		8
+#define EXT4_FT_MASK           0xf
+
+#if EXT4_FT_MAX > EXT4_FT_MASK
+#error "conflicting EXT4_FT_MAX and EXT4_FT_MASK"
+#endif
+
+/*
+ * d_type has 4 unused bits, so it can hold four types data. these different
+ * type of data (e.g. lustre data, high 32 bits of 64-bit inode number) can be
+ * stored, in flag order, after file-name in ext4 dirent.
+*/
+/*
+ * These flags is added to d_type if ext4 dirent has extra data after
+ * filename. This data length is variable and length is stored in first byte
+ * of data. Data starts after filename NUL byte.
+ */
+#define EXT4_DIRENT_LUFID		0x10
+#define EXT4_DIRENT_INO64		0x20
+#define EXT4_DIRENT_CFHASH		0x40
+
+struct ext4_dirent_data_header {
+	/* length of this header + the whole data blob */
+	__u8	ddh_length;
+} __packed;
+
+struct ext4_dirent_hash {
+	struct ext4_dirent_data_header	dh_header;
+	struct ext4_dir_entry_hash	dh_hash;
+} __packed;
 
 #define EXT4_FT_DIR_CSUM	0xDE
 
@@ -2416,16 +2447,28 @@ struct ext4_dir_entry_tail {
 #define EXT4_DIR_ROUND			(EXT4_DIR_PAD - 1)
 #define EXT4_MAX_REC_LEN		((1<<16)-1)
 
+#define EXT4_DIR_REC_LEN_(name_len, i_dir) \
+       ext4_dir_rec_len((name_len), (i_dir))
+#define EXT4_DIR_ENTRY_LEN_(de, i_dir) \
+       (EXT4_DIR_REC_LEN_((de)->name_len + ext4_get_dirent_data_len(de), \
+               (i_dir)))
+/* ldiskfs */
+#define EXT4_DIR_REC_LEN(name_len, i_dir)      EXT4_DIR_REC_LEN_((name_len), (i_dir))
+#define EXT4_DIR_ENTRY_LEN(de, i_dir)          EXT4_DIR_ENTRY_LEN_((de), (i_dir))
+/* lustre osd_handler compat -- ifdef LDISKFS_DIR_REC_LEN_WITH_DIR */
+#define EXT4_DIR_REC_LEN_WITH_DIR              1
+#define __EXT4_DIR_REC_LEN(name_len)           EXT4_DIR_REC_LEN_((name_len), NULL)
+
 /*
  * The rec_len is dependent on the type of directory. Directories that are
  * casefolded and encrypted need to store the hash as well, so we add room for
  * ext4_extended_dir_entry_2. For all entries related to '.' or '..' you should
  * pass NULL for dir, as those entries do not use the extra fields.
  */
-static inline unsigned int ext4_dir_rec_len(__u8 name_len,
+static inline unsigned int ext4_dir_rec_len(__u32 name_len,
 						const struct inode *dir)
 {
-	int rec_len = (name_len + 8 + EXT4_DIR_ROUND);
+	__u32 rec_len = (name_len + 8 + EXT4_DIR_ROUND);
 
 	if (dir && ext4_hash_in_dirent(dir))
 		rec_len += sizeof(struct ext4_dir_entry_hash);
@@ -2879,10 +2922,16 @@ static const unsigned char ext4_filetype
 
 static inline  unsigned char get_dtype(struct super_block *sb, int filetype)
 {
-	if (!ext4_has_feature_filetype(sb) || filetype >= EXT4_FT_MAX)
+	int fl_index = filetype & EXT4_FT_MASK;
+
+	if (!ext4_has_feature_filetype(sb) || fl_index >= EXT4_FT_MAX)
 		return DT_UNKNOWN;
 
-	return ext4_filetype_table[filetype];
+	if (!test_opt(sb, DIRDATA))
+		return ext4_filetype_table[fl_index];
+
+	return (ext4_filetype_table[fl_index]) |
+		(filetype & ~EXT4_FT_MASK);
 }
 extern int ext4_check_all_de(struct inode *dir, struct buffer_head *bh,
 			     void *buf, int buf_size);
@@ -3879,6 +3928,48 @@ static inline void ext4_clear_io_unwritt
 	}
 }
 
+#define ext4_dirdata_next(ddh) \
+	(struct ext4_dirent_data_header *)((char *)ddh + ddh->ddh_length)
+/*
+ * Compute the total directory entry data length.
+ * This includes the filename and an implicit NUL terminator (always present),
+ * and optional extensions. Each extension has a bit set in the high 4 bits of
+ * de->file_type, and the extension length is the first byte in each entry.
+ */
+static inline int ext4_get_dirent_data_len(struct ext4_dir_entry_2 *de)
+{
+	struct ext4_dirent_data_header *ddh =
+		(struct ext4_dirent_data_header *)de->name + de->name_len +
+		1 /* NUL terminator */;
+	__u8 extra_data_flags = (de->file_type & ~EXT4_FT_MASK) >> 4;
+	struct ext4_dir_entry_tail *t = (struct ext4_dir_entry_tail *)de;
+	int dlen = 0;
+
+	if (!t->det_reserved_zero1 &&
+	    le16_to_cpu(t->det_rec_len) ==
+		sizeof(struct ext4_dir_entry_tail) &&
+	    !t->det_reserved_zero2 &&
+	    t->det_reserved_ft == EXT4_FT_DIR_CSUM)
+		return 0;
+
+	while (extra_data_flags) {
+		if (extra_data_flags & 1) {
+			dlen += ddh->ddh_length + (dlen == 0);
+			ddh = ext4_dirdata_next(ddh);
+		}
+		extra_data_flags >>= 1;
+	}
+	return dlen;
+}
+
+/* Inline function to calculate directory entry length */
+static inline unsigned int ext4_dir_entry_len(struct ext4_dir_entry_2 *de,
+					      const struct inode *dir)
+{
+	unsigned int name_len = de->name_len + ext4_get_dirent_data_len(de);
+	return ext4_dir_rec_len(name_len, dir);
+}
+
 extern const struct iomap_ops ext4_iomap_ops;
 extern const struct iomap_ops ext4_iomap_overwrite_ops;
 extern const struct iomap_ops ext4_iomap_report_ops;
Index: linux-stage/fs/ext4/namei.c
===================================================================
--- linux-stage.orig/fs/ext4/namei.c
+++ linux-stage/fs/ext4/namei.c
@@ -557,13 +557,15 @@ ext4_next_entry(struct ext4_dir_entry_2
  * Future: use high four bits of block for coalesce-on-delete flags
  * Mask them off for now.
  */
-struct dx_root_info *dx_get_dx_info(struct ext4_dir_entry_2 *de)
+static struct dx_root_info *dx_get_dx_info(struct ext4_dir_entry_2 *de)
 {
 	/* get dotdot first */
-	de = (struct ext4_dir_entry_2 *)((char *)de + EXT4_DIR_REC_LEN(1));
+	de = (struct ext4_dir_entry_2 *)((char *)de +
+		ext4_dir_entry_len(de, NULL));
 
 	/* dx root info is after dotdot entry */
-	de = (struct ext4_dir_entry_2 *)((char *)de + EXT4_DIR_REC_LEN(2));
+	de = (struct ext4_dir_entry_2 *)((char *)de +
+		ext4_dir_entry_len(de, NULL));
 
 	return (struct dx_root_info *)de;
 }
@@ -2384,7 +2386,7 @@ out_frames:
 	return retval;
 }
 
-/* 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)
 {
@@ -3062,7 +3064,7 @@ struct ext4_dir_entry_2 *ext4_init_dot_d
 					blocksize);
 	else
 		de->rec_len = ext4_rec_len_to_disk(
-					ext4_dir_rec_len(de->name_len, NULL),
+					ext4_dir_rec_len(de, NULL),
 					blocksize);
 	strcpy(de->name, "..");
 	ext4_set_de_type(inode->i_sb, de, S_IFDIR);