From c9f319493a619b52ae0be377bfa4bac0c6327150 Mon Sep 17 00:00:00 2001 From: Andreas Dilger Date: Thu, 12 Apr 2012 18:03:37 -0600 Subject: [PATCH] e2fsck: add support for expanding the inode size This patch adds a "-E expand_extra_isize" feature which makes sure that _every_ used inode has i_extra_isize >= s_min_extra_isize if s_min_extra_isize is set. Else it makes sure that i_extra_isize of every inode is equal to sizeof(ext2_inode_large) - 128. This is useful for the case where nanosecond timestamps or 64-bit inode version fields are required for all inodes in the filesystem. There is also a fix for test f_itable_collision, the original E2FSCK_TIME would overflow on a 32bit system when adding with ctx->time_fudge in EXT4_XTIME_FUTURE, making us increase the inode badness incorrectly. LU-10205 libext2fs: fix buffer overrun in ext2fs_expand_extra_isize In ext2fs_expand_extra_isize, we size buffer using 'size' but then do the memcpy with the rounded-up size, which can overflow the buffer. With MALLOC_CHECK_=2, I see: Error in `../e2fsck/e2fsck': free(): invalid pointer: Change-Id: I31be58de12d4d50646c7aa96959de0efc5c279c3 Signed-off-by: Jeff Mahoney Reviewed-on: https://review.whamcloud.com/29975 Reviewed-by: Andreas Dilger Tested-by: Jenkins Tested-by: Maloo Change-Id: I306ff4f81d8bd6bdf0446c76d6772951043fead4 Signed-off-by: Kalpak Shah Signed-off-by: Andreas Dilger --- e2fsck/e2fsck.c | 1 + e2fsck/e2fsck.h | 10 + e2fsck/emptydir.c | 4 +- e2fsck/pass1.c | 197 +++++++- e2fsck/pass5.c | 40 +- e2fsck/problem.c | 44 ++ e2fsck/problem.h | 31 ++ e2fsck/unix.c | 53 +++ lib/ext2fs/ext2_err.et.in | 21 + lib/ext2fs/ext2_ext_attr.h | 26 +- lib/ext2fs/ext2_fs.h | 9 +- lib/ext2fs/ext2fs.h | 21 + lib/ext2fs/ext_attr.c | 942 +++++++++++++++++++++++++++++++++++++- tests/f_itable_collision/expect.1 | 8 +- tests/f_itable_collision/script | 2 +- 15 files changed, 1389 insertions(+), 20 deletions(-) diff --git a/e2fsck/e2fsck.c b/e2fsck/e2fsck.c index db0a505..b79c600 100644 --- a/e2fsck/e2fsck.c +++ b/e2fsck/e2fsck.c @@ -198,6 +198,7 @@ errcode_t e2fsck_reset_context(e2fsck_t ctx) #ifdef HAVE_PTHREAD ctx->fs_need_locking = 0; #endif + ctx->fs_unexpanded_inodes = 0; for (i=0; i < MAX_EXTENT_DEPTH_COUNT; i++) ctx->extent_depth_count[i] = 0; diff --git a/e2fsck/e2fsck.h b/e2fsck/e2fsck.h index 2cdc9f5..2c903ec 100644 --- a/e2fsck/e2fsck.h +++ b/e2fsck/e2fsck.h @@ -211,6 +211,7 @@ struct resource_track { #define E2F_FLAG_TIME_INSANE 0x2000 /* Time is insane */ #define E2F_FLAG_PROBLEMS_FIXED 0x4000 /* At least one problem was fixed */ #define E2F_FLAG_ALLOC_OK 0x8000 /* Can we allocate blocks? */ +#define E2F_FLAG_EXPAND_EISIZE 0x10000 /* Expand the inodes (i_extra_isize) */ #define E2F_FLAG_DUP_BLOCK 0x20000 /* dup block found during pass1 */ #define E2F_RESET_FLAGS (E2F_FLAG_TIME_INSANE | E2F_FLAG_PROBLEMS_FIXED) @@ -482,6 +483,15 @@ struct e2fsck_struct { int blocks_per_page; ext2_u32_list casefolded_dirs; + /* Expand large inodes to atleast these many bytes */ + int want_extra_isize; + /* minimum i_extra_isize found in used inodes. Should not be lesser + * than s_min_extra_isize. + */ + __u32 min_extra_isize; + int fs_unexpanded_inodes; + ext2fs_inode_bitmap expand_eisize_map; + /* Reserve blocks for root and l+f re-creation */ blk64_t root_repair_block, lnf_repair_block; diff --git a/e2fsck/emptydir.c b/e2fsck/emptydir.c index 7aea7b6..d05ba98 100644 --- a/e2fsck/emptydir.c +++ b/e2fsck/emptydir.c @@ -98,9 +98,9 @@ void add_empty_dirblock(empty_dir_info edi, db->blk, db->blockcnt, db->ino); ext2fs_mark_block_bitmap2(edi->empty_dir_blocks, db->blk); - if (ext2fs_test_inode_bitmap(edi->dir_map, db->ino)) + if (ext2fs_test_inode_bitmap2(edi->dir_map, db->ino)) return; - ext2fs_mark_inode_bitmap(edi->dir_map, db->ino); + ext2fs_mark_inode_bitmap2(edi->dir_map, db->ino); ext2fs_add_dir_block2(edi->empty_dblist, db->ino, db->blk, db->blockcnt); diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index 8fd40bc..0b30e23 100644 --- a/e2fsck/pass1.c +++ b/e2fsck/pass1.c @@ -24,6 +24,7 @@ * - A bitmap of which inodes are in bad blocks. (inode_bb_map) * - A bitmap of which inodes are imagic inodes. (inode_imagic_map) * - A bitmap of which inodes are casefolded. (inode_casefold_map) + * - A bitmap of which inodes need to be expanded (expand_eisize_map) * - A bitmap of which blocks are in use. (block_found_map) * - A bitmap of which blocks are in use by two inodes (block_dup_map) * - The data blocks of the directory inodes. (dir_map) @@ -609,7 +610,7 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx, if (!fix_problem(ctx, PR_1_EXTRA_ISIZE, pctx)) return; if (inode->i_extra_isize < min || inode->i_extra_isize > max) - inode->i_extra_isize = sb->s_want_extra_isize; + inode->i_extra_isize = ctx->want_extra_isize; else inode->i_extra_isize = (inode->i_extra_isize + 3) & ~3; e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode, @@ -620,12 +621,24 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx, if (inode->i_extra_isize >= max - sizeof(__u32)) return; - eamagic = (__u32 *) (((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + - inode->i_extra_isize); - if (*eamagic == EXT2_EXT_ATTR_MAGIC) { - /* it seems inode has an extended attribute(s) in body */ + eamagic = IHDR(inode); + if (*eamagic != EXT2_EXT_ATTR_MAGIC && + (ctx->flags & E2F_FLAG_EXPAND_EISIZE) && + (inode->i_extra_isize < ctx->want_extra_isize)) { + fix_problem(ctx, PR_1_EXPAND_EISIZE, pctx); + memset((char *)inode + EXT2_GOOD_OLD_INODE_SIZE, 0, + EXT2_INODE_SIZE(sb) - EXT2_GOOD_OLD_INODE_SIZE); + inode->i_extra_isize = ctx->want_extra_isize; + e2fsck_write_inode_full(ctx, pctx->ino, + (struct ext2_inode *)inode, + EXT2_INODE_SIZE(sb), + "check_inode_extra_space"); + if (inode->i_extra_isize < ctx->min_extra_isize) + ctx->min_extra_isize = inode->i_extra_isize; + } + + if (*eamagic == EXT2_EXT_ATTR_MAGIC) check_ea_in_inode(ctx, pctx, ea_ibody_quota); - } /* * If the inode's extended atime (ctime, crtime, mtime) is stored in @@ -915,6 +928,151 @@ static errcode_t recheck_bad_inode_checksum(ext2_filsys fs, ext2_ino_t ino, return retval; } +int e2fsck_pass1_delete_attr(e2fsck_t ctx, struct ext2_inode_large *inode, + struct problem_context *pctx, int needed_size) +{ + struct ext2_ext_attr_header *header; + struct ext2_ext_attr_entry *entry_ino, *entry_blk = NULL, *entry; + char *start, name[4096], block_buf[4096]; + int len, index = EXT2_ATTR_INDEX_USER, entry_size, ea_size; + int in_inode = 1, error; + unsigned int freed_bytes = inode->i_extra_isize; + + start = (char *)inode + EXT2_GOOD_OLD_INODE_SIZE + + inode->i_extra_isize + sizeof(__u32); + entry_ino = (struct ext2_ext_attr_entry *)start; + + if (inode->i_file_acl) { + error = ext2fs_read_ext_attr(ctx->fs, inode->i_file_acl, + block_buf); + /* We have already checked this block, shouldn't happen */ + if (error) { + fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, pctx); + return 0; + } + header = BHDR(block_buf); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { + fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, pctx); + return 0; + } + + entry_blk = (struct ext2_ext_attr_entry *)(header+1); + } + entry = entry_ino; + len = sizeof(entry->e_name); + entry_size = ext2fs_attr_get_next_attr(entry, index, name, len, 1); + + while (freed_bytes < needed_size) { + if (entry_size && name[0] != '\0') { + pctx->str = name; + if (fix_problem(ctx, PR_1_EISIZE_DELETE_EA, pctx)) { + ea_size = EXT2_EXT_ATTR_LEN(entry->e_name_len) + + EXT2_EXT_ATTR_SIZE(entry->e_value_size); + error = ext2fs_attr_set(ctx->fs, pctx->ino, + (struct ext2_inode *)inode, + index, name, 0, 0, 0); + if (!error) + freed_bytes += ea_size; + } + } + len = sizeof(entry->e_name); + entry_size = ext2fs_attr_get_next_attr(entry, index,name,len,0); + entry = EXT2_EXT_ATTR_NEXT(entry); + if (EXT2_EXT_IS_LAST_ENTRY(entry)) { + if (in_inode) { + entry = entry_blk; + len = sizeof(entry->e_name); + entry_size = ext2fs_attr_get_next_attr(entry, + index, name, len, 1); + in_inode = 0; + } else { + index += 1; + in_inode = 1; + if (!entry && index < EXT2_ATTR_INDEX_MAX) + entry = (struct ext2_ext_attr_entry *)start; + else + return freed_bytes; + } + } + } + + return freed_bytes; +} + +int e2fsck_pass1_expand_eisize(e2fsck_t ctx, struct ext2_inode_large *inode, + struct problem_context *pctx) +{ + int needed_size = 0, retval, ret = EXT2_EXPAND_EISIZE_UNSAFE; + static int message; + +retry: + retval = ext2fs_expand_extra_isize(ctx->fs, pctx->ino, inode, + ctx->want_extra_isize, &ret, + &needed_size); + if (ret & EXT2_EXPAND_EISIZE_NEW_BLOCK) + goto mark_expand_eisize_map; + if (!retval) { + e2fsck_write_inode_full(ctx, pctx->ino, + (struct ext2_inode *)inode, + EXT2_INODE_SIZE(ctx->fs->super), + "pass1"); + return 0; + } + + if (ret & EXT2_EXPAND_EISIZE_NOSPC) { + if (ctx->options & (E2F_OPT_PREEN | E2F_OPT_YES)) { + fix_problem(ctx, PR_1_EA_BLK_NOSPC, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return -1; + } + + if (!message) { + pctx->num = ctx->fs->super->s_min_extra_isize; + fix_problem(ctx, PR_1_EXPAND_EISIZE_WARNING, pctx); + message = 1; + } +delete_EA: + retval = e2fsck_pass1_delete_attr(ctx, inode, pctx, + needed_size); + if (retval >= ctx->want_extra_isize) + goto retry; + + needed_size -= retval; + + /* + * We loop here until either the user deletes EA(s) or + * EXTRA_ISIZE feature is disabled. + */ + if (fix_problem(ctx, PR_1_CLEAR_EXTRA_ISIZE, pctx)) { + ctx->fs->super->s_feature_ro_compat &= + ~EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE; + ext2fs_mark_super_dirty(ctx->fs); + } else { + goto delete_EA; + } + ctx->fs_unexpanded_inodes++; + + /* No EA was deleted, inode cannot be expanded */ + return -1; + } + +mark_expand_eisize_map: + if (!ctx->expand_eisize_map) { + pctx->errcode = ext2fs_allocate_inode_bitmap(ctx->fs, + _("expand extrz isize map"), + &ctx->expand_eisize_map); + if (pctx->errcode) { + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, + pctx); + exit(1); + } + } + + /* Add this inode to the expand_eisize_map */ + ext2fs_mark_inode_bitmap2(ctx->expand_eisize_map, pctx->ino); + return 0; +} + static void reserve_block_for_root_repair(e2fsck_t ctx) { blk64_t blk = 0; @@ -1513,6 +1671,7 @@ void e2fsck_pass1_run(e2fsck_t ctx) struct process_inode_block *inodes_to_process; int process_inode_count, check_mmp; e2fsck_t global_ctx = ctx->global_ctx ? ctx->global_ctx : ctx; + int inode_exp = 0; init_resource_track(&rtrack, ctx->fs->io); clear_problem_context(&pctx); @@ -2379,6 +2538,22 @@ void e2fsck_pass1_run(e2fsck_t ctx) FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + if (ctx->flags & E2F_FLAG_EXPAND_EISIZE) { + struct ext2_inode_large *inode_l; + + inode_l = (struct ext2_inode_large *)inode; + + if (inode_l->i_extra_isize < ctx->want_extra_isize) { + fix_problem(ctx, PR_1_EXPAND_EISIZE, &pctx); + inode_exp = e2fsck_pass1_expand_eisize(ctx, + inode_l, + &pctx); + } + if ((inode_l->i_extra_isize < ctx->min_extra_isize) && + inode_exp == 0) + ctx->min_extra_isize = inode_l->i_extra_isize; + } + if (e2fsck_should_abort(ctx)) { e2fsck_pass1_check_unlock(ctx); goto endit; @@ -3787,11 +3962,17 @@ static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount, pctx.blk = blk; pctx.errcode = ext2fs_read_ext_attr3(fs, blk, block_buf, pctx.ino); + /* We already checked this block, shouldn't happen */ if (pctx.errcode) { fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx); return; } - header = (struct ext2_ext_attr_header *) block_buf; + header = BHDR(block_buf); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { + fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx); + return; + } + pctx.blkcount = header->h_refcount; should_be = header->h_refcount + adjust_sign * (int)count; pctx.num = should_be; @@ -3937,7 +4118,7 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, pctx->errcode = 0; goto clear_extattr; } - header = (struct ext2_ext_attr_header *) block_buf; + header = BHDR(block_buf); pctx->blk = ext2fs_file_acl_block(fs, inode); if (((ctx->ext_attr_ver == 1) && (header->h_magic != EXT2_EXT_ATTR_MAGIC_v1)) || diff --git a/e2fsck/pass5.c b/e2fsck/pass5.c index c1d45a5..23a4746 100644 --- a/e2fsck/pass5.c +++ b/e2fsck/pass5.c @@ -76,6 +76,42 @@ void e2fsck_pass5(e2fsck_t ctx) ext2fs_free_block_bitmap(ctx->block_metadata_map); ctx->block_metadata_map = 0; + if (ctx->flags & E2F_FLAG_EXPAND_EISIZE) { + int min_extra_isize; + + if (!ctx->expand_eisize_map) + goto set_min_extra_isize; + + for (pctx.ino = 1; pctx.ino < ctx->fs->super->s_inodes_count; + pctx.ino++) { + if (ext2fs_test_inode_bitmap2(ctx->expand_eisize_map, + pctx.ino)) { + fix_problem(ctx, PR_5_EXPAND_EISIZE, &pctx); + ext2fs_expand_extra_isize(ctx->fs, pctx.ino, 0, + ctx->want_extra_isize, + NULL, NULL); + } + } + ext2fs_free_inode_bitmap(ctx->expand_eisize_map); + +set_min_extra_isize: + if (ctx->fs->super->s_min_extra_isize) + min_extra_isize = ctx->fs->super->s_min_extra_isize; + else + min_extra_isize = ctx->want_extra_isize; + if (ctx->min_extra_isize >= min_extra_isize && + !ctx->fs_unexpanded_inodes) { + ctx->fs->super->s_min_extra_isize =ctx->min_extra_isize; + ctx->fs->super->s_feature_ro_compat |= + EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE; + } else { + ctx->fs->super->s_min_extra_isize = 0; + ctx->fs->super->s_feature_ro_compat &= + ~EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE; + } + ext2fs_mark_super_dirty(ctx->fs); + } + print_resource_track(ctx, _("Pass 5"), &rtrack, ctx->fs->io); } @@ -856,11 +892,11 @@ static void check_inode_end(e2fsck_t ctx) /* protect loop from wrap-around if end is maxed */ for (i = save_inodes_count + 1; i <= end && i > save_inodes_count; i++) { - if (!ext2fs_test_inode_bitmap(fs->inode_map, i)) { + if (!ext2fs_test_inode_bitmap2(fs->inode_map, i)) { asked = 1; if (fix_problem(ctx, PR_5_INODE_BMAP_PADDING, &pctx)) { for (; i <= end; i++) - ext2fs_mark_inode_bitmap(fs->inode_map, + ext2fs_mark_inode_bitmap2(fs->inode_map, i); ext2fs_mark_ib_dirty(fs); } else diff --git a/e2fsck/problem.c b/e2fsck/problem.c index d9d77af..095384a 100644 --- a/e2fsck/problem.c +++ b/e2fsck/problem.c @@ -546,6 +546,14 @@ static struct e2fsck_problem problem_table[] = { N_("Orphan file (@i %i) size is not multiple of block size. Terminating orphan file recovery.\n"), PROMPT_NONE, 0 }, + { PR_0_MIN_EXTRA_ISIZE_INVALID, + N_("@S has invalid s_min_extra_isize. "), + PROMPT_FIX, PR_PREEN_OK }, + + { PR_0_WANT_EXTRA_ISIZE_INVALID, + N_("@S has invalid s_want_extra_isize. "), + PROMPT_FIX, PR_PREEN_OK }, + /* Pass 1 errors */ /* Pass 1: Checking inodes, blocks, and sizes */ @@ -1309,6 +1317,37 @@ static struct e2fsck_problem problem_table[] = { N_("Orphan file @i %i is not in use, but contains data. "), PROMPT_CLEAR, PR_PREEN_OK }, + /* expand inode */ + { PR_1_EXPAND_EISIZE_WARNING, + N_("\ne2fsck is being run with \"expand_extra_isize\" option or\n" + "s_min_extra_isize of %d bytes has been set in the superblock.\n" + "Inode %i does not have enough free space. Either some EAs\n" + "need to be deleted from this inode or the RO_COMPAT_EXTRA_ISIZE\n" + "flag must be cleared.\n\n"), PROMPT_NONE, PR_PREEN_OK | PR_NO_OK | + PR_PREEN_NOMSG }, + + /* expand inode */ + { PR_1_EXPAND_EISIZE, + N_("Expanding @i %i.\n"), + PROMPT_NONE, PR_PREEN_OK | PR_NO_OK | PR_PREEN_NOMSG }, + + /* delete an EA so that EXTRA_ISIZE feature may be enabled */ + { PR_1_EISIZE_DELETE_EA, + N_("Delete EA %s of @i %i so that EXTRA_ISIZE feature may be " + "enabled?\n"), PROMPT_FIX, PR_NO_OK | PR_PREEN_NO }, + + /* an EA needs to be deleted by e2fsck is being run with -p or -y */ + { PR_1_EA_BLK_NOSPC, + N_("An EA needs to be deleted for @i %i but e2fsck is being run\n" + "with -p or -y mode.\n"), + PROMPT_ABORT, 0 }, + + /* disable EXTRA_ISIZE feature since inode cannot be expanded */ + { PR_1_CLEAR_EXTRA_ISIZE, + N_("Disable EXTRA_ISIZE feature since @i %i cannot be expanded\n" + "without deletion of an EA.\n"), + PROMPT_FIX, 0 }, + /* Failed to goto block group */ { PR_1_SCAN_GOTO, N_("failed to goto block group"), @@ -2272,6 +2311,11 @@ static struct e2fsck_problem problem_table[] = { N_("@g %g @b @B does not match checksum.\n"), PROMPT_FIX, PR_LATCH_BBITMAP | PR_PREEN_OK, 0, 0, 0 }, + /* Expand inode */ + { PR_5_EXPAND_EISIZE, + N_("Expanding @i %i.\n"), + PROMPT_NONE, PR_PREEN_OK | PR_NO_OK | PR_PREEN_NOMSG }, + /* Post-Pass 5 errors */ /* Recreate journal if E2F_FLAG_JOURNAL_INODE flag is set */ diff --git a/e2fsck/problem.h b/e2fsck/problem.h index 4c06a5e..9f86416 100644 --- a/e2fsck/problem.h +++ b/e2fsck/problem.h @@ -300,6 +300,15 @@ struct problem_context { /* Orphan file size isn't multiple of blocks size */ #define PR_0_ORPHAN_FILE_WRONG_SIZE 0x000055 +/* Invalid s_min_extra_isize */ +#define PR_0_MIN_EXTRA_ISIZE_INVALID 0x000056 + +/* Invalid s_want_extra_isize */ +#define PR_0_WANT_EXTRA_ISIZE_INVALID 0x000057 + +/* Clear EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE flag */ +#define PR_0_CLEAR_EXTRA_ISIZE 0x000058 + /* * Pass 1 errors */ @@ -734,6 +743,25 @@ struct problem_context { /* Orphan file inode is not in use, but contains data */ #define PR_1_ORPHAN_FILE_NOT_CLEAR 0x010090 +/* Warning for user that all inodes need to be expanded atleast by + * s_min_extra_isize + */ +#define PR_1_EXPAND_EISIZE_WARNING 0x010091 + +/* Expand the inode */ +#define PR_1_EXPAND_EISIZE 0x010092 + +/* Delete an EA so that EXTRA_ISIZE may be enabled */ +#define PR_1_EISIZE_DELETE_EA 0x010093 + +/* An EA needs to be deleted by e2fsck is being run with -p or -y */ +#define PR_1_EA_BLK_NOSPC 0x010094 + +/* Disable EXTRA_ISIZE feature as inode cannot be expanded + * without deletion of an EA + */ +#define PR_1_CLEAR_EXTRA_ISIZE 0x010095 + /* Failed to goto block group */ #define PR_1_SCAN_GOTO 0x0100A0 @@ -1301,6 +1329,9 @@ struct problem_context { /* Block bitmap checksum does not match */ #define PR_5_BLOCK_BITMAP_CSUM_INVALID 0x05001B +/* Expand the inodes which need a new EA block */ +#define PR_5_EXPAND_EISIZE 0x05001C + /* * Post-Pass 5 errors */ diff --git a/e2fsck/unix.c b/e2fsck/unix.c index 07ce949..0701733 100644 --- a/e2fsck/unix.c +++ b/e2fsck/unix.c @@ -786,6 +786,12 @@ static void parse_extended_opts(e2fsck_t ctx, const char *opts) extended_usage++; continue; } + } else if (strcmp(token, "expand_extra_isize") == 0) { + ctx->flags |= E2F_FLAG_EXPAND_EISIZE; + if (arg) { + extended_usage++; + continue; + } } else if (strcmp(token, "journal_only") == 0) { if (arg) { extended_usage++; @@ -860,6 +866,7 @@ static void parse_extended_opts(e2fsck_t ctx, const char *opts) fputs("\tnodiscard\n", stderr); fputs(("\tshared=\n"), stderr); fputs(("\tclone=\n"), stderr); + fputs(("\texpand_extra_isize\n"), stderr); fputs("\toptimize_extents\n", stderr); fputs("\tno_optimize_extents\n", stderr); fputs("\tinode_count_fullmap\n", stderr); @@ -1996,6 +2003,52 @@ print_unsupp_features: if (ctx->flags & E2F_FLAG_SIGNAL_MASK) fatal_error(ctx, 0); check_if_skip(ctx); + + if (EXT2_GOOD_OLD_INODE_SIZE + sb->s_want_extra_isize > + EXT2_INODE_SIZE(sb)) { + if (fix_problem(ctx, PR_0_WANT_EXTRA_ISIZE_INVALID, &pctx)) + sb->s_want_extra_isize = + sizeof(struct ext2_inode_large) - + EXT2_GOOD_OLD_INODE_SIZE; + } + if (EXT2_GOOD_OLD_INODE_SIZE + sb->s_min_extra_isize > + EXT2_INODE_SIZE(sb)) { + if (fix_problem(ctx, PR_0_MIN_EXTRA_ISIZE_INVALID, &pctx)) + sb->s_min_extra_isize = 0; + } + if (EXT2_INODE_SIZE(sb) > EXT2_GOOD_OLD_INODE_SIZE) { + ctx->want_extra_isize = sizeof(struct ext2_inode_large) - + EXT2_GOOD_OLD_INODE_SIZE; + ctx->min_extra_isize = ~0L; + if (EXT2_HAS_RO_COMPAT_FEATURE(sb, + EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE)) { + if (ctx->want_extra_isize < sb->s_want_extra_isize) + ctx->want_extra_isize = sb->s_want_extra_isize; + if (ctx->want_extra_isize < sb->s_min_extra_isize) + ctx->want_extra_isize = sb->s_min_extra_isize; + } + } else { + /* Leave extra_isize set, it is harmless, and clearing it + * here breaks some regression tests by printing an extra + * message to the output for very little value. */ + sb->s_want_extra_isize = 0; + sb->s_min_extra_isize = 0; + ctx->flags &= ~E2F_FLAG_EXPAND_EISIZE; + } + + if (ctx->options & E2F_OPT_READONLY) { + if (ctx->flags & (E2F_FLAG_EXPAND_EISIZE)) { + fprintf(stderr, _("Cannot enable EXTRA_ISIZE feature " + "on read-only filesystem\n")); + exit(1); + } + } else { + if (sb->s_want_extra_isize > sb->s_min_extra_isize && + (sb->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE)) + ctx->flags |= E2F_FLAG_EXPAND_EISIZE; + } + check_resize_inode(ctx); if (bad_blocks_file) read_bad_blocks_file(ctx, bad_blocks_file, replace_bad_blocks); diff --git a/lib/ext2fs/ext2_err.et.in b/lib/ext2fs/ext2_err.et.in index b8f45ae..bab5913 100644 --- a/lib/ext2fs/ext2_err.et.in +++ b/lib/ext2fs/ext2_err.et.in @@ -560,4 +560,25 @@ ec EXT2_ET_EXTERNAL_JOURNAL_NOSUPP, ec EXT2_ET_SCAN_FINISHED, "Scanning finished" +ec EXT2_ET_EA_BAD_MAGIC, + "Extended attribute block has bad magic value" + +ec EXT2_ET_EA_BAD_ENTRIES, + "Extended attribute block has bad entries" + +ec EXT2_ET_EA_TOO_BIG, + "Extended attribute too big for buffer" + +ec EXT2_ET_EA_NAME_TOO_BIG, + "Extended attribute name too big for header" + +ec EXT2_ET_EA_BAD_NAME, + "Extended attribute name is bad" + +ec EXT2_ET_EA_NAME_NOT_FOUND, + "Extended attribute name not found" + +ec EXT2_ET_EA_NAME_EXISTS, + "Extended attribute name already exists" + end diff --git a/lib/ext2fs/ext2_ext_attr.h b/lib/ext2fs/ext2_ext_attr.h index c6068c4..6d85b14 100644 --- a/lib/ext2fs/ext2_ext_attr.h +++ b/lib/ext2fs/ext2_ext_attr.h @@ -32,11 +32,35 @@ struct ext2_ext_attr_entry { __u32 e_value_inum; /* inode in which the value is stored */ __u32 e_value_size; /* size of attribute value */ __u32 e_hash; /* hash value of name and value */ -#if 0 +#if 1 char e_name[0]; /* attribute name */ #endif }; +#define BHDR(block) ((struct ext2_ext_attr_header *)block) +#define IHDR(inode) ((__u32 *)((char *)inode + EXT2_GOOD_OLD_INODE_SIZE + \ + (inode)->i_extra_isize)) +#define ENTRY(ptr) ((struct ext2_ext_attr_entry *)(ptr)) + +/* Name indexes */ +#define EXT2_ATTR_INDEX_USER 1 +#define EXT2_ATTR_INDEX_POSIX_ACL_ACCESS 2 +#define EXT2_ATTR_INDEX_POSIX_ACL_DEFAULT 3 +#define EXT2_ATTR_INDEX_TRUSTED 4 +#define EXT2_ATTR_INDEX_LUSTRE 5 +#define EXT2_ATTR_INDEX_SECURITY 6 +#define EXT2_ATTR_INDEX_MAX 7 + +#define EXT2_ATTR_INDEX_USER_PREFIX "user." +#define EXT2_ATTR_INDEX_POSIX_ACL_ACCESS_PREFIX "system.posix_acl_access" +#define EXT2_ATTR_INDEX_POSIX_ACL_DEFAULT_PREFIX "system.posix_acl_default" +#define EXT2_ATTR_INDEX_TRUSTED_PREFIX "trusted." +#define EXT2_ATTR_INDEX_LUSTRE_PREFIX "lustre." +#define EXT2_ATTR_INDEX_SECURITY_PREFIX "security." + +#define EXT2_ATTR_PREFIX(index) (index ## _PREFIX) +#define EXT2_ATTR_PREFIX_LEN(index) (index ## _PRE_LEN) + #define EXT2_EXT_ATTR_PAD_BITS 2 #define EXT2_EXT_ATTR_PAD ((unsigned) 1<= (sizeof(((struct ext2_inode_large *)0)->field) + \ offsetof(struct ext2_inode_large, field))) +#define EXT2_FITS_IN_INODE(inode, field) \ + ((offsetof(struct ext2_inode_large, field) + \ + sizeof((inode)->field)) <= \ + (EXT2_GOOD_OLD_INODE_SIZE + \ + (inode)->i_extra_isize)) \ + #if defined(__KERNEL__) || defined(__linux__) #define i_reserved1 osd1.linux1.l_i_reserved1 #define i_frag osd2.linux2.l_i_frag @@ -978,7 +984,8 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold, 4, CASEFOLD) EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \ EXT4_FEATURE_RO_COMPAT_DIR_NLINK| \ EXT2_FEATURE_RO_COMPAT_BTREE_DIR| \ - EXT4_FEATURE_RO_COMPAT_VERITY) + EXT4_FEATURE_RO_COMPAT_VERITY| \ + EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE) /* * Default values for user and/or group using reserved blocks diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h index 45045c4..e8a2c21 100644 --- a/lib/ext2fs/ext2fs.h +++ b/lib/ext2fs/ext2fs.h @@ -624,6 +624,13 @@ typedef struct ext2_icount *ext2_icount_t; if ((struct)->magic != (code)) return (code) /* + * Flags for returning status of ext2fs_expand_extra_isize() + */ +#define EXT2_EXPAND_EISIZE_UNSAFE 0x0001 +#define EXT2_EXPAND_EISIZE_NEW_BLOCK 0x0002 +#define EXT2_EXPAND_EISIZE_NOSPC 0x0004 + +/* * Features supported by this version of the library */ #define EXT2_LIB_FEATURE_COMPAT_SUPP (EXT2_FEATURE_COMPAT_DIR_PREALLOC|\ @@ -1271,6 +1278,10 @@ extern errcode_t ext2fs_dup_handle(ext2_filsys src, ext2_filsys *dest); extern errcode_t ext2fs_expand_dir(ext2_filsys fs, ext2_ino_t dir); /* ext_attr.c */ +extern errcode_t ext2fs_attr_get(ext2_filsys fs, struct ext2_inode *inode, + int name_index, const char *name, char *buffer, + size_t buffer_size, int *easize); + extern __u32 ext2fs_ext_attr_hash_entry(struct ext2_ext_attr_entry *entry, void *data); extern __u32 ext2fs_ext_attr_hash_entry_signed(struct ext2_ext_attr_entry *entry, @@ -1282,6 +1293,16 @@ extern errcode_t ext2fs_ext_attr_hash_entry3(ext2_filsys fs, struct ext2_ext_attr_entry *entry, void *data, __u32 *hash, __u32 *signed_hash); +int ext2fs_attr_get_next_attr(struct ext2_ext_attr_entry *entry, int name_index, + char *buffer, int buffer_size, int start); +errcode_t ext2fs_attr_set(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + int name_index, const char *name, const char *value, + int value_len, int flags); +extern errcode_t ext2fs_expand_extra_isize(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode_large *inode, + int new_extra_isize, int *ret, + int *needed_size); extern errcode_t ext2fs_read_ext_attr(ext2_filsys fs, blk_t block, void *buf); extern errcode_t ext2fs_read_ext_attr2(ext2_filsys fs, blk64_t block, void *buf); diff --git a/lib/ext2fs/ext_attr.c b/lib/ext2fs/ext_attr.c index 3494046..f7a03c7 100644 --- a/lib/ext2fs/ext_attr.c +++ b/lib/ext2fs/ext_attr.c @@ -18,6 +18,12 @@ #endif #include #include +#include +#if defined HAVE_SYS_XATTR_H +#include +#elif defined HAVE_ATTR_XATTR_H +#include +#endif #include "ext2_fs.h" #include "ext2_ext_attr.h" @@ -180,6 +186,33 @@ void ext2fs_ext_attr_block_rehash(struct ext2_ext_attr_header *header, header->h_hash = hash; } +/* + * Re-compute the extended attribute hash value after an entry has changed. + */ +static void ext2fs_attr_rehash(struct ext2_ext_attr_header *header, + struct ext2_ext_attr_entry *entry) +{ + struct ext2_ext_attr_entry *here; + __u32 hash = 0; + + entry->e_hash = ext2fs_ext_attr_hash_entry(entry, (char *)header + + entry->e_value_offs); + + here = ENTRY(header+1); + while (!EXT2_EXT_IS_LAST_ENTRY(here)) { + if (!here->e_hash) { + /* Block is not shared if an entry's hash value == 0 */ + hash = 0; + break; + } + hash = (hash << BLOCK_HASH_SHIFT) ^ + (hash >> (8*sizeof(hash) - BLOCK_HASH_SHIFT)) ^ + here->e_hash; + here = EXT2_EXT_ATTR_NEXT(here); + } + header->h_hash = hash; +} + #undef BLOCK_HASH_SHIFT __u32 ext2fs_get_ea_inode_hash(struct ext2_inode *inode) @@ -313,7 +346,10 @@ errcode_t ext2fs_adjust_ea_refcount3(ext2_filsys fs, blk64_t blk, if (retval) goto errout; - header = (struct ext2_ext_attr_header *) block_buf; + header = BHDR(block_buf); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) + return EXT2_ET_EA_BAD_MAGIC; + header->h_refcount += adjust; if (newcount) *newcount = header->h_refcount; @@ -1782,3 +1818,907 @@ errcode_t ext2fs_xattrs_flags(struct ext2_xattr_handle *handle, handle->flags = *new_flags; return 0; } + +struct ext2_attr_info { + int name_index; + const char *name; + const char *value; + int value_len; +}; + +struct ext2_attr_search { + struct ext2_ext_attr_entry *first; + char *base; + char *end; + struct ext2_ext_attr_entry *here; + int not_found; +}; + +struct ext2_attr_ibody_find { + ext2_ino_t ino; + struct ext2_attr_search s; +}; + +struct ext2_attr_block_find { + struct ext2_attr_search s; + char *block; +}; + +void ext2fs_attr_shift_entries(struct ext2_ext_attr_entry *entry, + int value_offs_shift, char *to, + char *from, int n) +{ + struct ext2_ext_attr_entry *last = entry; + + /* Adjust the value offsets of the entries */ + for (; !EXT2_EXT_IS_LAST_ENTRY(last); last = EXT2_EXT_ATTR_NEXT(last)) { + if (!last->e_value_inum && last->e_value_size) { + last->e_value_offs = last->e_value_offs + + value_offs_shift; + } + } + /* Shift the entries by n bytes and zero freed space in inode */ + memmove(to, from, n); + if (to > from) + memset(from, 0, to - from); +} + +/* + * This function returns the free space present in the inode or the EA block. + * total is number of bytes taken up by the EA entries and is used to shift + * the EAs in ext2fs_expand_extra_isize(). + */ +int ext2fs_attr_free_space(struct ext2_ext_attr_entry *last, + int *min_offs, char *base, int *total) +{ + for (; !EXT2_EXT_IS_LAST_ENTRY(last); last = EXT2_EXT_ATTR_NEXT(last)) { + *total += EXT2_EXT_ATTR_LEN(last->e_name_len); + if (!last->e_value_inum && last->e_value_size) { + int offs = last->e_value_offs; + if (offs < *min_offs) + *min_offs = offs; + } + } + + return *min_offs - ((char *)last - base) - sizeof(__u32); +} + +static errcode_t ext2fs_attr_check_names(struct ext2_ext_attr_entry *entry, + char *end) +{ + while (!EXT2_EXT_IS_LAST_ENTRY(entry)) { + struct ext2_ext_attr_entry *next = EXT2_EXT_ATTR_NEXT(entry); + if ((char *)next >= end) + return EXT2_ET_EA_BAD_ENTRIES; + entry = next; + } + return 0; +} + +/* The unused parameter used to be the blocksize, but with in-inode xattrs + * the xattr storage area size depends on where the xattrs are kept. Keep + * this parameter for API/ABI compatibility, but it is not needed. */ +static errcode_t ext2fs_attr_find_entry(struct ext2_ext_attr_entry **pentry, + int name_index, const char *name, + int unused, int sorted) +{ + struct ext2_ext_attr_entry *entry; + int name_len; + int cmp = 1; + + if (name == NULL) + return EXT2_ET_EA_BAD_NAME; + + name_len = strlen(name); + entry = *pentry; + for (; !EXT2_EXT_IS_LAST_ENTRY(entry); + entry = EXT2_EXT_ATTR_NEXT(entry)) { + cmp = name_index - entry->e_name_index; + if (!cmp) + cmp = name_len - entry->e_name_len; + if (!cmp) + cmp = memcmp(name, entry->e_name, name_len); + if (cmp <= 0 && (sorted || cmp == 0)) + break; + } + *pentry = entry; + + return cmp ? EXT2_ET_EA_NAME_NOT_FOUND : 0; +} + +static errcode_t ext2fs_attr_block_find(ext2_filsys fs,struct ext2_inode *inode, + struct ext2_attr_info *i, + struct ext2_attr_block_find *bs) +{ + struct ext2_ext_attr_header *header; + errcode_t error; + + if (inode->i_file_acl) { + /* The inode already has an extended attribute block. */ + error = ext2fs_get_mem(fs->blocksize, &bs->block); + if (error) + return error; + error = ext2fs_read_ext_attr(fs, inode->i_file_acl, bs->block); + if (error) + goto cleanup; + + header = BHDR(bs->block); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { + error = EXT2_ET_EA_BAD_MAGIC; + goto cleanup; + } + + /* Find the named attribute. */ + bs->s.base = bs->block; + bs->s.first = (struct ext2_ext_attr_entry *)(header + 1); + bs->s.end = bs->block + fs->blocksize; + bs->s.here = bs->s.first; + error = ext2fs_attr_find_entry(&bs->s.here, i->name_index, + i->name, fs->blocksize, 1); + if (error && error != EXT2_ET_EA_NAME_NOT_FOUND) + goto cleanup; + bs->s.not_found = error; + } + error = 0; + +cleanup: + if (error && bs->block) + ext2fs_free_mem(&bs->block); + return error; +} + +static errcode_t ext2fs_attr_ibody_find(ext2_filsys fs, + struct ext2_inode_large *inode, + struct ext2_attr_info *i, + struct ext2_attr_ibody_find *is) +{ + __u32 *eamagic; + char *start; + errcode_t error; + + if (EXT2_INODE_SIZE(fs->super) == EXT2_GOOD_OLD_INODE_SIZE) + return 0; + + if (inode->i_extra_isize == 0) + return 0; + eamagic = IHDR(inode); + + start = (char *)inode + EXT2_GOOD_OLD_INODE_SIZE + + inode->i_extra_isize + sizeof(__u32); + is->s.first = (struct ext2_ext_attr_entry *)start; + is->s.base = start; + is->s.here = is->s.first; + is->s.end = (char *)inode + EXT2_INODE_SIZE(fs->super); + if (*eamagic == EXT2_EXT_ATTR_MAGIC) { + error = ext2fs_attr_check_names((struct ext2_ext_attr_entry *) + start, is->s.end); + if (error) + return error; + /* Find the named attribute. */ + error = ext2fs_attr_find_entry(&is->s.here, i->name_index, + i->name, is->s.end - + (char *)is->s.base, 0); + if (error && error != EXT2_ET_EA_NAME_NOT_FOUND) + return error; + is->s.not_found = error; + } + + return 0; +} + +static errcode_t ext2fs_attr_set_entry(ext2_filsys fs, struct ext2_attr_info *i, + struct ext2_attr_search *s) +{ + struct ext2_ext_attr_entry *last; + int free, min_offs = s->end - s->base, name_len = strlen(i->name); + + /* Compute min_offs and last. */ + for (last = s->first; !EXT2_EXT_IS_LAST_ENTRY(last); + last = EXT2_EXT_ATTR_NEXT(last)) { + if (!last->e_value_inum && last->e_value_size) { + int offs = last->e_value_offs; + + if (offs < min_offs) + min_offs = offs; + } + } + free = min_offs - ((char *)last - s->base) - sizeof(__u32); + + if (!s->not_found) { + if (!s->here->e_value_inum && s->here->e_value_size) { + int size = s->here->e_value_size; + free += EXT2_EXT_ATTR_SIZE(size); + } + free += EXT2_EXT_ATTR_LEN(name_len); + } + if (i->value) { + if (free < EXT2_EXT_ATTR_LEN(name_len) + + EXT2_EXT_ATTR_SIZE(i->value_len)) + return EXT2_ET_EA_NO_SPACE; + } + + if (i->value && s->not_found) { + /* Insert the new name. */ + int size = EXT2_EXT_ATTR_LEN(name_len); + int rest = (char *)last - (char *)s->here + sizeof(__u32); + + memmove((char *)s->here + size, s->here, rest); + memset(s->here, 0, size); + s->here->e_name_index = i->name_index; + s->here->e_name_len = name_len; + memcpy(s->here->e_name, i->name, name_len); + } else { + if (!s->here->e_value_inum && s->here->e_value_size) { + char *first_val = s->base + min_offs; + int offs = s->here->e_value_offs; + char *val = s->base + offs; + int size = EXT2_EXT_ATTR_SIZE(s->here->e_value_size); + + if (i->value && + size == EXT2_EXT_ATTR_SIZE(i->value_len)) { + /* The old and the new value have the same + size. Just replace. */ + s->here->e_value_size = i->value_len; + memset(val + size - EXT2_EXT_ATTR_PAD, 0, + EXT2_EXT_ATTR_PAD); /* Clear pad bytes */ + memcpy(val, i->value, i->value_len); + return 0; + } + + /* Remove the old value. */ + memmove(first_val + size, first_val, val - first_val); + memset(first_val, 0, size); + s->here->e_value_size = 0; + s->here->e_value_offs = 0; + min_offs += size; + + /* Adjust all value offsets. */ + last = s->first; + while (!EXT2_EXT_IS_LAST_ENTRY(last)) { + int o = last->e_value_offs; + + if (!last->e_value_inum && + last->e_value_size && o < offs) + last->e_value_offs = o + size; + last = EXT2_EXT_ATTR_NEXT(last); + } + } + if (!i->value) { + /* Remove the old name. */ + int size = EXT2_EXT_ATTR_LEN(name_len); + + last = ENTRY((char *)last - size); + memmove((char *)s->here, (char *)s->here + size, + (char *)last - (char *)s->here + sizeof(__u32)); + memset(last, 0, size); + } + } + + if (i->value) { + /* Insert the new value. */ + s->here->e_value_size = i->value_len; + if (i->value_len) { + int size = EXT2_EXT_ATTR_SIZE(i->value_len); + char *val = s->base + min_offs - size; + + s->here->e_value_offs = min_offs - size; + memset(val + size - EXT2_EXT_ATTR_PAD, 0, + EXT2_EXT_ATTR_PAD); /* Clear the pad bytes. */ + memcpy(val, i->value, i->value_len); + } + } + + return 0; +} + +static errcode_t ext2fs_attr_block_set(ext2_filsys fs, struct ext2_inode *inode, + struct ext2_attr_info *i, + struct ext2_attr_block_find *bs) +{ + struct ext2_attr_search *s = &bs->s; + char *new_buf = NULL, *old_block = NULL; + blk_t blk; + int clear_flag = 0; + errcode_t error; + + if (i->value && i->value_len > fs->blocksize) + return EXT2_ET_EA_NO_SPACE; + + if (s->base) { + if (BHDR(s->base)->h_refcount != 1) { + int offset = (char *)s->here - bs->block; + + /* Decrement the refcount of the shared block */ + old_block = s->base; + BHDR(s->base)->h_refcount -= 1; + + error = ext2fs_get_mem(fs->blocksize, &s->base); + if (error) + goto cleanup; + clear_flag = 1; + memcpy(s->base, bs->block, fs->blocksize); + s->first = ENTRY(BHDR(s->base)+1); + BHDR(s->base)->h_refcount = 1; + s->here = ENTRY(s->base + offset); + s->end = s->base + fs->blocksize; + } + } else { + error = ext2fs_get_mem(fs->blocksize, &s->base); + if (error) + goto cleanup; + clear_flag = 1; + memset(s->base, 0, fs->blocksize); + BHDR(s->base)->h_magic = EXT2_EXT_ATTR_MAGIC; + BHDR(s->base)->h_blocks = 1; + BHDR(s->base)->h_refcount = 1; + s->first = ENTRY(BHDR(s->base)+1); + s->here = ENTRY(BHDR(s->base)+1); + s->end = s->base + fs->blocksize; + } + + error = ext2fs_attr_set_entry(fs, i, s); + if (error) + goto cleanup; + + if (!EXT2_EXT_IS_LAST_ENTRY(s->first)) + ext2fs_attr_rehash(BHDR(s->base), s->here); + + if (!EXT2_EXT_IS_LAST_ENTRY(s->first)) { + if (bs->block && bs->block == s->base) { + /* We are modifying this block in-place */ + new_buf = bs->block; + blk = inode->i_file_acl; + error = ext2fs_write_ext_attr(fs, blk, s->base); + if (error) + goto cleanup; + } else { + /* We need to allocate a new block */ + error = ext2fs_new_block(fs, 0, 0, &blk); + if (error) + goto cleanup; + ext2fs_block_alloc_stats(fs, blk, 1); + error = ext2fs_write_ext_attr(fs, blk, s->base); + if (error) + goto cleanup; + new_buf = s->base; + if (old_block) { + BHDR(s->base)->h_refcount -= 1; + error = ext2fs_write_ext_attr(fs, + inode->i_file_acl, + s->base); + if (error) + goto cleanup; + } + } + } + + /* Update the i_blocks if we added a new EA block */ + if (!inode->i_file_acl && new_buf) + inode->i_blocks += fs->blocksize / 512; + /* Update the inode. */ + inode->i_file_acl = new_buf ? blk : 0; + +cleanup: + if (clear_flag) + ext2fs_free_mem(&s->base); + return 0; +} + +static errcode_t ext2fs_attr_ibody_set(ext2_filsys fs, + struct ext2_inode_large *inode, + struct ext2_attr_info *i, + struct ext2_attr_ibody_find *is) +{ + __u32 *eamagic; + struct ext2_attr_search *s = &is->s; + errcode_t error; + + if (EXT2_INODE_SIZE(fs->super) == EXT2_GOOD_OLD_INODE_SIZE) + return EXT2_ET_EA_NO_SPACE; + + error = ext2fs_attr_set_entry(fs, i, s); + if (error) + return error; + + eamagic = IHDR(inode); + if (!EXT2_EXT_IS_LAST_ENTRY(s->first)) + *eamagic = EXT2_EXT_ATTR_MAGIC; + else + *eamagic = 0; + + return ext2fs_write_inode_full(fs, is->ino, (struct ext2_inode *)inode, + EXT2_INODE_SIZE(fs->super)); +} + +static struct { + char str[28]; + int len; +} ext2_attr_index_prefix[] = { + [EXT2_ATTR_INDEX_USER] = { EXT2_ATTR_INDEX_USER_PREFIX, + sizeof(EXT2_ATTR_INDEX_USER_PREFIX) }, + [EXT2_ATTR_INDEX_POSIX_ACL_ACCESS] = { + EXT2_ATTR_INDEX_POSIX_ACL_ACCESS_PREFIX, + sizeof(EXT2_ATTR_INDEX_POSIX_ACL_ACCESS_PREFIX) }, + [EXT2_ATTR_INDEX_POSIX_ACL_DEFAULT] = { + EXT2_ATTR_INDEX_POSIX_ACL_DEFAULT_PREFIX, + sizeof(EXT2_ATTR_INDEX_POSIX_ACL_DEFAULT_PREFIX) }, + [EXT2_ATTR_INDEX_TRUSTED] = { EXT2_ATTR_INDEX_TRUSTED_PREFIX, + sizeof(EXT2_ATTR_INDEX_TRUSTED_PREFIX) }, + [EXT2_ATTR_INDEX_LUSTRE] = { EXT2_ATTR_INDEX_LUSTRE_PREFIX, + sizeof(EXT2_ATTR_INDEX_LUSTRE_PREFIX) }, + [EXT2_ATTR_INDEX_SECURITY] = { EXT2_ATTR_INDEX_SECURITY_PREFIX, + sizeof(EXT2_ATTR_INDEX_SECURITY_PREFIX)}, + { "", 0 } +}; + +errcode_t ext2fs_attr_set(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + int name_index, const char *name, const char *value, + int value_len, int flags) +{ + struct ext2_inode_large *inode_large = NULL; + struct ext2_attr_info i = { + .name_index = name_index, + .name = name, + .value = value, + .value_len = value_len, + }; + struct ext2_attr_ibody_find is = { + .ino = ino, + .s = { .not_found = -ENODATA, }, + }; + struct ext2_attr_block_find bs = { + .s = { .not_found = -ENODATA, }, + }; + errcode_t error; + + if (!name) + return EXT2_ET_EA_BAD_NAME; + if (strlen(name) > 255) + return EXT2_ET_EA_NAME_TOO_BIG; + + /* If the prefix is still present, skip it */ + if (strncmp(name, ext2_attr_index_prefix[name_index].str, + ext2_attr_index_prefix[name_index].len) == 0) + i.name += ext2_attr_index_prefix[name_index].len; + + if (EXT2_INODE_SIZE(fs->super) > EXT2_GOOD_OLD_INODE_SIZE) { + inode_large = (struct ext2_inode_large *)inode; + + error = ext2fs_attr_ibody_find(fs, inode_large, &i, &is); + if (error) + goto cleanup; + } + if (is.s.not_found) { + error = ext2fs_attr_block_find(fs, inode, &i, &bs); + if (error) + goto cleanup; + } + + if (is.s.not_found && bs.s.not_found) { + error = EXT2_ET_EA_NAME_NOT_FOUND; + if (flags & XATTR_REPLACE) + goto cleanup; + error = 0; + if (!value) + goto cleanup; + } else { + error = EXT2_ET_EA_NAME_EXISTS; + if (flags & XATTR_CREATE) + goto cleanup; + } + + if (!value) { + if (!is.s.not_found && + (EXT2_INODE_SIZE(fs->super) > EXT2_GOOD_OLD_INODE_SIZE)) + error = ext2fs_attr_ibody_set(fs, inode_large, &i, &is); + else if (!bs.s.not_found) + error = ext2fs_attr_block_set(fs, inode, &i, &bs); + } else { + if (EXT2_INODE_SIZE(fs->super) > EXT2_GOOD_OLD_INODE_SIZE) + error = ext2fs_attr_ibody_set(fs, inode_large, &i, &is); + if (!error && !bs.s.not_found) { + i.value = NULL; + error = ext2fs_attr_block_set(fs, inode, &i, &bs); + } else if (error == EXT2_ET_EA_NO_SPACE) { + error = ext2fs_attr_block_set(fs, inode, &i, &bs); + if (error) + goto cleanup; + if (!is.s.not_found) { + i.value = NULL; + if (EXT2_INODE_SIZE(fs->super) > + EXT2_GOOD_OLD_INODE_SIZE) + error = ext2fs_attr_ibody_set(fs, + inode_large, &i, &is); + } + } + } + +cleanup: + return error; +} + +static errcode_t ext2fs_attr_check_block(ext2_filsys fs, char *buffer) +{ + if (BHDR(buffer)->h_magic != (EXT2_EXT_ATTR_MAGIC) || + BHDR(buffer)->h_blocks != 1) + return EXT2_ET_EA_BAD_MAGIC; + + return ext2fs_attr_check_names((struct ext2_ext_attr_entry *) + (BHDR(buffer) + 1), + buffer + fs->blocksize); +} + +static errcode_t ext2fs_attr_block_get(ext2_filsys fs, struct ext2_inode *inode, + int name_index, const char *name, + void *buffer, size_t buffer_size, + int *easize) +{ + struct ext2_ext_attr_header *header = NULL; + struct ext2_ext_attr_entry *entry; + char *block_buf = NULL; + errcode_t error; + + error = EXT2_ET_EA_NAME_NOT_FOUND; + if (!inode->i_file_acl) + goto cleanup; + + error = ext2fs_get_mem(fs->blocksize, &block_buf); + if (error) + return error; + error = ext2fs_read_ext_attr(fs, inode->i_file_acl, block_buf); + if (error) + goto cleanup; + + error = ext2fs_attr_check_block(fs, block_buf); + if (error) + goto cleanup; + + header = BHDR(block_buf); + entry = (struct ext2_ext_attr_entry *)(header + 1); + error = ext2fs_attr_find_entry(&entry, name_index, name, + fs->blocksize, 1); + if (error) + goto cleanup; + if (easize) + *easize = entry->e_value_size; + if (buffer) { + if (entry->e_value_size > buffer_size) { + error = EXT2_ET_EA_TOO_BIG; + goto cleanup; + } + memcpy(buffer, block_buf + entry->e_value_offs, + entry->e_value_size); + } + +cleanup: + if (block_buf) + ext2fs_free_mem(&block_buf); + return error; +} + +static errcode_t ext2fs_attr_ibody_get(ext2_filsys fs, + struct ext2_inode_large *inode, + int name_index, const char *name, + void *buffer, size_t buffer_size, + int *easize) +{ + struct ext2_ext_attr_entry *entry; + int error; + char *end, *start; + __u32 *eamagic; + + if (EXT2_INODE_SIZE(fs->super) == EXT2_GOOD_OLD_INODE_SIZE) + return EXT2_ET_EA_NAME_NOT_FOUND; + + eamagic = IHDR(inode); + error = ext2fs_attr_check_block(fs, buffer); + if (error) + return error; + + start = (char *)inode + EXT2_GOOD_OLD_INODE_SIZE + + inode->i_extra_isize + sizeof(__u32); + entry = (struct ext2_ext_attr_entry *)start; + end = (char *)inode + EXT2_INODE_SIZE(fs->super); + error = ext2fs_attr_check_names(entry, end); + if (error) + goto cleanup; + error = ext2fs_attr_find_entry(&entry, name_index, name, + end - (char *)entry, 0); + if (error) + goto cleanup; + if (easize) + *easize = entry->e_value_size; + if (buffer) { + if (entry->e_value_size > buffer_size) { + error = EXT2_ET_EA_TOO_BIG; + goto cleanup; + } + memcpy(buffer, start + entry->e_value_offs,entry->e_value_size); + } + +cleanup: + return error; +} + + +errcode_t ext2fs_attr_get(ext2_filsys fs, struct ext2_inode *inode, + int name_index, const char *name, char *buffer, + size_t buffer_size, int *easize) +{ + errcode_t error; + + error = ext2fs_attr_ibody_get(fs, (struct ext2_inode_large *)inode, + name_index, name, buffer, buffer_size, + easize); + if (error == EXT2_ET_EA_NAME_NOT_FOUND || error == EXT2_ET_EA_BAD_MAGIC) + error = ext2fs_attr_block_get(fs, inode, name_index, name, + buffer, buffer_size, easize); + + return error; +} + +int ext2fs_attr_get_next_attr(struct ext2_ext_attr_entry *entry, int name_index, + char *buffer, int buffer_size, int start) +{ + const int prefix_len = ext2_attr_index_prefix[name_index].len; + int total_len; + + if (!start && !EXT2_EXT_IS_LAST_ENTRY(entry)) + entry = EXT2_EXT_ATTR_NEXT(entry); + + for (; !EXT2_EXT_IS_LAST_ENTRY(entry); + entry = EXT2_EXT_ATTR_NEXT(entry)) { + if (!name_index) + break; + if (name_index == entry->e_name_index) + break; + } + if (EXT2_EXT_IS_LAST_ENTRY(entry)) + return 0; + + total_len = prefix_len + entry->e_name_len + 1; + if (buffer && total_len <= buffer_size) { + memcpy(buffer, ext2_attr_index_prefix[name_index].str, + prefix_len); + memcpy(buffer + prefix_len, entry->e_name, entry->e_name_len); + buffer[prefix_len + entry->e_name_len] = '\0'; + } + + return total_len; +} + +errcode_t ext2fs_expand_extra_isize(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode_large *inode, + int new_extra_isize, int *ret, + int *needed_size) +{ + struct ext2_inode *inode_buf = NULL; + __u32 *eamagic = NULL; + struct ext2_ext_attr_header *header = NULL; + struct ext2_ext_attr_entry *entry = NULL, *last = NULL; + struct ext2_attr_ibody_find is = { + .ino = ino, + .s = { .not_found = EXT2_ET_EA_NO_SPACE, }, + }; + struct ext2_attr_block_find bs = { + .s = { .not_found = EXT2_ET_EA_NO_SPACE, }, + }; + char *start, *end, *block_buf = NULL, *buffer =NULL, *b_entry_name=NULL; + int total_ino = 0, total_blk, free, offs, tried_min_extra_isize = 0; + int s_min_extra_isize = fs->super->s_min_extra_isize; + errcode_t error = 0; + + if (needed_size) + *needed_size = new_extra_isize; + error = ext2fs_get_mem(fs->blocksize, &block_buf); + if (error) + return error; + + if (inode == NULL) { + error = ext2fs_get_mem(EXT2_INODE_SIZE(fs->super), &inode_buf); + if (error) + goto cleanup; + + error = ext2fs_read_inode_full(fs, ino, inode_buf, + EXT2_INODE_SIZE(fs->super)); + if (error) + goto cleanup; + + inode = (struct ext2_inode_large *)inode_buf; + } + +retry: + if (inode->i_extra_isize >= new_extra_isize) + goto cleanup; + + eamagic = IHDR(inode); + start = (char *)inode + EXT2_GOOD_OLD_INODE_SIZE + inode->i_extra_isize; + /* No extended attributes present */ + if (*eamagic != EXT2_EXT_ATTR_MAGIC) { + memset(start, 0, + EXT2_INODE_SIZE(fs->super) - EXT2_GOOD_OLD_INODE_SIZE - + inode->i_extra_isize); + inode->i_extra_isize = new_extra_isize; + if (needed_size) + *needed_size = 0; + goto write_inode; + } + + start += sizeof(__u32); + end = (char *)inode + EXT2_INODE_SIZE(fs->super); + last = entry = (struct ext2_ext_attr_entry *)start; + offs = end - start; + /* Consider space takenup by magic number */ + total_ino = sizeof(__u32); + free = ext2fs_attr_free_space(last, &offs, start, &total_ino); + + /* Enough free space available in the inode for expansion */ + if (free >= new_extra_isize) { + ext2fs_attr_shift_entries(entry, + inode->i_extra_isize - new_extra_isize, + (char *)inode + + EXT2_GOOD_OLD_INODE_SIZE + + new_extra_isize, + start - sizeof(__u32), total_ino); + inode->i_extra_isize = new_extra_isize; + if (needed_size) + *needed_size = 0; + goto write_inode; + } + + if (inode->i_file_acl) { + error = ext2fs_read_ext_attr(fs, inode->i_file_acl, block_buf); + if (error) + goto cleanup; + + header = BHDR(block_buf); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { + error = EXT2_ET_EA_BAD_MAGIC; + goto cleanup; + } + end = block_buf + fs->blocksize; + last = entry = (struct ext2_ext_attr_entry *)(header+1); + start = (char *)entry; + offs = end - start; + free = ext2fs_attr_free_space(last, &offs, start, &total_blk); + if (free < new_extra_isize) { + if (!tried_min_extra_isize && s_min_extra_isize) { + tried_min_extra_isize++; + new_extra_isize = s_min_extra_isize; + goto retry; + } + if (ret) + *ret = EXT2_EXPAND_EISIZE_NOSPC; + error = EXT2_ET_EA_NO_SPACE; + goto cleanup; + } + } else { + if (ret && *ret == EXT2_EXPAND_EISIZE_UNSAFE) { + *ret = EXT2_EXPAND_EISIZE_NEW_BLOCK; + error = 0; + goto cleanup; + } + free = fs->blocksize; + } + + while (new_extra_isize > 0) { + int offs, size, entry_size; + struct ext2_ext_attr_entry *small_entry = NULL; + struct ext2_attr_info i = { + .value = NULL, + .value_len = 0, + }; + unsigned int total_size, shift_bytes, temp = ~0U, extra_isize=0; + + start = (char *)inode + EXT2_GOOD_OLD_INODE_SIZE + + inode->i_extra_isize + sizeof(__u32); + end = (char *)inode + EXT2_INODE_SIZE(fs->super); + last = (struct ext2_ext_attr_entry *)start; + + /* Find the entry best suited to be pushed into EA block */ + entry = NULL; + for (; !EXT2_EXT_IS_LAST_ENTRY(last); + last = EXT2_EXT_ATTR_NEXT(last)) { + total_size = EXT2_EXT_ATTR_SIZE(last->e_value_size) + + EXT2_EXT_ATTR_LEN(last->e_name_len); + if (total_size <= free && total_size < temp) { + if (total_size < new_extra_isize) { + small_entry = last; + } else { + entry = last; + temp = total_size; + } + } + } + + if (entry == NULL) { + if (small_entry) { + entry = small_entry; + } else { + if (!tried_min_extra_isize && + s_min_extra_isize) { + tried_min_extra_isize++; + new_extra_isize = s_min_extra_isize; + goto retry; + } + if (ret) + *ret = EXT2_EXPAND_EISIZE_NOSPC; + error = EXT2_ET_EA_NO_SPACE; + goto cleanup; + } + } + offs = entry->e_value_offs; + size = entry->e_value_size; + entry_size = EXT2_EXT_ATTR_LEN(entry->e_name_len); + i.name_index = entry->e_name_index; + error = ext2fs_get_mem(EXT2_EXT_ATTR_SIZE(size), &buffer); + if (error) + goto cleanup; + error = ext2fs_get_mem(entry->e_name_len + 1, &b_entry_name); + if (error) + goto cleanup; + /* Save the entry name and the entry value */ + memcpy((char *)buffer, (char *)start + offs, + EXT2_EXT_ATTR_SIZE(size)); + memcpy((char *)b_entry_name, (char *)entry->e_name, + entry->e_name_len); + b_entry_name[entry->e_name_len] = '\0'; + i.name = b_entry_name; + + error = ext2fs_attr_ibody_find(fs, inode, &i, &is); + if (error) + goto cleanup; + + error = ext2fs_attr_set_entry(fs, &i, &is.s); + if (error) + goto cleanup; + + entry = (struct ext2_ext_attr_entry *)start; + if (entry_size + EXT2_EXT_ATTR_SIZE(size) >= new_extra_isize) + shift_bytes = new_extra_isize; + else + shift_bytes = entry_size + EXT2_EXT_ATTR_SIZE(size); + ext2fs_attr_shift_entries(entry, + inode->i_extra_isize - shift_bytes, + (char *)inode +EXT2_GOOD_OLD_INODE_SIZE+ + extra_isize + shift_bytes, + start - sizeof(__u32), + total_ino - entry_size); + + extra_isize += shift_bytes; + new_extra_isize -= shift_bytes; + if (needed_size) + *needed_size = new_extra_isize; + inode->i_extra_isize = extra_isize; + + i.name = b_entry_name; + i.value = buffer; + i.value_len = size; + error = ext2fs_attr_block_find(fs, (struct ext2_inode *)inode, + &i, &bs); + if (error) + goto cleanup; + + /* Add entry which was removed from the inode into the block */ + error = ext2fs_attr_block_set(fs, (struct ext2_inode *)inode, + &i, &bs); + if (error) + goto cleanup; + } + +write_inode: + error = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)inode, + EXT2_INODE_SIZE(fs->super)); +cleanup: + if (inode_buf) + ext2fs_free_mem(&inode_buf); + if (block_buf) + ext2fs_free_mem(&block_buf); + if (buffer) + ext2fs_free_mem(&buffer); + if (b_entry_name) + ext2fs_free_mem(&b_entry_name); + + return error; +} diff --git a/tests/f_itable_collision/expect.1 b/tests/f_itable_collision/expect.1 index 7e98baa..f303a48 100644 --- a/tests/f_itable_collision/expect.1 +++ b/tests/f_itable_collision/expect.1 @@ -26,12 +26,12 @@ Clear inode? yes Restarting e2fsck from the beginning... Pass 1: Checking inodes, blocks, and sizes Inode 12 block 37 conflicts with critical metadata, skipping block checks. -Illegal block number passed to ext2fs_mark_block_bitmap #4294967294 for in-use block map +Illegal block number passed to ext2fs_mark_block_bitmap #4294880894 for in-use block map Illegal block number passed to ext2fs_mark_block_bitmap #268435455 for in-use block map Running additional passes to resolve blocks claimed by more than one inode... Pass 1B: Rescanning for multiply-claimed blocks -Illegal block number passed to ext2fs_test_block_bitmap #4294967294 for multiply claimed block map +Illegal block number passed to ext2fs_test_block_bitmap #4294880894 for multiply claimed block map Illegal block number passed to ext2fs_test_block_bitmap #268435455 for multiply claimed block map Multiply-claimed block(s) in inode 12: 37 Pass 1C: Scanning directories for inodes with multiply-claimed blocks @@ -43,7 +43,7 @@ File /a (inode #12, mod time Fri Jun 27 18:34:44 2014) Clone multiply-claimed blocks? yes -Illegal block number passed to ext2fs_test_block_bitmap #4294967294 for multiply claimed block map +Illegal block number passed to ext2fs_test_block_bitmap #4294880894 for multiply claimed block map Illegal block number passed to ext2fs_test_block_bitmap #268435455 for multiply claimed block map Pass 2: Checking directory structure Setting filetype for entry 'bad1' in / (2) to 1. @@ -51,7 +51,7 @@ Setting filetype for entry 'bad2' in / (2) to 1. Restarting e2fsck from the beginning... Pass 1: Checking inodes, blocks, and sizes Inode 12 has an invalid extent - (logical block 0, invalid physical block 4294967294, len 1) + (logical block 0, invalid physical block 4294880894, len 1) Clear? yes Inode 12 has an invalid extent diff --git a/tests/f_itable_collision/script b/tests/f_itable_collision/script index 66abd90..2dd19bd 100755 --- a/tests/f_itable_collision/script +++ b/tests/f_itable_collision/script @@ -6,7 +6,7 @@ FSCK_OPT=-fy IMAGE=$test_dir/image.gz -E2FSCK_TIME=4294967294 +E2FSCK_TIME=4294880894 export E2FSCK_TIME gzip -d < $IMAGE > $TMPFILE -- 1.8.3.1