X-Git-Url: https://git.whamcloud.com/?a=blobdiff_plain;f=e2fsck%2Frehash.c;h=4847d172e5fe639bc5bf020cd5f5b83091ad8132;hb=18d47788c45c5361b93bcd0eb4c84c394a06d06a;hp=7dcb386fb4c2d9e785cc7b461b42dec36252cbc9;hpb=ae9efd05a9860b53691884f5fed40d37fc3d7edb;p=tools%2Fe2fsprogs.git diff --git a/e2fsck/rehash.c b/e2fsck/rehash.c index 7dcb386..4847d17 100644 --- a/e2fsck/rehash.c +++ b/e2fsck/rehash.c @@ -51,6 +51,7 @@ #include #include "e2fsck.h" #include "problem.h" +#include "support/sort_r.h" /* Schedule a dir to be rebuilt during pass 3A. */ void e2fsck_rehash_dir_later(e2fsck_t ctx, ext2_ino_t ino) @@ -80,27 +81,42 @@ struct fill_dir_struct { errcode_t err; e2fsck_t ctx; struct hash_entry *harray; - int max_array, num_array; - unsigned int dir_size; + blk_t max_array, num_array; + ext2_off64_t dir_size; int compress; - ino_t parent; + ext2_ino_t parent; ext2_ino_t dir; }; struct hash_entry { - ext2_dirhash_t hash; - ext2_dirhash_t minor_hash; - ino_t ino; + ext2_dirhash_t hash; + ext2_dirhash_t minor_hash; + ext2_ino_t ino; struct ext2_dir_entry *dir; }; struct out_dir { - int num; - int max; + blk_t num; + blk_t max; char *buf; 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, @@ -109,11 +125,11 @@ static int fill_dir_block(ext2_filsys fs, void *priv_data) { struct fill_dir_struct *fd = (struct fill_dir_struct *) priv_data; - struct hash_entry *new_array, *ent; + struct hash_entry *ent; struct ext2_dir_entry *dirent; char *dir; unsigned int offset, dir_offset, rec_len, name_len; - int hash_alg; + int hash_alg, hash_flags, hash_in_entry; if (blockcnt < 0) return 0; @@ -139,6 +155,8 @@ static int fill_dir_block(ext2_filsys fs, if (fd->err) 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)) @@ -146,19 +164,28 @@ static int fill_dir_block(ext2_filsys fs, /* While the directory block is "hot", index it. */ dir_offset = 0; while (dir_offset < fs->blocksize) { + unsigned 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; } dir_offset += rec_len; if (dirent->inode == 0) continue; + if ((name_len) == 0) { + fd->err = EXT2_ET_DIR_CORRUPTED; + return BLOCK_ABORT; + } if (!fd->compress && (name_len == 1) && (dirent->name[0] == '.')) continue; @@ -168,26 +195,33 @@ static int fill_dir_block(ext2_filsys fs, continue; } if (fd->num_array >= fd->max_array) { - new_array = realloc(fd->harray, - sizeof(struct hash_entry) * (fd->max_array+500)); - if (!new_array) { - fd->err = ENOMEM; + errcode_t retval; + + retval = ext2fs_resize_array(sizeof(struct hash_entry), + fd->max_array, + fd->max_array + 500, + &fd->harray); + if (retval) { + fd->err = retval; return BLOCK_ABORT; } - fd->harray = new_array; fd->max_array += 500; } 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 { - fd->err = ext2fs_dirhash(hash_alg, dirent->name, - name_len, - fs->super->s_hash_seed, - &ent->hash, &ent->minor_hash); + } else { + fd->err = ext2fs_dirhash2(hash_alg, + dirent->name, name_len, + fs->encoding, hash_flags, + fs->super->s_hash_seed, + &ent->hash, &ent->minor_hash); if (fd->err) return BLOCK_ABORT; } @@ -205,6 +239,23 @@ static EXT2_QSORT_TYPE ino_cmp(const void *a, const void *b) return (he_a->ino - he_b->ino); } +struct name_cmp_ctx +{ + int casefold; + const struct ext2fs_nls_table *tbl; +}; + +static int same_name(const struct name_cmp_ctx *cmp_ctx, char *s1, + int len1, char *s2, int len2) +{ + if (!cmp_ctx->casefold) + return (len1 == len2 && !memcmp(s1, s2, len1)); + else + return !ext2fs_casefold_cmp(cmp_ctx->tbl, + (unsigned char *) s1, len1, + (unsigned char *) s2, len2); +} + /* Used for sorting the hash entry */ static EXT2_QSORT_TYPE name_cmp(const void *a, const void *b) { @@ -219,7 +270,32 @@ static EXT2_QSORT_TYPE name_cmp(const void *a, const void *b) if (min_len > he_b_len) min_len = he_b_len; - ret = strncmp(he_a->dir->name, he_b->dir->name, min_len); + ret = memcmp(he_a->dir->name, he_b->dir->name, min_len); + if (ret == 0) { + if (he_a_len > he_b_len) + ret = 1; + else if (he_a_len < he_b_len) + ret = -1; + else + ret = he_b->dir->inode - he_a->dir->inode; + } + return ret; +} + +static EXT2_QSORT_TYPE name_cf_cmp(const struct name_cmp_ctx *ctx, + const void *a, const void *b) +{ + const struct hash_entry *he_a = (const struct hash_entry *) a; + const struct hash_entry *he_b = (const struct hash_entry *) b; + unsigned int he_a_len, he_b_len; + int ret; + + he_a_len = ext2fs_dirent_name_len(he_a->dir); + he_b_len = ext2fs_dirent_name_len(he_b->dir); + + ret = ext2fs_casefold_cmp(ctx->tbl, + (unsigned char *) he_a->dir->name, he_a_len, + (unsigned char *) he_b->dir->name, he_b_len); if (ret == 0) { if (he_a_len > he_b_len) ret = 1; @@ -232,8 +308,9 @@ static EXT2_QSORT_TYPE name_cmp(const void *a, const void *b) } /* Used for sorting the hash entry */ -static EXT2_QSORT_TYPE hash_cmp(const void *a, const void *b) +static EXT2_QSORT_TYPE hash_cmp(const void *a, const void *b, void *arg) { + const struct name_cmp_ctx *ctx = (struct name_cmp_ctx *) arg; const struct hash_entry *he_a = (const struct hash_entry *) a; const struct hash_entry *he_b = (const struct hash_entry *) b; int ret; @@ -247,30 +324,39 @@ static EXT2_QSORT_TYPE hash_cmp(const void *a, const void *b) ret = 1; else if (he_a->minor_hash < he_b->minor_hash) ret = -1; - else - ret = name_cmp(a, b); + else { + if (ctx->casefold) + ret = name_cf_cmp(ctx, a, b); + else + ret = name_cmp(a, b); + } } return ret; } static errcode_t alloc_size_dir(ext2_filsys fs, struct out_dir *outdir, - int blocks) + blk_t blocks) { - void *new_mem; + errcode_t retval; if (outdir->max) { - new_mem = realloc(outdir->buf, blocks * fs->blocksize); - if (!new_mem) - return ENOMEM; - outdir->buf = new_mem; - new_mem = realloc(outdir->hashes, - blocks * sizeof(ext2_dirhash_t)); - if (!new_mem) - return ENOMEM; - outdir->hashes = new_mem; + retval = ext2fs_resize_array(fs->blocksize, outdir->max, blocks, + &outdir->buf); + if (retval) + return retval; + retval = ext2fs_resize_array(sizeof(ext2_dirhash_t), + outdir->max, blocks, + &outdir->hashes); + if (retval) + return retval; } else { - outdir->buf = malloc(blocks * fs->blocksize); - outdir->hashes = malloc(blocks * sizeof(ext2_dirhash_t)); + retval = ext2fs_get_array(fs->blocksize, blocks, &outdir->buf); + if (retval) + return retval; + retval = ext2fs_get_array(sizeof(ext2_dirhash_t), blocks, + &outdir->hashes); + if (retval) + return retval; outdir->num = 0; } outdir->max = blocks; @@ -291,11 +377,15 @@ static errcode_t get_next_block(ext2_filsys fs, struct out_dir *outdir, errcode_t retval; if (outdir->num >= outdir->max) { - retval = alloc_size_dir(fs, outdir, outdir->max + 50); + int increment = outdir->max / 10; + + if (increment < 50) + increment = 50; + retval = alloc_size_dir(fs, outdir, outdir->max + increment); if (retval) return retval; } - *ret = outdir->buf + (outdir->num++ * fs->blocksize); + *ret = outdir->buf + (size_t)outdir->num++ * fs->blocksize; memset(*ret, 0, fs->blocksize); return 0; } @@ -324,6 +414,8 @@ static void mutate_name(char *str, unsigned int *len) l += 2; else l = (l+3) & ~3; + if (l > 255) + l = 255; str[l-2] = '~'; str[l-1] = '0'; *len = l; @@ -362,15 +454,17 @@ static void mutate_name(char *str, unsigned int *len) static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs, ext2_ino_t ino, - struct fill_dir_struct *fd) + struct fill_dir_struct *fd, + const struct name_cmp_ctx *cmp_ctx) { struct problem_context pctx; - struct hash_entry *ent, *prev; - int i, j; + struct hash_entry *ent, *prev; + blk_t i, j; int fixed = 0; char new_name[256]; unsigned int new_len; int hash_alg; + int hash_flags = fd->inode->i_flags & EXT4_CASEFOLD_FL; clear_problem_context(&pctx); pctx.ino = ino; @@ -384,10 +478,10 @@ static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs, ent = fd->harray + i; prev = ent - 1; if (!ent->dir->inode || - (ext2fs_dirent_name_len(ent->dir) != - ext2fs_dirent_name_len(prev->dir)) || - strncmp(ent->dir->name, prev->dir->name, - ext2fs_dirent_name_len(ent->dir))) + !same_name(cmp_ctx, ent->dir->name, + ext2fs_dirent_name_len(ent->dir), + prev->dir->name, + ext2fs_dirent_name_len(prev->dir))) continue; pctx.dirent = ent->dir; if ((ent->dir->inode == prev->dir->inode) && @@ -397,15 +491,30 @@ 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); + if (new_len == 0) { + /* should never happen */ + ext2fs_unmark_valid(fs); + continue; + } memcpy(new_name, ent->dir->name, new_len); mutate_name(new_name, &new_len); for (j=0; j < fd->num_array; j++) { if ((i==j) || - (new_len != - (unsigned) ext2fs_dirent_name_len(fd->harray[j].dir)) || - strncmp(new_name, fd->harray[j].dir->name, new_len)) + !same_name(cmp_ctx, new_name, new_len, + fd->harray[j].dir->name, + ext2fs_dirent_name_len(fd->harray[j].dir))) { continue; + } mutate_name(new_name, &new_len); j = -1; @@ -415,9 +524,10 @@ static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs, if (fix_problem(ctx, PR_2_NON_UNIQUE_FILE, &pctx)) { memcpy(ent->dir->name, new_name, new_len); ext2fs_dirent_set_name_len(ent->dir, new_len); - ext2fs_dirhash(hash_alg, new_name, new_len, - fs->super->s_hash_seed, - &ent->hash, &ent->minor_hash); + ext2fs_dirhash2(hash_alg, new_name, new_len, + fs->encoding, hash_flags, + fs->super->s_hash_seed, + &ent->hash, &ent->minor_hash); fixed++; } } @@ -435,10 +545,12 @@ static errcode_t copy_dir_entries(e2fsck_t ctx, struct hash_entry *ent; struct ext2_dir_entry *dirent; unsigned int rec_len, prev_rec_len, left, slack, offset; - int i; + blk_t i; 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); + unsigned int min_rec_len = ext2fs_dir_rec_len(1, hash_in_entry); if (ctx->htree_slack_percentage == 255) { profile_get_uint(ctx->profile, "options", @@ -467,15 +579,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; @@ -512,6 +625,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) { @@ -536,7 +654,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; @@ -564,7 +683,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; @@ -628,6 +750,9 @@ static int alloc_blocks(ext2_filsys fs, if (retval) return retval; + /* outdir->buf might be reallocated */ + *prev_ent = (struct ext2_dx_entry *) (outdir->buf + *prev_offset); + *next_ent = set_int_node(fs, block_start); *limit = (struct ext2_dx_countlimit *)(*next_ent); if (next_offset) @@ -647,17 +772,17 @@ 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; struct ext2_dx_countlimit *root_limit, *int_limit, *limit; errcode_t retval; - char * block_start; 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); @@ -718,6 +843,9 @@ static errcode_t calculate_tree(ext2_filsys fs, return retval; } if (c3 == 0) { + int delta1 = (char *)int_limit - outdir->buf; + int delta2 = (char *)root - outdir->buf; + retval = alloc_blocks(fs, &limit, &int_ent, &dx_ent, &int_offset, NULL, outdir, i, &c2, @@ -725,6 +853,11 @@ static errcode_t calculate_tree(ext2_filsys fs, if (retval) return retval; + /* outdir->buf might be reallocated */ + int_limit = (struct ext2_dx_countlimit *) + (outdir->buf + delta1); + root = (struct ext2_dx_entry *) + (outdir->buf + delta2); } dx_ent->block = ext2fs_cpu_to_le32(i); if (c3 != limit->limit) @@ -854,38 +987,48 @@ errcode_t e2fsck_rehash_dir(e2fsck_t ctx, ext2_ino_t ino, { ext2_filsys fs = ctx->fs; errcode_t retval; - struct ext2_inode inode; + struct ext2_inode_large inode; char *dir_buf = 0; struct fill_dir_struct fd = { NULL, NULL, 0, 0, 0, NULL, 0, 0, 0, 0, 0, 0 }; struct out_dir outdir = { 0, 0, 0, 0 }; + struct name_cmp_ctx name_cmp_ctx = {0, NULL}; + __u64 osize; + + e2fsck_read_inode_full(ctx, ino, EXT2_INODE(&inode), + sizeof(inode), "rehash_dir"); - e2fsck_read_inode(ctx, ino, &inode, "rehash_dir"); + osize = EXT2_I_SIZE(&inode); if (ext2fs_has_feature_inline_data(fs->super) && (inode.i_flags & EXT4_INLINE_DATA_FL)) return 0; - retval = ENOMEM; - dir_buf = malloc(inode.i_size); - if (!dir_buf) + retval = ext2fs_get_mem(inode.i_size, &dir_buf); + if (retval) goto errout; fd.max_array = inode.i_size / 32; - fd.harray = malloc(fd.max_array * sizeof(struct hash_entry)); - if (!fd.harray) + retval = ext2fs_get_array(sizeof(struct hash_entry), + fd.max_array, &fd.harray); + if (retval) goto errout; fd.ino = ino; fd.ctx = ctx; fd.buf = dir_buf; - fd.inode = &inode; + fd.inode = EXT2_INODE(&inode); fd.dir = ino; if (!ext2fs_has_feature_dir_index(fs->super) || (inode.i_size / fs->blocksize) < 2) fd.compress = 1; fd.parent = 0; + if (fs->encoding && (inode.i_flags & EXT4_CASEFOLD_FL)) { + name_cmp_ctx.casefold = 1; + name_cmp_ctx.tbl = fs->encoding; + } + retry_nohash: /* Read in the entire directory into memory */ retval = ext2fs_block_iterate3(fs, ino, 0, 0, @@ -914,16 +1057,16 @@ retry_nohash: /* Sort the list */ resort: if (fd.compress && fd.num_array > 1) - qsort(fd.harray+2, fd.num_array-2, sizeof(struct hash_entry), - hash_cmp); + sort_r(fd.harray+2, fd.num_array-2, sizeof(struct hash_entry), + hash_cmp, &name_cmp_ctx); else - qsort(fd.harray, fd.num_array, sizeof(struct hash_entry), - hash_cmp); + sort_r(fd.harray, fd.num_array, sizeof(struct hash_entry), + hash_cmp, &name_cmp_ctx); /* * Look for duplicates */ - if (duplicate_search_and_fix(ctx, fs, ino, &fd)) + if (duplicate_search_and_fix(ctx, fs, ino, &fd, &name_cmp_ctx)) goto resort; if (ctx->options & E2F_OPT_NO) { @@ -948,22 +1091,33 @@ 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; } - retval = write_directory(ctx, fs, &outdir, ino, &inode, fd.compress); + retval = write_directory(ctx, fs, &outdir, ino, EXT2_INODE(&inode), + fd.compress); if (retval) goto errout; + if ((osize > EXT2_I_SIZE(&inode)) && + (ino != quota_type2inum(PRJQUOTA, fs->super)) && + (ino != fs->super->s_orphan_file_inum) && + (ino == EXT2_ROOT_INO || ino >= EXT2_FIRST_INODE(ctx->fs->super)) && + !(inode.i_flags & EXT4_EA_INODE_FL)) { + quota_data_sub(ctx->qctx, &inode, + ino, osize - EXT2_I_SIZE(&inode)); + } + if (ctx->options & E2F_OPT_CONVERT_BMAP) retval = e2fsck_rebuild_extents_later(ctx, ino); else - retval = e2fsck_check_rebuild_extents(ctx, ino, &inode, pctx); + retval = e2fsck_check_rebuild_extents(ctx, ino, + EXT2_INODE(&inode), pctx); errout: - free(dir_buf); - free(fd.harray); + ext2fs_free_mem(&dir_buf); + ext2fs_free_mem(&fd.harray); free_out_dir(&outdir); return retval; @@ -1016,6 +1170,8 @@ void e2fsck_rehash_directories(e2fsck_t ctx) if (!ext2fs_u32_list_iterate(iter, &ino)) break; } + if (!ext2fs_test_inode_bitmap2(ctx->inode_dir_map, ino)) + continue; pctx.dir = ino; if (first) {