From: Artem Blagodarenko Add helpers to set and retrieve dirdata payload and hook them up at the appropriate call sites. Enable dirdata for casefold+encryption hashes and storing unique 128-bit file identifier in the directory entry for testing. Signed-off-by: Artem Blagodarenko Reviewed-by: Andreas Dilger --- foofile.txt | 0 fs/ext4/ext4.h | 4 + fs/ext4/inline.c | 6 +- fs/ext4/namei.c | 196 ++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 176 insertions(+), 30 deletions(-) diff --git a/foofile.txt b/foofile.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index ef99e4fa99d7..defa18d98c74 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -3787,6 +3787,10 @@ extern int __ext4_unlink(struct inode *dir, const struct qstr *d_name, struct inode *inode, struct dentry *dentry); extern int __ext4_link(struct inode *dir, struct inode *inode, const struct qstr *d_name, struct dentry *dentry); +extern unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, + struct inode *dir, + struct ext4_dirent_fid *lufid, + struct dx_hash_info *hinfo); #define S_SHIFT 12 static const unsigned char ext4_type_by_mode[(S_IFMT >> S_SHIFT) + 1] = { diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index c57a8ebe4f94..71c395c9a162 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c @@ -1346,10 +1346,8 @@ int ext4_inlinedir_to_tree(struct file *dir_file, } } - if (ext4_hash_in_dirent(dir)) { - hinfo->hash = EXT4_DIRENT_HASH(de); - hinfo->minor_hash = EXT4_DIRENT_MINOR_HASH(de); - } else { + if (!(ext4_dirdata_get(de, dir, NULL, hinfo) & + EXT4_DIRENT_CFHASH)) { err = ext4fs_dirhash(dir, de->name, de->name_len, hinfo); if (err) { ret = err; diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 40a3394f7eac..c799f87d7459 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -1084,22 +1084,22 @@ static int htree_dirblock_to_tree(struct file *dir_file, /* silently ignore the rest of the block */ break; } - if (ext4_hash_in_dirent(dir)) { - if (de->name_len && de->inode) { - hinfo->hash = EXT4_DIRENT_HASH(de); - hinfo->minor_hash = EXT4_DIRENT_MINOR_HASH(de); - } else { - hinfo->hash = 0; - hinfo->minor_hash = 0; - } + if (de->name_len && de->inode) { + /* check for saved hash first, or generate it from name */ + if (!(ext4_dirdata_get(de, dir, NULL, hinfo) & + EXT4_DIRENT_CFHASH)) { + err = ext4fs_dirhash(dir, de->name, + de->name_len, hinfo); + if (err < 0) { + count = err; + goto errout; + } + } } else { - err = ext4fs_dirhash(dir, de->name, - de->name_len, hinfo); - if (err < 0) { - count = err; - goto errout; - } + hinfo->hash = 0; + hinfo->minor_hash = 0; } + if ((hinfo->hash < start_hash) || ((hinfo->hash == start_hash) && (hinfo->minor_hash < start_minor_hash))) @@ -1277,9 +1277,160 @@ static inline int search_dirblock(struct buffer_head *bh, */ /* - * Create map of hash values, offsets, and sizes, stored at end of block. - * Returns number of entries mapped. + * ext4_dirdata_get() - Read dirdata fields from a directory entry. + * @de: directory entry + * @dir: directory inode (used for fscrypt+casefold hash fallback) + * @dfid: if non-NULL and EXT4_DIRENT_LUFID is set, LUFID data is copied + * here + * @hinfo: if non-NULL, receives the casefold hash and minor hash + * + * Reads any dirdata stored in @de. If the dirdata feature is not enabled, + * falls back to reading the hash stored inline after the filename (for + * compatibility with the older casefold+fscrypt format). + * + * Returns a bitmask of EXT4_DIRENT_* flags indicating which fields were read. */ +unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir, + struct ext4_dirent_fid *dfid, + struct dx_hash_info *hinfo) +{ + unsigned char ret = 0; + unsigned int data_offset = de->name_len + 1; + + if (data_offset > de->rec_len) + return ret; + + /* compatibility: hash stored inline after filename (no dirdata) */ + if (hinfo && !ext4_has_feature_dirdata(dir->i_sb) && + ext4_hash_in_dirent(dir)) { + hinfo->hash = EXT4_DIRENT_HASH(de); + hinfo->minor_hash = EXT4_DIRENT_MINOR_HASH(de); + ret |= EXT4_DIRENT_CFHASH; + + return ret; + } + + /* EXT4_DIRENT_* are not expected without flag in i_sb */ + if (de->file_type & EXT4_DIRENT_LUFID) { + struct ext4_dirent_fid *dfid = + (struct ext4_dirent_fid *)(de->name + data_offset); + unsigned int dlen; + + if (data_offset + sizeof(dfid->df_header) > de->rec_len) + return ret; + + dlen = dfid->df_header.ddh_length; + if (dlen < sizeof(*dfid) || data_offset + dlen > de->rec_len) + return ret; + + if (dfid) { + memcpy(dfid, dfid->df_fid, dfid->df_header.ddh_length); + ret |= EXT4_DIRENT_LUFID; + } + data_offset += dlen; + } + + /* Skip INO64 for now*/ + if (de->file_type & EXT4_DIRENT_INO64) { + struct ext4_dirent_data_header *ddh = + (struct ext4_dirent_data_header *)(de->name + data_offset); + unsigned int dlen; + + if (data_offset + sizeof(*ddh) > de->rec_len) + return ret; + + dlen = ddh->ddh_length; + if (dlen < sizeof(*ddh) || data_offset + dlen > de->rec_len) + return ret; + + data_offset += dlen; + } + + if (!hinfo) + return ret; + + if (de->file_type & EXT4_DIRENT_CFHASH) { + struct ext4_dirent_hash *dh = + (struct ext4_dirent_hash *)(de->name + data_offset); + unsigned int dlen; + + dlen = dh->dh_header.ddh_length; + if (dlen < sizeof(*dh) || data_offset + dlen > de->rec_len) + return ret; + + hinfo->hash = le32_to_cpu(dh->dh_hash.hash); + hinfo->minor_hash = le32_to_cpu(dh->dh_hash.minor_hash); + ret |= EXT4_DIRENT_CFHASH; + } + + return ret; +} + +/* + * ext4_dirdata_set() - Write dirdata fields into a directory entry. + * @de: directory entry (name must already be set) + * @dir: directory inode + * @data: LUFID data to store (or NULL) + * @fname: filename info carrying the casefold hash + * + * Writes any required dirdata into @de after the filename. If the dirdata + * feature is not enabled, falls back to writing the hash inline after the + * filename (for compatibility with the older casefold+fscrypt format). + */ +static void ext4_dirdata_set(struct ext4_dir_entry_2 *de, struct inode *dir, + struct ext4_dirent_fid *dfid, + struct ext4_filename *fname) +{ + struct dx_hash_info *hinfo = &fname->hinfo; + unsigned int data_offset = de->name_len + 1; + + + if (dfid) { + unsigned int dlen = dfid->df_header.ddh_length; + + if (data_offset + dlen > de->rec_len) { + EXT4_ERROR_INODE(dir, "Can not insert FID"); + return; + } + + + de->name[de->name_len] = 0; + memcpy(&de->name[de->name_len + 1], dfid, + dlen); + de->file_type |= EXT4_DIRENT_LUFID; + data_offset += dlen; + } + + if (ext4_hash_in_dirent(dir)) { + if (ext4_has_feature_dirdata(dir->i_sb)) { + struct ext4_dirent_hash *dh = + (struct ext4_dirent_hash *)(de->name + data_offset); + + if (data_offset + sizeof(*dh) > de->rec_len) { + EXT4_ERROR_INODE(dir, "Can not insert dhash dirdata"); + return; + } + + dh->dh_header.ddh_length = sizeof(*dh); + dh->dh_hash.hash = cpu_to_le32(hinfo->hash); + dh->dh_hash.minor_hash = cpu_to_le32(hinfo->minor_hash); + de->file_type |= EXT4_DIRENT_CFHASH; + } else { + /* Compatibility: store hash inline after filename */ + if (data_offset + sizeof(struct ext4_dir_entry_hash) > + de-> rec_len) { + EXT4_ERROR_INODE(dir, "Can not insert dhash"); + return; + } + + EXT4_DIRENT_HASHES(de)->hash = cpu_to_le32(hinfo->hash); + EXT4_DIRENT_HASHES(de)->minor_hash = + cpu_to_le32(hinfo->minor_hash); + } + } +} + + static int dx_make_map(struct inode *dir, struct buffer_head *bh, struct dx_hash_info *hinfo, struct dx_map_entry *map_tail) @@ -1299,9 +1450,8 @@ static int dx_make_map(struct inode *dir, struct buffer_head *bh, ((char *)de) - base)) return -EFSCORRUPTED; if (de->name_len && de->inode) { - if (ext4_hash_in_dirent(dir)) - h.hash = EXT4_DIRENT_HASH(de); - else { + if (!(ext4_dirdata_get(de, dir, NULL, &h) & + EXT4_DIRENT_CFHASH)) { int err = ext4fs_dirhash(dir, de->name, de->name_len, &h); if (err < 0) @@ -2089,13 +2239,7 @@ void ext4_insert_dentry_data(struct inode *dir, struct inode *inode, ext4_set_de_type(inode->i_sb, de, inode->i_mode); de->name_len = fname_len(fname); memcpy(de->name, fname_name(fname), fname_len(fname)); - if (ext4_hash_in_dirent(dir)) { - struct dx_hash_info *hinfo = &fname->hinfo; - - EXT4_DIRENT_HASHES(de)->hash = cpu_to_le32(hinfo->hash); - EXT4_DIRENT_HASHES(de)->minor_hash = - cpu_to_le32(hinfo->minor_hash); - } + ext4_dirdata_set(de, dir, data, fname); } /* -- 2.43.7