diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2020-06-25 01:01:50 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2020-06-25 01:01:50 +0000 |
commit | 4992ec87d697ea4ae6c4bdf69a266993a9837db1 (patch) | |
tree | a9efafcae02ba58640ef52784deeb8838c98c74f | |
parent | e7b5937033309fb350b78e894861a2fb59dbffac (diff) | |
parent | 656231487d8f0f30fb9cf1716cb994d22fbe6fb6 (diff) | |
download | e2fsprogs-android11-d1-s1-release.tar.gz |
Snap for 6626136 from 656231487d8f0f30fb9cf1716cb994d22fbe6fb6 to rvc-d1-releaseandroid-11.0.0_r9android-11.0.0_r8android-11.0.0_r7android-11.0.0_r15android-11.0.0_r14android-11.0.0_r13android-11.0.0_r12android-11.0.0_r11android-11.0.0_r10android11-d1-s7-releaseandroid11-d1-s6-releaseandroid11-d1-s5-releaseandroid11-d1-s1-releaseandroid11-d1-release
Change-Id: If3d1b7ea94978296321526d41f458c5598a22ddc
-rw-r--r-- | e2fsck/e2fsck.c | 4 | ||||
-rw-r--r-- | e2fsck/e2fsck.h | 1 | ||||
-rw-r--r-- | e2fsck/pass1.c | 32 | ||||
-rw-r--r-- | e2fsck/pass2.c | 45 | ||||
-rw-r--r-- | e2fsck/problem.c | 15 | ||||
-rw-r--r-- | e2fsck/problem.h | 12 | ||||
-rw-r--r-- | e2fsck/rehash.c | 76 | ||||
-rw-r--r-- | lib/ext2fs/ext2_fs.h | 43 | ||||
-rw-r--r-- | misc/mke2fs.c | 9 | ||||
-rw-r--r-- | misc/tune2fs.8.in | 32 | ||||
-rw-r--r-- | misc/tune2fs.c | 92 | ||||
-rw-r--r-- | tests/f_dup_de_crypt/expect.1 | 18 | ||||
-rw-r--r-- | tests/f_dup_de_crypt/expect.2 | 7 | ||||
-rw-r--r-- | tests/f_dup_de_crypt/image.gz | bin | 0 -> 3039 bytes | |||
-rw-r--r-- | tests/f_dup_de_crypt/name | 1 |
15 files changed, 335 insertions, 52 deletions
diff --git a/e2fsck/e2fsck.c b/e2fsck/e2fsck.c index 929bd78d..6bf68197 100644 --- a/e2fsck/e2fsck.c +++ b/e2fsck/e2fsck.c @@ -158,6 +158,10 @@ errcode_t e2fsck_reset_context(e2fsck_t ctx) ext2fs_u32_list_free(ctx->encrypted_dirs); ctx->encrypted_dirs = 0; } + if (ctx->casefolded_dirs) { + ext2fs_u32_list_free(ctx->casefolded_dirs); + ctx->casefolded_dirs = 0; + } if (ctx->inode_count) { ext2fs_free_icount(ctx->inode_count); ctx->inode_count = 0; diff --git a/e2fsck/e2fsck.h b/e2fsck/e2fsck.h index 2d359b38..18c08a16 100644 --- a/e2fsck/e2fsck.h +++ b/e2fsck/e2fsck.h @@ -390,6 +390,7 @@ struct e2fsck_struct { profile_t profile; int blocks_per_page; ext2_u32_list encrypted_dirs; + ext2_u32_list casefolded_dirs; /* Reserve blocks for root and l+f re-creation */ blk64_t root_repair_block, lnf_repair_block; diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index b1469088..8ea9d20d 100644 --- a/e2fsck/pass1.c +++ b/e2fsck/pass1.c @@ -79,6 +79,7 @@ static void alloc_bb_map(e2fsck_t ctx); static void alloc_imagic_map(e2fsck_t ctx); static void mark_inode_bad(e2fsck_t ctx, ino_t ino); static void add_encrypted_dir(e2fsck_t ctx, ino_t ino); +static void add_casefolded_dir(e2fsck_t ctx, ino_t ino); static void handle_fs_bad_blocks(e2fsck_t ctx); static void process_inodes(e2fsck_t ctx, char *block_buf); static EXT2_QSORT_TYPE process_inode_cmp(const void *a, const void *b); @@ -1889,6 +1890,8 @@ void e2fsck_pass1(e2fsck_t ctx) ctx->fs_directory_count++; if (inode->i_flags & EXT4_ENCRYPT_FL) add_encrypted_dir(ctx, ino); + if (inode->i_flags & EXT4_CASEFOLD_FL) + add_casefolded_dir(ctx, ino); } else if (LINUX_S_ISREG (inode->i_mode)) { ext2fs_mark_inode_bitmap2(ctx->inode_reg_map, ino); ctx->fs_regular_count++; @@ -2219,6 +2222,24 @@ error: ctx->flags |= E2F_FLAG_ABORT; } +static void add_casefolded_dir(e2fsck_t ctx, ino_t ino) +{ + struct problem_context pctx; + + if (!ctx->casefolded_dirs) { + pctx.errcode = ext2fs_u32_list_create(&ctx->casefolded_dirs, 0); + if (pctx.errcode) + goto error; + } + pctx.errcode = ext2fs_u32_list_add(ctx->casefolded_dirs, ino); + if (pctx.errcode == 0) + return; +error: + fix_problem(ctx, PR_1_ALLOCATE_CASEFOLDED_DIRLIST, &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; +} + /* * This procedure will allocate the inode "bb" (badblock) map table */ @@ -2676,9 +2697,20 @@ static int handle_htree(e2fsck_t ctx, struct problem_context *pctx, if ((root->hash_version != EXT2_HASH_LEGACY) && (root->hash_version != EXT2_HASH_HALF_MD4) && (root->hash_version != EXT2_HASH_TEA) && + (root->hash_version != EXT2_HASH_SIPHASH) && fix_problem(ctx, PR_1_HTREE_HASHV, pctx)) return 1; + if (ext4_hash_in_dirent(inode)) { + if (root->hash_version != EXT2_HASH_SIPHASH && + fix_problem(ctx, PR_1_HTREE_NEEDS_SIPHASH, pctx)) + return 1; + } else { + if (root->hash_version == EXT2_HASH_SIPHASH && + fix_problem(ctx, PR_1_HTREE_CANNOT_SIPHASH, pctx)) + return 1; + } + if ((root->unused_flags & EXT2_HASH_FLAG_INCOMPAT) && fix_problem(ctx, PR_1_HTREE_INCOMPAT, pctx)) return 1; diff --git a/e2fsck/pass2.c b/e2fsck/pass2.c index 8b40e93d..c45c73ef 100644 --- a/e2fsck/pass2.c +++ b/e2fsck/pass2.c @@ -288,6 +288,10 @@ void e2fsck_pass2(e2fsck_t ctx) ext2fs_u32_list_free(ctx->encrypted_dirs); ctx->encrypted_dirs = 0; } + if (ctx->casefolded_dirs) { + ext2fs_u32_list_free(ctx->casefolded_dirs); + ctx->casefolded_dirs = 0; + } clear_problem_context(&pctx); if (ctx->large_files) { @@ -705,7 +709,8 @@ static void salvage_directory(ext2_filsys fs, struct ext2_dir_entry *dirent, struct ext2_dir_entry *prev, unsigned int *offset, - unsigned int block_len) + unsigned int block_len, + int hash_in_dirent) { char *cp = (char *) dirent; int left; @@ -730,7 +735,8 @@ static void salvage_directory(ext2_filsys fs, * Special case of directory entry of size 8: copy what's left * of the directory block up to cover up the invalid hole. */ - if ((left >= 12) && (rec_len == EXT2_DIR_ENTRY_HEADER_LEN)) { + if ((left >= ext2fs_dir_rec_len(1, hash_in_dirent)) && + (rec_len == EXT2_DIR_ENTRY_HEADER_LEN)) { memmove(cp, cp+EXT2_DIR_ENTRY_HEADER_LEN, left); memset(cp + left, 0, EXT2_DIR_ENTRY_HEADER_LEN); return; @@ -742,7 +748,7 @@ static void salvage_directory(ext2_filsys fs, */ if ((left < 0) && ((int) rec_len + left > EXT2_DIR_ENTRY_HEADER_LEN) && - ((int) name_len + EXT2_DIR_ENTRY_HEADER_LEN <= (int) rec_len + left) && + ((int) ext2fs_dir_rec_len(name_len, hash_in_dirent) <= (int) rec_len + left) && dirent->inode <= fs->super->s_inodes_count && strnlen(dirent->name, name_len) == name_len) { (void) ext2fs_set_rec_len(fs, (int) rec_len + left, dirent); @@ -932,6 +938,8 @@ static int check_dir_block(ext2_filsys fs, size_t inline_data_size = 0; int filetype = 0; int encrypted = 0; + int hash_in_dirent = 0; + int casefolded = 0; size_t max_block_size; int hash_flags = 0; @@ -1152,6 +1160,9 @@ skip_checksum: if (ctx->encrypted_dirs) encrypted = ext2fs_u32_list_test(ctx->encrypted_dirs, ino); + if (ctx->casefolded_dirs) + casefolded = ext2fs_u32_list_test(ctx->casefolded_dirs, ino); + hash_in_dirent = encrypted && casefolded; dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp); prev = 0; @@ -1159,6 +1170,9 @@ skip_checksum: dgrp_t group; ext2_ino_t first_unused_inode; unsigned int name_len; + /* csum entry is not checked here, so don't worry about it */ + int extended = (dot_state > 1) && hash_in_dirent; + int min_dir_len = ext2fs_dir_rec_len(1, extended); problem = 0; if (!inline_data_size || dot_state > 1) { @@ -1168,15 +1182,16 @@ skip_checksum: * force salvaging this dir. */ if (max_block_size - offset < EXT2_DIR_ENTRY_HEADER_LEN) - rec_len = EXT2_DIR_REC_LEN(1); + rec_len = ext2fs_dir_rec_len(1, extended); else (void) ext2fs_get_rec_len(fs, dirent, &rec_len); cd->pctx.dirent = dirent; cd->pctx.num = offset; if ((offset + rec_len > max_block_size) || - (rec_len < 12) || + (rec_len < min_dir_len) || ((rec_len % 4) != 0) || - (((unsigned) ext2fs_dirent_name_len(dirent) + EXT2_DIR_ENTRY_HEADER_LEN) > rec_len)) { + ((ext2fs_dir_rec_len(ext2fs_dirent_name_len(dirent), + extended)) > rec_len)) { if (fix_problem(ctx, PR_2_DIR_CORRUPTED, &cd->pctx)) { #ifdef WORDS_BIGENDIAN @@ -1209,7 +1224,8 @@ skip_checksum: #endif salvage_directory(fs, dirent, prev, &offset, - max_block_size); + max_block_size, + hash_in_dirent); #ifdef WORDS_BIGENDIAN if (need_reswab) { (void) ext2fs_get_rec_len(fs, @@ -1431,10 +1447,17 @@ skip_checksum: if (dx_dir->casefolded_hash) hash_flags = EXT4_CASEFOLD_FL; - ext2fs_dirhash2(dx_dir->hashversion, dirent->name, - ext2fs_dirent_name_len(dirent), - fs->encoding, hash_flags, - fs->super->s_hash_seed, &hash, 0); + if (dx_dir->hashversion == EXT2_HASH_SIPHASH) { + if (dot_state > 1) + hash = EXT2_DIRENT_HASH(dirent); + } else { + ext2fs_dirhash2(dx_dir->hashversion, + dirent->name, + ext2fs_dirent_name_len(dirent), + fs->encoding, hash_flags, + fs->super->s_hash_seed, + &hash, 0); + } if (hash < dx_db->min_hash) dx_db->min_hash = hash; if (hash > dx_db->max_hash) diff --git a/e2fsck/problem.c b/e2fsck/problem.c index 5eb5973d..a3530f52 100644 --- a/e2fsck/problem.c +++ b/e2fsck/problem.c @@ -1253,6 +1253,16 @@ static struct e2fsck_problem problem_table[] = { N_("@d %p has the casefold flag, but the\ncasefold feature is not enabled. "), PROMPT_CLEAR_FLAG, 0, 0, 0, 0 }, + /* Htree directory should use SipHash but does not */ + { PR_1_HTREE_NEEDS_SIPHASH, + N_("@h %i uses hash version (%N), but should use SipHash (6) \n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK, 0, 0, 0 }, + + /* Htree directory uses SipHash but should not */ + { PR_1_HTREE_CANNOT_SIPHASH, + N_("@h %i uses SipHash, but should not. "), + PROMPT_CLEAR_HTREE, PR_PREEN_OK, 0, 0, 0 }, + /* Pass 1b errors */ /* Pass 1B: Rescan for duplicate/bad blocks */ @@ -1779,6 +1789,11 @@ static struct e2fsck_problem problem_table[] = { N_("Encrypted @E is too short.\n"), PROMPT_CLEAR, 0, 0, 0, 0 }, + /* Non-unique filename found, but can't rename */ + { PR_2_NON_UNIQUE_FILE_NO_RENAME, + N_("Duplicate filename @E found. "), + PROMPT_CLEAR, 0, 0, 0, 0 }, + /* Pass 3 errors */ /* Pass 3: Checking directory connectivity */ diff --git a/e2fsck/problem.h b/e2fsck/problem.h index 5cc89249..e67e4cac 100644 --- a/e2fsck/problem.h +++ b/e2fsck/problem.h @@ -701,6 +701,15 @@ struct problem_context { /* Casefold flag set, but file system is missing the casefold feature */ #define PR_1_CASEFOLD_FEATURE 0x010089 +/* Error allocating memory for casefolded directory list */ +#define PR_1_ALLOCATE_CASEFOLDED_DIRLIST 0x01008C + +/* Htree directory should use SipHash but does not */ +#define PR_1_HTREE_NEEDS_SIPHASH 0x01008D + +/* Htree directory uses SipHash but should not */ +#define PR_1_HTREE_CANNOT_SIPHASH 0x01008E + /* * Pass 1b errors @@ -1017,6 +1026,9 @@ struct problem_context { /* Encrypted directory entry is too short */ #define PR_2_BAD_ENCRYPTED_NAME 0x020050 +/* Non-unique filename found, but can't rename */ +#define PR_2_NON_UNIQUE_FILE_NO_RENAME 0x020053 + /* * Pass 3 errors */ diff --git a/e2fsck/rehash.c b/e2fsck/rehash.c index a5fc1be1..17ab1843 100644 --- a/e2fsck/rehash.c +++ b/e2fsck/rehash.c @@ -101,6 +101,21 @@ struct out_dir { ext2_dirhash_t *hashes; }; +#define DOTDOT_OFFSET 12 + +static int is_fake_entry(ext2_filsys fs, int lblk, unsigned int offset) +{ + /* Entries in the first block before this value refer to . or .. */ + if (lblk == 0 && offset <= DOTDOT_OFFSET) + return 1; + /* Check if this is likely the csum entry */ + if (ext2fs_has_feature_metadata_csum(fs->super) && + (offset & (fs->blocksize - 1)) == + fs->blocksize - sizeof(struct ext2_dir_entry_tail)) + return 1; + return 0; +} + static int fill_dir_block(ext2_filsys fs, blk64_t *block_nr, e2_blkcnt_t blockcnt, @@ -113,7 +128,7 @@ static int fill_dir_block(ext2_filsys fs, struct ext2_dir_entry *dirent; char *dir; unsigned int offset, dir_offset, rec_len, name_len; - int hash_alg, hash_flags; + int hash_alg, hash_flags, hash_in_entry; if (blockcnt < 0) return 0; @@ -140,6 +155,7 @@ static int fill_dir_block(ext2_filsys fs, return BLOCK_ABORT; } hash_flags = fd->inode->i_flags & EXT4_CASEFOLD_FL; + hash_in_entry = ext4_hash_in_dirent(fd->inode); hash_alg = fs->super->s_def_hash_version; if ((hash_alg <= EXT2_HASH_TEA) && (fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH)) @@ -147,13 +163,18 @@ static int fill_dir_block(ext2_filsys fs, /* While the directory block is "hot", index it. */ dir_offset = 0; while (dir_offset < fs->blocksize) { + int min_rec = EXT2_DIR_ENTRY_HEADER_LEN; + int extended = hash_in_entry && !is_fake_entry(fs, blockcnt, dir_offset); + + if (extended) + min_rec += EXT2_DIR_ENTRY_HASH_LEN; dirent = (struct ext2_dir_entry *) (dir + dir_offset); (void) ext2fs_get_rec_len(fs, dirent, &rec_len); name_len = ext2fs_dirent_name_len(dirent); if (((dir_offset + rec_len) > fs->blocksize) || - (rec_len < 8) || + (rec_len < min_rec) || ((rec_len % 4) != 0) || - (name_len + 8 > rec_len)) { + (name_len + min_rec > rec_len)) { fd->err = EXT2_ET_DIR_CORRUPTED; return BLOCK_ABORT; } @@ -180,11 +201,14 @@ static int fill_dir_block(ext2_filsys fs, } ent = fd->harray + fd->num_array++; ent->dir = dirent; - fd->dir_size += EXT2_DIR_REC_LEN(name_len); + fd->dir_size += ext2fs_dir_rec_len(name_len, extended); ent->ino = dirent->inode; - if (fd->compress) + if (extended) { + ent->hash = EXT2_DIRENT_HASH(dirent); + ent->minor_hash = EXT2_DIRENT_MINOR_HASH(dirent); + } else if (fd->compress) { ent->hash = ent->minor_hash = 0; - else { + } else { fd->err = ext2fs_dirhash2(hash_alg, dirent->name, name_len, fs->encoding, hash_flags, @@ -400,6 +424,15 @@ static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs, fixed++; continue; } + /* Can't alter encrypted name without key, so just drop it */ + if (fd->inode->i_flags & EXT4_ENCRYPT_FL) { + if (fix_problem(ctx, PR_2_NON_UNIQUE_FILE_NO_RENAME, &pctx)) { + e2fsck_adjust_inode_count(ctx, ent->dir->inode, -1); + ent->dir->inode = 0; + fixed++; + continue; + } + } new_len = ext2fs_dirent_name_len(ent->dir); memcpy(new_name, ent->dir->name, new_len); mutate_name(new_name, &new_len); @@ -443,6 +476,8 @@ static errcode_t copy_dir_entries(e2fsck_t ctx, ext2_dirhash_t prev_hash; int csum_size = 0; struct ext2_dir_entry_tail *t; + int hash_in_entry = ext4_hash_in_dirent(fd->inode); + int min_rec_len = ext2fs_dir_rec_len(1, hash_in_entry); if (ctx->htree_slack_percentage == 255) { profile_get_uint(ctx->profile, "options", @@ -471,15 +506,16 @@ static errcode_t copy_dir_entries(e2fsck_t ctx, prev_rec_len = 0; rec_len = 0; left = fs->blocksize - csum_size; - slack = fd->compress ? 12 : + slack = fd->compress ? min_rec_len : ((fs->blocksize - csum_size) * ctx->htree_slack_percentage)/100; - if (slack < 12) - slack = 12; + if (slack < min_rec_len) + slack = min_rec_len; for (i = 0; i < fd->num_array; i++) { ent = fd->harray + i; if (ent->dir->inode == 0) continue; - rec_len = EXT2_DIR_REC_LEN(ext2fs_dirent_name_len(ent->dir)); + rec_len = ext2fs_dir_rec_len(ext2fs_dirent_name_len(ent->dir), + hash_in_entry); if (rec_len > left) { if (left) { left += prev_rec_len; @@ -516,6 +552,11 @@ static errcode_t copy_dir_entries(e2fsck_t ctx, prev_rec_len = rec_len; memcpy(dirent->name, ent->dir->name, ext2fs_dirent_name_len(dirent)); + if (hash_in_entry) { + EXT2_DIRENT_HASHES(dirent)->hash = ext2fs_cpu_to_le32(ent->hash); + EXT2_DIRENT_HASHES(dirent)->minor_hash = + ext2fs_cpu_to_le32(ent->minor_hash); + } offset += rec_len; left -= rec_len; if (left < slack) { @@ -540,7 +581,8 @@ static errcode_t copy_dir_entries(e2fsck_t ctx, static struct ext2_dx_root_info *set_root_node(ext2_filsys fs, char *buf, - ext2_ino_t ino, ext2_ino_t parent) + ext2_ino_t ino, ext2_ino_t parent, + struct ext2_inode *inode) { struct ext2_dir_entry *dir; struct ext2_dx_root_info *root; @@ -568,7 +610,10 @@ static struct ext2_dx_root_info *set_root_node(ext2_filsys fs, char *buf, root = (struct ext2_dx_root_info *) (buf+24); root->reserved_zero = 0; - root->hash_version = fs->super->s_def_hash_version; + if (ext4_hash_in_dirent(inode)) + root->hash_version = EXT2_HASH_SIPHASH; + else + root->hash_version = fs->super->s_def_hash_version; root->info_length = 8; root->indirect_levels = 0; root->unused_flags = 0; @@ -651,7 +696,8 @@ static int alloc_blocks(ext2_filsys fs, static errcode_t calculate_tree(ext2_filsys fs, struct out_dir *outdir, ext2_ino_t ino, - ext2_ino_t parent) + ext2_ino_t parent, + struct ext2_inode *inode) { struct ext2_dx_root_info *root_info; struct ext2_dx_entry *root, *int_ent, *dx_ent = 0; @@ -660,7 +706,7 @@ static errcode_t calculate_tree(ext2_filsys fs, int i, c1, c2, c3, nblks; int limit_offset, int_offset, root_offset; - root_info = set_root_node(fs, outdir->buf, ino, parent); + root_info = set_root_node(fs, outdir->buf, ino, parent, inode); root_offset = limit_offset = ((char *) root_info - outdir->buf) + root_info->info_length; root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset); @@ -951,7 +997,7 @@ resort: if (!fd.compress) { /* Calculate the interior nodes */ - retval = calculate_tree(fs, &outdir, ino, fd.parent); + retval = calculate_tree(fs, &outdir, ino, fd.parent, fd.inode); if (retval) goto errout; } diff --git a/lib/ext2fs/ext2_fs.h b/lib/ext2fs/ext2_fs.h index c303ff44..857658d9 100644 --- a/lib/ext2fs/ext2_fs.h +++ b/lib/ext2fs/ext2_fs.h @@ -238,6 +238,7 @@ struct ext2_dx_root_info { #define EXT2_HASH_LEGACY_UNSIGNED 3 /* reserved for userspace lib */ #define EXT2_HASH_HALF_MD4_UNSIGNED 4 /* reserved for userspace lib */ #define EXT2_HASH_TEA_UNSIGNED 5 /* reserved for userspace lib */ +#define EXT2_HASH_SIPHASH 6 #define EXT2_HASH_FLAG_INCOMPAT 0x1 @@ -981,6 +982,12 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold, 4, CASEFOLD) #define EXT4_DEFM_DISCARD 0x0400 #define EXT4_DEFM_NODELALLOC 0x0800 +static inline int ext4_hash_in_dirent(const struct ext2_inode *inode) +{ + return (inode->i_flags & EXT4_ENCRYPT_FL) && + (inode->i_flags & EXT4_CASEFOLD_FL); +} + /* * Structure of a directory entry */ @@ -1016,6 +1023,25 @@ struct ext2_dir_entry_2 { }; /* + * Hashes for ext4_dir_entry for casefolded and ecrypted directories. + * This is located at the first 4 bit aligned location after the name. + */ + +struct ext2_dir_entry_hash { + __le32 hash; + __le32 minor_hash; +}; + +#define EXT2_DIRENT_HASHES(entry) \ + ((struct ext2_dir_entry_hash *) &entry->name[\ + (ext2fs_dirent_name_len(entry) + \ + EXT2_DIR_ROUND) & ~EXT2_DIR_ROUND]) +#define EXT2_DIRENT_HASH(entry) \ + ext2fs_le32_to_cpu(EXT2_DIRENT_HASHES(entry)->hash) +#define EXT2_DIRENT_MINOR_HASH(entry) \ + ext2fs_le32_to_cpu(EXT2_DIRENT_HASHES(entry)->minor_hash) + +/* * This is a bogus directory entry at the end of each leaf block that * records checksums. */ @@ -1055,12 +1081,21 @@ struct ext2_dir_entry_tail { * NOTE: It must be a multiple of 4 */ #define EXT2_DIR_ENTRY_HEADER_LEN 8 +#define EXT2_DIR_ENTRY_HASH_LEN 8 #define EXT2_DIR_PAD 4 #define EXT2_DIR_ROUND (EXT2_DIR_PAD - 1) -#define EXT2_DIR_REC_LEN(name_len) (((name_len) + \ - EXT2_DIR_ENTRY_HEADER_LEN + \ - EXT2_DIR_ROUND) & \ - ~EXT2_DIR_ROUND) +#define EXT2_DIR_REC_LEN(name_len) ext2fs_dir_rec_len(name_len, 0) + +static inline unsigned int ext2fs_dir_rec_len(__u8 name_len, + int extended) +{ + int rec_len = (name_len + EXT2_DIR_ENTRY_HEADER_LEN + EXT2_DIR_ROUND); + + rec_len &= ~EXT2_DIR_ROUND; + if (extended) + rec_len += EXT2_DIR_ENTRY_HASH_LEN; + return rec_len; +} /* * Constants for ext4's extended time encoding diff --git a/misc/mke2fs.c b/misc/mke2fs.c index da29ab39..879e3914 100644 --- a/misc/mke2fs.c +++ b/misc/mke2fs.c @@ -2460,15 +2460,6 @@ profile_error: } } - if (ext2fs_has_feature_casefold(&fs_param) && - ext2fs_has_feature_encrypt(&fs_param)) { - com_err(program_name, 0, "%s", - _("The encrypt and casefold features are not " - "compatible.\nThey can not be both enabled " - "simultaneously.\n")); - exit (1); - } - /* Don't allow user to set both metadata_csum and uninit_bg bits. */ if (ext2fs_has_feature_metadata_csum(&fs_param) && ext2fs_has_feature_gdt_csum(&fs_param)) diff --git a/misc/tune2fs.8.in b/misc/tune2fs.8.in index 74eebb6a..8b3604f4 100644 --- a/misc/tune2fs.8.in +++ b/misc/tune2fs.8.in @@ -218,6 +218,33 @@ directories. Valid algorithms accepted are: and .IR tea . .TP +.BI encoding= encoding-name +Enable the +.I casefold +feature in the super block and set +.I encoding-name +as the encoding to be used. If +.I encoding-name +is not specified, utf8 is used. The encoding cannot be altered if casefold +was previously enabled. +.TP +.BI encoding_flags= encoding-flags +Override the flags for filename encoding. This option may only be specified +when +.I encoding-name +is also specified and when the file system doesn't already have the +.I casefold +feature enabled. Currently the only recognized flag is +.I strict +which means that invalid filenames are rejected by the file system. By default, +.I strict +is not enabled. +Flags may be prefixed with "no" to explicitly leave them disabled, e.g. +.I nostrict +to explicitly leave the +.I strict +flag disabled. +.TP .BI mount_opts= mount_option_string Set a set of default mount options which will be used when the file system is mounted. Unlike the bitmask-based default mount options which @@ -536,6 +563,11 @@ The following filesystem features can be set or cleared using .B 64bit Enable the file system to be larger than 2^32 blocks. .TP +.B casefold +Enable support for file system level casefolding. +.B Tune2fs +currently only supports setting this filesystem feature. +.TP .B dir_index Use hashed b-trees to speed up lookups for large directories. .TP diff --git a/misc/tune2fs.c b/misc/tune2fs.c index c31c9a7c..b01497cb 100644 --- a/misc/tune2fs.c +++ b/misc/tune2fs.c @@ -101,6 +101,7 @@ static int rewrite_checksums; static int feature_64bit; static int fsck_requested; static char *undo_file; +static int enabling_casefold; int journal_size, journal_flags; char *journal_device; @@ -160,7 +161,8 @@ static __u32 ok_features[3] = { EXT4_FEATURE_INCOMPAT_64BIT | EXT4_FEATURE_INCOMPAT_ENCRYPT | EXT4_FEATURE_INCOMPAT_CSUM_SEED | - EXT4_FEATURE_INCOMPAT_LARGEDIR, + EXT4_FEATURE_INCOMPAT_LARGEDIR | + EXT4_FEATURE_INCOMPAT_CASEFOLD, /* R/O compat */ EXT2_FEATURE_RO_COMPAT_LARGE_FILE | EXT4_FEATURE_RO_COMPAT_HUGE_FILE| @@ -1403,18 +1405,23 @@ mmp_error: } if (FEATURE_ON(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_ENCRYPT)) { - if (ext2fs_has_feature_casefold(sb)) { - fputs(_("Cannot enable encrypt feature on filesystems " - "with the encoding feature enabled.\n"), - stderr); - return 1; - } fs->super->s_encrypt_algos[0] = EXT4_ENCRYPTION_MODE_AES_256_XTS; fs->super->s_encrypt_algos[1] = EXT4_ENCRYPTION_MODE_AES_256_CTS; } + if (FEATURE_ON(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_CASEFOLD)) { + if (mount_flags & EXT2_MF_MOUNTED) { + fputs(_("The casefold feature may only be enabled when " + "the filesystem is unmounted.\n"), stderr); + return 1; + } + fs->super->s_encoding = EXT4_ENC_UTF8_12_1; + fs->super->s_encoding_flags = e2p_get_encoding_flags(EXT4_ENC_UTF8_12_1); + enabling_casefold = 1; + } + if (FEATURE_ON(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_CSUM_SEED)) { if (!ext2fs_has_feature_metadata_csum(sb)) { @@ -2018,9 +2025,12 @@ void do_findfs(int argc, char **argv) static int parse_extended_opts(ext2_filsys fs, const char *opts) { + struct ext2_super_block *sb = fs->super; char *buf, *token, *next, *p, *arg; int len, hash_alg; int r_usage = 0; + int encoding = 0; + char *encoding_flags = NULL; len = strlen(opts); buf = malloc(len+1); @@ -2073,18 +2083,18 @@ static int parse_extended_opts(ext2_filsys fs, const char *opts) "Setting multiple mount protection update " "interval to %lu seconds\n", intv), intv); - fs->super->s_mmp_update_interval = intv; + sb->s_mmp_update_interval = intv; ext2fs_mark_super_dirty(fs); } else if (!strcmp(token, "force_fsck")) { - fs->super->s_state |= EXT2_ERROR_FS; + sb->s_state |= EXT2_ERROR_FS; printf(_("Setting filesystem error flag to force fsck.\n")); ext2fs_mark_super_dirty(fs); } else if (!strcmp(token, "test_fs")) { - fs->super->s_flags |= EXT2_FLAGS_TEST_FILESYS; + sb->s_flags |= EXT2_FLAGS_TEST_FILESYS; printf("Setting test filesystem flag\n"); ext2fs_mark_super_dirty(fs); } else if (!strcmp(token, "^test_fs")) { - fs->super->s_flags &= ~EXT2_FLAGS_TEST_FILESYS; + sb->s_flags &= ~EXT2_FLAGS_TEST_FILESYS; printf("Clearing test filesystem flag\n"); ext2fs_mark_super_dirty(fs); } else if (strcmp(token, "stride") == 0) { @@ -2130,7 +2140,7 @@ static int parse_extended_opts(ext2_filsys fs, const char *opts) r_usage++; continue; } - fs->super->s_def_hash_version = hash_alg; + sb->s_def_hash_version = hash_alg; printf(_("Setting default hash algorithm " "to %s (%d)\n"), arg, hash_alg); @@ -2146,9 +2156,63 @@ static int parse_extended_opts(ext2_filsys fs, const char *opts) continue; } ext_mount_opts = strdup(arg); + } else if (!strcmp(token, "encoding")) { + if (!arg) { + r_usage++; + continue; + } + if (mount_flags & EXT2_MF_MOUNTED) { + fputs(_("The casefold feature may only be enabled when " + "the filesystem is unmounted.\n"), stderr); + r_usage++; + continue; + } + if (ext2fs_has_feature_casefold(sb) && !enabling_casefold) { + fprintf(stderr, _("Cannot alter existing encoding\n")); + r_usage++; + continue; + } + encoding = e2p_str2encoding(arg); + if (encoding < 0) { + fprintf(stderr, _("Invalid encoding: %s\n"), arg); + r_usage++; + continue; + } + enabling_casefold = 1; + sb->s_encoding = encoding; + printf(_("Setting encoding to '%s'\n"), arg); + sb->s_encoding_flags = + e2p_get_encoding_flags(sb->s_encoding); + } else if (!strcmp(token, "encoding_flags")) { + if (!arg) { + r_usage++; + continue; + } + encoding_flags = arg; } else r_usage++; } + + if (encoding > 0 && !r_usage) { + sb->s_encoding_flags = + e2p_get_encoding_flags(sb->s_encoding); + + if (encoding_flags && + e2p_str2encoding_flags(sb->s_encoding, encoding_flags, + &sb->s_encoding_flags)) { + fprintf(stderr, _("error: Invalid encoding flag: %s\n"), + encoding_flags); + r_usage++; + } else if (encoding_flags) + printf(_("Setting encoding_flags to '%s'\n"), + encoding_flags); + ext2fs_set_feature_casefold(sb); + ext2fs_mark_super_dirty(fs); + } else if (encoding_flags && !r_usage) { + fprintf(stderr, _("error: An encoding must be explicitly " + "specified when passing encoding-flags\n")); + r_usage++; + } if (r_usage) { fprintf(stderr, "%s", _("\nBad options specified.\n\n" "Extended options are separated by commas, " @@ -2163,7 +2227,9 @@ static int parse_extended_opts(ext2_filsys fs, const char *opts) "\tstripe_width=<RAID stride*data disks in blocks>\n" "\tforce_fsck\n" "\ttest_fs\n" - "\t^test_fs\n")); + "\t^test_fs\n" + "\tencoding=<encoding>\n" + "\tencoding_flags=<flags>\n")); free(buf); return 1; } diff --git a/tests/f_dup_de_crypt/expect.1 b/tests/f_dup_de_crypt/expect.1 new file mode 100644 index 00000000..03e0ad6c --- /dev/null +++ b/tests/f_dup_de_crypt/expect.1 @@ -0,0 +1,18 @@ +Pass 1: Checking inodes, blocks, and sizes +Pass 2: Checking directory structure +Duplicate entry '+M-^AT^EM-1M-^CM-/)*M-L^RM-^L^@M-WM-)M-+' found. + Marking /test (12) to be rebuilt. + +Pass 3: Checking directory connectivity +Pass 3A: Optimizing directories +Duplicate filename entry '+M-^AT^EM-1M-^CM-/)*M-L^RM-^L^@M-WM-)M-+' in /test (12) found. Clear? yes + +Pass 4: Checking reference counts +Unattached inode 13 +Connect to /lost+found? yes + +Pass 5: Checking group summary information + +test_filesys: ***** FILE SYSTEM WAS MODIFIED ***** +test_filesys: 14/16 files (0.0% non-contiguous), 26/60 blocks +Exit status is 1 diff --git a/tests/f_dup_de_crypt/expect.2 b/tests/f_dup_de_crypt/expect.2 new file mode 100644 index 00000000..cfca772d --- /dev/null +++ b/tests/f_dup_de_crypt/expect.2 @@ -0,0 +1,7 @@ +Pass 1: Checking inodes, blocks, and sizes +Pass 2: Checking directory structure +Pass 3: Checking directory connectivity +Pass 4: Checking reference counts +Pass 5: Checking group summary information +test_filesys: 14/16 files (0.0% non-contiguous), 26/60 blocks +Exit status is 0 diff --git a/tests/f_dup_de_crypt/image.gz b/tests/f_dup_de_crypt/image.gz Binary files differnew file mode 100644 index 00000000..07a44d7c --- /dev/null +++ b/tests/f_dup_de_crypt/image.gz diff --git a/tests/f_dup_de_crypt/name b/tests/f_dup_de_crypt/name new file mode 100644 index 00000000..aff30a8f --- /dev/null +++ b/tests/f_dup_de_crypt/name @@ -0,0 +1 @@ +duplicate directory entries for encrypted dirs |