diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h index 9492955d84f9f1efd3696fbbac0379e45e6d1b2e..a0cb756ac28fc8eca5e7ce4bcb46e6e8a68054cc 100644 --- a/libxfs/libxfs_api_defs.h +++ b/libxfs/libxfs_api_defs.h @@ -195,6 +195,7 @@ #define xfs_verify_agino libxfs_verify_agino #define xfs_verify_cksum libxfs_verify_cksum #define xfs_verify_dir_ino libxfs_verify_dir_ino +#define xfs_verify_fsbext libxfs_verify_fsbext #define xfs_verify_fsbno libxfs_verify_fsbno #define xfs_verify_ino libxfs_verify_ino #define xfs_verify_rtbno libxfs_verify_rtbno diff --git a/libxfs/xfs_bmap.c b/libxfs/xfs_bmap.c index e0ca8b050c46853063c8da523e224148df29f004..b25e32d48d3faafe1f106f47e7cc73db142bc274 100644 --- a/libxfs/xfs_bmap.c +++ b/libxfs/xfs_bmap.c @@ -6230,12 +6230,8 @@ xfs_bmap_validate_extent( if (!xfs_verify_rtbno(mp, endfsb)) return __this_address; } else { - if (!xfs_verify_fsbno(mp, irec->br_startblock)) - return __this_address; - if (!xfs_verify_fsbno(mp, endfsb)) - return __this_address; - if (XFS_FSB_TO_AGNO(mp, irec->br_startblock) != - XFS_FSB_TO_AGNO(mp, endfsb)) + if (!xfs_verify_fsbext(mp, irec->br_startblock, + irec->br_blockcount)) return __this_address; } if (irec->br_state != XFS_EXT_NORM && whichfork != XFS_DATA_FORK) diff --git a/libxfs/xfs_types.c b/libxfs/xfs_types.c index fa113727b476062c5f5c3af2b92202410d289f28..3e6921e0137a0f75389059174c287cce835c6322 100644 --- a/libxfs/xfs_types.c +++ b/libxfs/xfs_types.c @@ -61,6 +61,29 @@ xfs_verify_fsbno( return xfs_verify_agbno(mp, agno, XFS_FSB_TO_AGBNO(mp, fsbno)); } +/* + * Verify that a data device extent is fully contained inside the filesystem, + * does not cross an AG boundary, and does not point at static metadata. + */ +bool +xfs_verify_fsbext( + struct xfs_mount *mp, + xfs_fsblock_t fsbno, + xfs_fsblock_t len) +{ + if (fsbno + len <= fsbno) + return false; + + if (!xfs_verify_fsbno(mp, fsbno)) + return false; + + if (!xfs_verify_fsbno(mp, fsbno + len - 1)) + return false; + + return XFS_FSB_TO_AGNO(mp, fsbno) == + XFS_FSB_TO_AGNO(mp, fsbno + len - 1); +} + /* Calculate the first and last possible inode number in an AG. */ void xfs_agino_range( diff --git a/libxfs/xfs_types.h b/libxfs/xfs_types.h index 397d94775440d039e43d05b37ebb68dac045459a..7feaaac25b3d76ee95c3c40463c4b99d08cb994b 100644 --- a/libxfs/xfs_types.h +++ b/libxfs/xfs_types.h @@ -184,6 +184,8 @@ xfs_agblock_t xfs_ag_block_count(struct xfs_mount *mp, xfs_agnumber_t agno); bool xfs_verify_agbno(struct xfs_mount *mp, xfs_agnumber_t agno, xfs_agblock_t agbno); bool xfs_verify_fsbno(struct xfs_mount *mp, xfs_fsblock_t fsbno); +bool xfs_verify_fsbext(struct xfs_mount *mp, xfs_fsblock_t fsbno, + xfs_fsblock_t len); void xfs_agino_range(struct xfs_mount *mp, xfs_agnumber_t agno, xfs_agino_t *first, xfs_agino_t *last); diff --git a/mkfs/xfs_mkfs.c b/mkfs/xfs_mkfs.c index 0581843f50d052944d348bdc6876a09021c133e9..ecd13fca701541f2cb27f3ddda03467f97c38a8d 100644 --- a/mkfs/xfs_mkfs.c +++ b/mkfs/xfs_mkfs.c @@ -130,8 +130,11 @@ enum { M_MAX_OPTS, }; -/* Just define the max options array size manually right now */ -#define MAX_SUBOPTS D_MAX_OPTS +/* + * Just define the max options array size manually to the largest + * enum right now, leaving room for a NULL terminator at the end + */ +#define MAX_SUBOPTS (D_MAX_OPTS + 1) #define SUBOPT_NEEDS_VAL (-1LL) #define MAX_CONFLICTS 8 @@ -241,6 +244,7 @@ static struct opt_params bopts = { .ini_section = "block", .subopts = { [B_SIZE] = "size", + [B_MAX_OPTS] = NULL, }, .subopt_params = { { .index = B_SIZE, @@ -267,6 +271,7 @@ static struct opt_params copts = { .name = 'c', .subopts = { [C_OPTFILE] = "options", + [C_MAX_OPTS] = NULL, }, .subopt_params = { { .index = C_OPTFILE, @@ -296,6 +301,7 @@ static struct opt_params dopts = { [D_EXTSZINHERIT] = "extszinherit", [D_COWEXTSIZE] = "cowextsize", [D_DAXINHERIT] = "daxinherit", + [D_MAX_OPTS] = NULL, }, .subopt_params = { { .index = D_AGCOUNT, @@ -432,6 +438,7 @@ static struct opt_params iopts = { [I_ATTR] = "attr", [I_PROJID32BIT] = "projid32bit", [I_SPINODES] = "sparse", + [I_MAX_OPTS] = NULL, }, .subopt_params = { { .index = I_ALIGN, @@ -498,6 +505,7 @@ static struct opt_params lopts = { [L_FILE] = "file", [L_NAME] = "name", [L_LAZYSBCNTR] = "lazy-count", + [L_MAX_OPTS] = NULL, }, .subopt_params = { { .index = L_AGNUM, @@ -590,6 +598,7 @@ static struct opt_params nopts = { [N_SIZE] = "size", [N_VERSION] = "version", [N_FTYPE] = "ftype", + [N_MAX_OPTS] = NULL, }, .subopt_params = { { .index = N_SIZE, @@ -625,6 +634,7 @@ static struct opt_params ropts = { [R_FILE] = "file", [R_NAME] = "name", [R_NOALIGN] = "noalign", + [R_MAX_OPTS] = NULL, }, .subopt_params = { { .index = R_EXTSIZE, @@ -672,6 +682,7 @@ static struct opt_params sopts = { .subopts = { [S_SIZE] = "size", [S_SECTSIZE] = "sectsize", + [S_MAX_OPTS] = NULL, }, .subopt_params = { { .index = S_SIZE, @@ -708,6 +719,7 @@ static struct opt_params mopts = { [M_REFLINK] = "reflink", [M_INOBTCNT] = "inobtcount", [M_BIGTIME] = "bigtime", + [M_MAX_OPTS] = NULL, }, .subopt_params = { { .index = M_CRC, @@ -3151,9 +3163,10 @@ sb_set_features( static void align_log_size( struct mkfs_params *cfg, - int sunit) + int sunit, + int max_logblocks) { - uint64_t tmp_logblocks; + uint64_t tmp_logblocks; /* nothing to do if it's already aligned. */ if ((cfg->logblocks % sunit) == 0) @@ -3170,7 +3183,8 @@ _("log size %lld is not a multiple of the log stripe unit %d\n"), /* If the log is too large, round down instead of round up */ if ((tmp_logblocks > XFS_MAX_LOG_BLOCKS) || - ((tmp_logblocks << cfg->blocklog) > XFS_MAX_LOG_BYTES)) { + ((tmp_logblocks << cfg->blocklog) > XFS_MAX_LOG_BYTES) || + tmp_logblocks > max_logblocks) { tmp_logblocks = (cfg->logblocks / sunit) * sunit; } cfg->logblocks = tmp_logblocks; @@ -3184,17 +3198,16 @@ static void align_internal_log( struct mkfs_params *cfg, struct xfs_mount *mp, - int sunit) + int sunit, + int max_logblocks) { - uint64_t logend; - /* round up log start if necessary */ if ((cfg->logstart % sunit) != 0) cfg->logstart = ((cfg->logstart + (sunit - 1)) / sunit) * sunit; /* If our log start overlaps the next AG's metadata, fail. */ - if (XFS_FSB_TO_AGBNO(mp, cfg->logstart) <= XFS_AGFL_BLOCK(mp)) { - fprintf(stderr, + if (!libxfs_verify_fsbno(mp, cfg->logstart)) { + fprintf(stderr, _("Due to stripe alignment, the internal log start (%lld) cannot be aligned\n" "within an allocation group.\n"), (long long) cfg->logstart); @@ -3202,11 +3215,19 @@ _("Due to stripe alignment, the internal log start (%lld) cannot be aligned\n" } /* round up/down the log size now */ - align_log_size(cfg, sunit); + align_log_size(cfg, sunit, max_logblocks); + + /* + * If the end of the log has been rounded past the end of the AG, + * reduce logblocks by a stripe unit to try to get it back under EOAG. + */ + if (!libxfs_verify_fsbext(mp, cfg->logstart, cfg->logblocks) && + cfg->logblocks > sunit) { + cfg->logblocks -= sunit; + } /* check the aligned log still starts and ends in the same AG. */ - logend = cfg->logstart + cfg->logblocks - 1; - if (XFS_FSB_TO_AGNO(mp, cfg->logstart) != XFS_FSB_TO_AGNO(mp, logend)) { + if (!libxfs_verify_fsbext(mp, cfg->logstart, cfg->logblocks)) { fprintf(stderr, _("Due to stripe alignment, the internal log size (%lld) is too large.\n" "Must fit within an allocation group.\n"), @@ -3238,6 +3259,49 @@ validate_log_size(uint64_t logblocks, int blocklog, int min_logblocks) } } +static void +adjust_ag0_internal_logblocks( + struct mkfs_params *cfg, + struct xfs_mount *mp, + int min_logblocks, + int *max_logblocks) +{ + int backoff = 0; + int ichunk_blocks; + + /* + * mkfs will trip over the write verifiers if the log is allocated in + * AG 0 and consumes enough space that we cannot allocate a non-sparse + * inode chunk for the root directory. The inode allocator requires + * that the AG have enough free space for the chunk itself plus enough + * to fix up the freelist with aligned blocks if we need to fill the + * allocation from the AGFL. + */ + ichunk_blocks = XFS_INODES_PER_CHUNK * cfg->inodesize >> cfg->blocklog; + backoff = ichunk_blocks * 4; + + /* + * We try to align inode allocations to the data device stripe unit, + * so ensure there's enough space to perform an aligned allocation. + * The inode geometry structure isn't set up yet, so compute this by + * hand. + */ + backoff = max(backoff, cfg->dsunit * 2); + + *max_logblocks -= backoff; + + /* If the specified log size is too big, complain. */ + if (cli_opt_set(&lopts, L_SIZE) && cfg->logblocks > *max_logblocks) { + fprintf(stderr, +_("internal log size %lld too large, must be less than %d\n"), + (long long)cfg->logblocks, + *max_logblocks); + usage(); + } + + cfg->logblocks = min(cfg->logblocks, *max_logblocks); +} + static void calculate_log_size( struct mkfs_params *cfg, @@ -3246,6 +3310,7 @@ calculate_log_size( { struct xfs_sb *sbp = &mp->m_sb; int min_logblocks; + int max_logblocks; /* absolute max for this AG */ struct xfs_mount mount; /* we need a temporary mount to calculate the minimum log size. */ @@ -3279,12 +3344,24 @@ _("external log device size %lld blocks too small, must be at least %lld blocks\ cfg->logstart = 0; cfg->logagno = 0; if (cfg->lsunit) - align_log_size(cfg, cfg->lsunit); + align_log_size(cfg, cfg->lsunit, XFS_MAX_LOG_BLOCKS); validate_log_size(cfg->logblocks, cfg->blocklog, min_logblocks); return; } + /* + * Make sure the log fits wholly within an AG + * + * XXX: If agf->freeblks ends up as 0 because the log uses all + * the free space, it causes the kernel all sorts of problems + * with per-ag reservations. Right now just back it off one + * block, but there's a whole can of worms here that needs to be + * opened to decide what is the valid maximum size of a log in + * an AG. + */ + max_logblocks = libxfs_alloc_ag_max_usable(mp) - 1; + /* internal log - if no size specified, calculate automatically */ if (!cfg->logblocks) { if (cfg->dblocks < GIGABYTES(1, cfg->blocklog)) { @@ -3310,21 +3387,9 @@ _("external log device size %lld blocks too small, must be at least %lld blocks\ cfg->logblocks = cfg->logblocks >> cfg->blocklog; } - /* Ensure the chosen size meets minimum log size requirements */ + /* Ensure the chosen size fits within log size requirements */ cfg->logblocks = max(min_logblocks, cfg->logblocks); - - /* - * Make sure the log fits wholly within an AG - * - * XXX: If agf->freeblks ends up as 0 because the log uses all - * the free space, it causes the kernel all sorts of problems - * with per-ag reservations. Right now just back it off one - * block, but there's a whole can of worms here that needs to be - * opened to decide what is the valid maximum size of a log in - * an AG. - */ - cfg->logblocks = min(cfg->logblocks, - libxfs_alloc_ag_max_usable(mp) - 1); + cfg->logblocks = min(cfg->logblocks, max_logblocks); /* and now clamp the size to the maximum supported size */ cfg->logblocks = min(cfg->logblocks, XFS_MAX_LOG_BLOCKS); @@ -3332,6 +3397,13 @@ _("external log device size %lld blocks too small, must be at least %lld blocks\ cfg->logblocks = XFS_MAX_LOG_BYTES >> cfg->blocklog; validate_log_size(cfg->logblocks, cfg->blocklog, min_logblocks); + } else if (cfg->logblocks > max_logblocks) { + /* check specified log size */ + fprintf(stderr, +_("internal log size %lld too large, must be less than %d\n"), + (long long)cfg->logblocks, + max_logblocks); + usage(); } if (cfg->logblocks > sbp->sb_agblocks - libxfs_prealloc_blocks(mp)) { @@ -3353,6 +3425,10 @@ _("log ag number %lld too large, must be less than %lld\n"), } else cfg->logagno = (xfs_agnumber_t)(sbp->sb_agcount / 2); + if (cfg->logagno == 0) + adjust_ag0_internal_logblocks(cfg, mp, min_logblocks, + &max_logblocks); + cfg->logstart = XFS_AGB_TO_FSB(mp, cfg->logagno, libxfs_prealloc_blocks(mp)); @@ -3360,9 +3436,9 @@ _("log ag number %lld too large, must be less than %lld\n"), * Align the logstart at stripe unit boundary. */ if (cfg->lsunit) { - align_internal_log(cfg, mp, cfg->lsunit); + align_internal_log(cfg, mp, cfg->lsunit, max_logblocks); } else if (cfg->dsunit) { - align_internal_log(cfg, mp, cfg->dsunit); + align_internal_log(cfg, mp, cfg->dsunit, max_logblocks); } validate_log_size(cfg->logblocks, cfg->blocklog, min_logblocks); } @@ -3389,6 +3465,7 @@ start_superblock_setup( sbp->sb_agblocks = (xfs_agblock_t)cfg->agsize; sbp->sb_agblklog = (uint8_t)log2_roundup(cfg->agsize); sbp->sb_agcount = (xfs_agnumber_t)cfg->agcount; + sbp->sb_dblocks = (xfs_rfsblock_t)cfg->dblocks; sbp->sb_inodesize = (uint16_t)cfg->inodesize; sbp->sb_inodelog = (uint8_t)cfg->inodelog; diff --git a/repair/attr_repair.c b/repair/attr_repair.c index ba39c2b5ede48c8a9d8af1bf9457c49fa7136b2a..090b5caee9528c955b205e22dd4ec8ce52b0ed92 100644 --- a/repair/attr_repair.c +++ b/repair/attr_repair.c @@ -579,6 +579,26 @@ process_leaf_attr_block( firstb = mp->m_sb.sb_blocksize; stop = xfs_attr3_leaf_hdr_size(leaf); + /* + * Empty leaf blocks at offset zero can occur as a race between + * setxattr and the system going down, so we only take action if we're + * running in modify mode. See xfs_attr3_leaf_verify for details of + * how we've screwed this up many times. + */ + if (!leafhdr.count && da_bno == 0) { + if (no_modify) { + do_log( + _("would clear empty leaf attr block 0, inode %" PRIu64 "\n"), + ino); + return 0; + } + + do_warn( + _("will clear empty leaf attr block 0, inode %" PRIu64 "\n"), + ino); + return 1; + } + /* does the count look sorta valid? */ if (!leafhdr.count || leafhdr.count * sizeof(xfs_attr_leaf_entry_t) + stop >