From: Artem Blagodarenko Implement comprehensive validation and repair logic for dirdata fields in pass 2 of e2fsck. This ensures directory entries with embedded Lustre FID data are properly checked and corrected. Signed-off-by: Andreas Dilger Signed-off-by: Pravin Shelar Signed-off-by: Artem Blagodarenko Reviewed-by: Andreas Dilger --- e2fsck/pass2.c | 130 ++++++++++++++++++++++++++++++++++++++++++----- e2fsck/problem.c | 5 ++ e2fsck/problem.h | 3 ++ 3 files changed, 125 insertions(+), 13 deletions(-) diff --git a/e2fsck/pass2.c b/e2fsck/pass2.c index cf1678c85..938942a80 100644 --- a/e2fsck/pass2.c +++ b/e2fsck/pass2.c @@ -395,13 +395,103 @@ static EXT2_QSORT_TYPE special_dir_block_cmp(const void *a, const void *b) return (int) (db_a->blockcnt - db_b->blockcnt); } +void ext2_fix_dirent_dirdata(struct ext2_dir_entry *de) +{ + __u16 file_type = de->name_len & (EXT2_FT_MASK << 8); + __u8 de_flags = (de->name_len >> 8) & ~EXT2_FT_MASK; + __u8 name_len = de->name_len & EXT2_NAME_LEN; + __u8 new_flag = 0; + int i; + + for (i = 0; i < 4; i++) { + __u8 flags = new_flag | (1 << i) << 4; + + /* new_flag is accumulating flags that are set in de_flags + * and still fit inside rec_len. ext2_get_dirent_dirdata_size() + * returns the size of all the dirdata entries in flags, and + * chops off any that are beyond rec_len. + */ + if ((de_flags & flags) == flags) { + int dirdatalen = ext2_get_dirdata_field_size(de,flags); + int rlen = EXT2_DIR_NAME_LEN(name_len + 1 + + dirdatalen); + + if (rlen > de->rec_len) + break; + + new_flag |= flags; + } + } + + de->name_len = name_len | file_type | (new_flag << 8); +} + +/* + * check for dirent data in ext4 dirent. + * return 0 if dirent data is ok. + * return 1 if dirent data does not exist. + * return 2 if dirent was modified due to error. + */ +int e2fsck_check_dirent_data(e2fsck_t ctx, struct ext2_dir_entry *de, + unsigned int offset, struct problem_context *pctx) +{ + if (!(ctx->fs->super->s_feature_incompat & + EXT4_FEATURE_INCOMPAT_DIRDATA)) { + if ((de->name_len >> 8) & ~EXT2_FT_MASK) { + /* clear dirent extra data flags. */ + if (fix_problem(ctx, PR_2_CLEAR_DIRDATA, pctx)) { + de->name_len &= (EXT2_FT_MASK << 8) | + EXT2_NAME_LEN; + return 2; + } + } + return 1; + } + + if (!((de->name_len >> 8) & ~EXT2_FT_MASK)) + return 1; + + if (de->rec_len >= EXT2_DIRENT_REC_LEN(de) || + de->rec_len + offset == EXT2_BLOCK_SIZE(ctx->fs->super)) { + int lufid_size, cshash_size; + + lufid_size = ext2_get_dirdata_field_size(de, + EXT2_DIRENT_LUFID); + + if (lufid_size != 0 && lufid_size % + EXT2_DIRENT_LUFID_SIZE != 1) /* size byte */ + goto fix; + + cshash_size = ext2_get_dirdata_field_size(de, + EXT2_DIRENT_CFHASH); + + if (cshash_size != 0 && cshash_size != + sizeof(struct ext2_dir_entry_hash) + 1) /* size byte */ + goto fix; + + return 0; + } + +fix: + /* just clear dirent data flags for now */ + if (fix_problem(ctx, PR_2_CLEAR_DIRDATA, pctx)) { + ext2_fix_dirent_dirdata(de); + if (ext2_get_dirdata_field_size(de, + EXT2_DIRENT_LUFID) % + EXT2_DIRENT_LUFID_SIZE != 1) + de->name_len &= ~(EXT2_DIRENT_LUFID << 8); + + return 2; + } + return 1; +} /* * Make sure the first entry in the directory is '.', and that the * directory entry is sane. */ static int check_dot(e2fsck_t ctx, - struct ext2_dir_entry *dirent, + struct ext2_dir_entry *dirent, unsigned int offset, ext2_ino_t ino, struct problem_context *pctx) { struct ext2_dir_entry *nextdir; @@ -410,6 +500,7 @@ static int check_dot(e2fsck_t ctx, int created = 0; problem_t problem = 0; int ftype = EXT2_FT_DIR; + int dir_data_error; if (!dirent->inode) problem = PR_2_MISSING_DOT; @@ -419,12 +510,14 @@ static int check_dot(e2fsck_t ctx, else if (dirent->name[1] != '\0') problem = PR_2_DOT_NULL_TERM; + dir_data_error = e2fsck_check_dirent_data(ctx, dirent, offset, pctx); + (void) ext2fs_get_rec_len(ctx->fs, dirent, &rec_len); if (problem) { if (!ext2fs_has_feature_filetype(ctx->fs->super)) ftype = EXT2_FT_UNKNOWN; if (fix_problem(ctx, problem, pctx)) { - if (rec_len < 12) + if (rec_len < 12 && dir_data_error) rec_len = dirent->rec_len = 12; dirent->inode = ino; ext2fs_dirent_set_name_len(dirent, 1); @@ -443,7 +536,7 @@ static int check_dot(e2fsck_t ctx, } if (rec_len > 12) { new_len = rec_len - 12; - if (new_len > 12) { + if (new_len > 12 && dir_data_error) { if (created || fix_problem(ctx, PR_2_SPLIT_DOT, pctx)) { nextdir = (struct ext2_dir_entry *) @@ -480,12 +573,13 @@ static int check_dot(e2fsck_t ctx, * here; this gets done in pass 3. */ static int check_dotdot(e2fsck_t ctx, - struct ext2_dir_entry *dirent, + struct ext2_dir_entry *dirent, unsigned int offset, ext2_ino_t ino, struct problem_context *pctx) { problem_t problem = 0; unsigned int rec_len; int ftype = EXT2_FT_DIR; + int dir_data_error; if (!dirent->inode) problem = PR_2_MISSING_DOT_DOT; @@ -496,12 +590,14 @@ static int check_dotdot(e2fsck_t ctx, else if (dirent->name[2] != '\0') problem = PR_2_DOT_DOT_NULL_TERM; + dir_data_error = e2fsck_check_dirent_data(ctx, dirent, offset, pctx); + (void) ext2fs_get_rec_len(ctx->fs, dirent, &rec_len); if (problem) { if (!ext2fs_has_feature_filetype(ctx->fs->super)) ftype = EXT2_FT_UNKNOWN; if (fix_problem(ctx, problem, pctx)) { - if (rec_len < 12) + if (rec_len < 12 && dir_data_error) dirent->rec_len = 12; /* * Note: we don't have the parent inode just @@ -597,6 +693,12 @@ static _INLINE_ int check_filetype(e2fsck_t ctx, int filetype = ext2fs_dirent_file_type(dirent); int should_be = EXT2_FT_UNKNOWN; struct ext2_inode inode; + __u8 dirdata = 0; + + if (ext2fs_has_feature_dirdata(ctx->fs->super)) { + dirdata = filetype & ~EXT2_FT_MASK; + filetype = filetype & EXT2_FT_MASK; + } if (!ext2fs_has_feature_filetype(ctx->fs->super)) { if (filetype == 0 || @@ -627,8 +729,7 @@ static _INLINE_ int check_filetype(e2fsck_t ctx, if (fix_problem(ctx, filetype ? PR_2_BAD_FILETYPE : PR_2_SET_FILETYPE, pctx) == 0) return 0; - - ext2fs_dirent_set_file_type(dirent, should_be); + ext2fs_dirent_set_file_type(dirent, should_be | dirdata); return 1; } @@ -660,8 +761,8 @@ static void parse_int_node(ext2_filsys fs, printf("\t Indirect levels: %u\n", root->indirect_levels); printf("\t Flags: %x\n", root->unused_flags); #endif - - ent = (struct ext2_dx_entry *) (block_buf + 24 + root->info_length); + ent = (struct ext2_dx_entry *)((char *)root + + root->info_length); if (failed_csum && (e2fsck_dir_will_be_rehashed(cd->ctx, cd->pctx.ino) || @@ -669,7 +770,7 @@ static void parse_int_node(ext2_filsys fs, &cd->pctx))) goto clear_and_exit; } else { - ent = (struct ext2_dx_entry *) (block_buf+8); + ent = (struct ext2_dx_entry *)(block_buf + 8); if (failed_csum && (e2fsck_dir_will_be_rehashed(cd->ctx, cd->pctx.ino) || @@ -851,7 +952,6 @@ static void salvage_directory(ext2_filsys fs, } } -#define NEXT_DIRENT(d) ((void *)((char *)(d) + (d)->rec_len)) static errcode_t insert_dirent_tail(ext2_filsys fs, void *dirbuf) { struct ext2_dir_entry *d; @@ -1427,10 +1527,10 @@ skip_checksum: } if (dot_state == 0) { - if (check_dot(ctx, dirent, ino, &cd->pctx)) + if (check_dot(ctx, dirent, offset, ino, &cd->pctx)) dir_modified++; } else if (dot_state == 1) { - ret = check_dotdot(ctx, dirent, ino, &cd->pctx); + ret = check_dotdot(ctx, dirent, offset, ino, &cd->pctx); if (ret < 0) goto abort_free_dict; if (ret) @@ -1446,6 +1546,10 @@ skip_checksum: if (!dirent->inode) goto next; + ret = e2fsck_check_dirent_data(ctx, dirent, offset, &cd->pctx); + if (ret == 2) + dir_modified++; + /* * Make sure the inode listed is a legal one. */ diff --git a/e2fsck/problem.c b/e2fsck/problem.c index e433281fa..afa3d517a 100644 --- a/e2fsck/problem.c +++ b/e2fsck/problem.c @@ -1875,6 +1875,11 @@ static struct e2fsck_problem problem_table[] = { N_("@E references EA @i %Di.\n"), PROMPT_CLEAR, 0, 0, 0, 0 }, + /* Directory entry dirdata length set incorrectly */ + { PR_2_CLEAR_DIRDATA, + N_("@E dirdata length set incorrectly.\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + /* Pass 3 errors */ /* Pass 3: Checking directory connectivity */ diff --git a/e2fsck/problem.h b/e2fsck/problem.h index ef15b8c84..1ef519908 100644 --- a/e2fsck/problem.h +++ b/e2fsck/problem.h @@ -1070,6 +1070,9 @@ struct problem_context { /* EA inode referenced from directory */ #define PR_2_EA_INODE_DIR_LINK 0x020055 +/* Entry dirdata length set incorrectly */ +#define PR_2_CLEAR_DIRDATA 0x020057 + /* * Pass 3 errors */ -- 2.43.7