| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| |
| #include "libxfs.h" |
| #include "globals.h" |
| #include "agheader.h" |
| #include "protos.h" |
| #include "err_protos.h" |
| |
| /* |
| * XXX (dgc): What is the point of all the check and repair here when phase 5 |
| * recreates the AGF/AGI/AGFL completely from scratch? |
| */ |
| |
| static int |
| verify_set_agf(xfs_mount_t *mp, xfs_agf_t *agf, xfs_agnumber_t i) |
| { |
| xfs_rfsblock_t agblocks; |
| int retval = 0; |
| |
| /* check common fields */ |
| |
| if (be32_to_cpu(agf->agf_magicnum) != XFS_AGF_MAGIC) { |
| retval = XR_AG_AGF; |
| do_warn(_("bad magic # 0x%x for agf %d\n"), |
| be32_to_cpu(agf->agf_magicnum), i); |
| |
| if (!no_modify) |
| agf->agf_magicnum = cpu_to_be32(XFS_AGF_MAGIC); |
| } |
| |
| if (!XFS_AGF_GOOD_VERSION(be32_to_cpu(agf->agf_versionnum))) { |
| retval = XR_AG_AGF; |
| do_warn(_("bad version # %d for agf %d\n"), |
| be32_to_cpu(agf->agf_versionnum), i); |
| |
| if (!no_modify) |
| agf->agf_versionnum = cpu_to_be32(XFS_AGF_VERSION); |
| } |
| |
| if (be32_to_cpu(agf->agf_seqno) != i) { |
| retval = XR_AG_AGF; |
| do_warn(_("bad sequence # %d for agf %d\n"), |
| be32_to_cpu(agf->agf_seqno), i); |
| |
| if (!no_modify) |
| agf->agf_seqno = cpu_to_be32(i); |
| } |
| |
| if (be32_to_cpu(agf->agf_length) != mp->m_sb.sb_agblocks) { |
| if (i != mp->m_sb.sb_agcount - 1) { |
| retval = XR_AG_AGF; |
| do_warn(_("bad length %d for agf %d, should be %d\n"), |
| be32_to_cpu(agf->agf_length), i, |
| mp->m_sb.sb_agblocks); |
| if (!no_modify) |
| agf->agf_length = |
| cpu_to_be32(mp->m_sb.sb_agblocks); |
| } else { |
| agblocks = mp->m_sb.sb_dblocks - |
| (xfs_rfsblock_t) mp->m_sb.sb_agblocks * i; |
| |
| if (be32_to_cpu(agf->agf_length) != agblocks) { |
| retval = XR_AG_AGF; |
| do_warn( |
| _("bad length %d for agf %d, should be %" PRIu64 "\n"), |
| be32_to_cpu(agf->agf_length), |
| i, agblocks); |
| if (!no_modify) |
| agf->agf_length = cpu_to_be32(agblocks); |
| } |
| } |
| } |
| |
| /* |
| * check first/last AGF fields. if need be, lose the free |
| * space in the AGFL, we'll reclaim it later. |
| */ |
| if (be32_to_cpu(agf->agf_flfirst) >= libxfs_agfl_size(mp)) { |
| do_warn(_("flfirst %d in agf %d too large (max = %u)\n"), |
| be32_to_cpu(agf->agf_flfirst), |
| i, libxfs_agfl_size(mp) - 1); |
| if (!no_modify) |
| agf->agf_flfirst = cpu_to_be32(0); |
| } |
| |
| if (be32_to_cpu(agf->agf_fllast) >= libxfs_agfl_size(mp)) { |
| do_warn(_("fllast %d in agf %d too large (max = %u)\n"), |
| be32_to_cpu(agf->agf_fllast), |
| i, libxfs_agfl_size(mp) - 1); |
| if (!no_modify) |
| agf->agf_fllast = cpu_to_be32(0); |
| } |
| |
| /* don't check freespace btrees -- will be checked by caller */ |
| |
| if (!xfs_has_crc(mp)) |
| return retval; |
| |
| if (platform_uuid_compare(&agf->agf_uuid, &mp->m_sb.sb_meta_uuid)) { |
| char uu[64]; |
| |
| retval = XR_AG_AGF; |
| platform_uuid_unparse(&agf->agf_uuid, uu); |
| do_warn(_("bad uuid %s for agf %d\n"), uu, i); |
| |
| if (!no_modify) |
| platform_uuid_copy(&agf->agf_uuid, |
| &mp->m_sb.sb_meta_uuid); |
| } |
| return retval; |
| } |
| |
| static int |
| verify_set_agi(xfs_mount_t *mp, xfs_agi_t *agi, xfs_agnumber_t agno) |
| { |
| xfs_rfsblock_t agblocks; |
| int retval = 0; |
| |
| /* check common fields */ |
| |
| if (be32_to_cpu(agi->agi_magicnum) != XFS_AGI_MAGIC) { |
| retval = XR_AG_AGI; |
| do_warn(_("bad magic # 0x%x for agi %d\n"), |
| be32_to_cpu(agi->agi_magicnum), agno); |
| |
| if (!no_modify) |
| agi->agi_magicnum = cpu_to_be32(XFS_AGI_MAGIC); |
| } |
| |
| if (!XFS_AGI_GOOD_VERSION(be32_to_cpu(agi->agi_versionnum))) { |
| retval = XR_AG_AGI; |
| do_warn(_("bad version # %d for agi %d\n"), |
| be32_to_cpu(agi->agi_versionnum), agno); |
| |
| if (!no_modify) |
| agi->agi_versionnum = cpu_to_be32(XFS_AGI_VERSION); |
| } |
| |
| if (be32_to_cpu(agi->agi_seqno) != agno) { |
| retval = XR_AG_AGI; |
| do_warn(_("bad sequence # %d for agi %d\n"), |
| be32_to_cpu(agi->agi_seqno), agno); |
| |
| if (!no_modify) |
| agi->agi_seqno = cpu_to_be32(agno); |
| } |
| |
| if (be32_to_cpu(agi->agi_length) != mp->m_sb.sb_agblocks) { |
| if (agno != mp->m_sb.sb_agcount - 1) { |
| retval = XR_AG_AGI; |
| do_warn(_("bad length # %d for agi %d, should be %d\n"), |
| be32_to_cpu(agi->agi_length), agno, |
| mp->m_sb.sb_agblocks); |
| if (!no_modify) |
| agi->agi_length = |
| cpu_to_be32(mp->m_sb.sb_agblocks); |
| } else { |
| agblocks = mp->m_sb.sb_dblocks - |
| (xfs_rfsblock_t) mp->m_sb.sb_agblocks * agno; |
| |
| if (be32_to_cpu(agi->agi_length) != agblocks) { |
| retval = XR_AG_AGI; |
| do_warn( |
| _("bad length # %d for agi %d, should be %" PRIu64 "\n"), |
| be32_to_cpu(agi->agi_length), |
| agno, agblocks); |
| if (!no_modify) |
| agi->agi_length = cpu_to_be32(agblocks); |
| } |
| } |
| } |
| |
| /* don't check inode btree -- will be checked by caller */ |
| |
| if (!xfs_has_crc(mp)) |
| return retval; |
| |
| if (platform_uuid_compare(&agi->agi_uuid, &mp->m_sb.sb_meta_uuid)) { |
| char uu[64]; |
| |
| retval = XR_AG_AGI; |
| platform_uuid_unparse(&agi->agi_uuid, uu); |
| do_warn(_("bad uuid %s for agi %d\n"), uu, agno); |
| |
| if (!no_modify) |
| platform_uuid_copy(&agi->agi_uuid, |
| &mp->m_sb.sb_meta_uuid); |
| } |
| |
| return retval; |
| } |
| |
| /* |
| * superblock comparison - compare arbitrary superblock with |
| * filesystem mount-point superblock |
| * |
| * the verified fields include id and geometry. |
| * |
| * the inprogress fields, version numbers, and counters |
| * are allowed to differ as well as all fields after the |
| * counters to cope with the pre-6.5 mkfs non-zeroed |
| * secondary superblock sectors. |
| */ |
| static int |
| compare_sb(xfs_mount_t *mp, xfs_sb_t *sb) |
| { |
| fs_geometry_t fs_geo, sb_geo; |
| |
| get_sb_geometry(&fs_geo, &mp->m_sb); |
| get_sb_geometry(&sb_geo, sb); |
| |
| if (memcmp(&fs_geo, &sb_geo, |
| (char *) &fs_geo.sb_shared_vn - (char *) &fs_geo)) |
| return(XR_SB_GEO_MISMATCH); |
| |
| return(XR_OK); |
| } |
| |
| /* |
| * If the fs feature bits on a secondary superblock don't match the |
| * primary, we need to update them. |
| */ |
| static inline int |
| check_v5_feature_mismatch( |
| struct xfs_mount *mp, |
| xfs_agnumber_t agno, |
| struct xfs_sb *sb) |
| { |
| bool dirty = false; |
| |
| if (!xfs_has_crc(mp) || agno == 0) |
| return 0; |
| |
| if (mp->m_sb.sb_features_compat != sb->sb_features_compat) { |
| if (no_modify) { |
| do_warn( |
| _("would fix compat feature mismatch in AG %u super, 0x%x != 0x%x\n"), |
| agno, mp->m_sb.sb_features_compat, |
| sb->sb_features_compat); |
| } else { |
| do_warn( |
| _("will fix compat feature mismatch in AG %u super, 0x%x != 0x%x\n"), |
| agno, mp->m_sb.sb_features_compat, |
| sb->sb_features_compat); |
| dirty = true; |
| } |
| } |
| |
| /* |
| * Ignore XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR becauses the repair upgrade |
| * path sets it only on the primary while upgrading. |
| */ |
| if ((mp->m_sb.sb_features_incompat ^ sb->sb_features_incompat) & |
| ~XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR) { |
| if (no_modify) { |
| do_warn( |
| _("would fix incompat feature mismatch in AG %u super, 0x%x != 0x%x\n"), |
| agno, mp->m_sb.sb_features_incompat, |
| sb->sb_features_incompat); |
| } else { |
| do_warn( |
| _("will fix incompat feature mismatch in AG %u super, 0x%x != 0x%x\n"), |
| agno, mp->m_sb.sb_features_incompat, |
| sb->sb_features_incompat); |
| dirty = true; |
| } |
| } |
| |
| if (mp->m_sb.sb_features_ro_compat != sb->sb_features_ro_compat) { |
| if (no_modify) { |
| do_warn( |
| _("would fix ro compat feature mismatch in AG %u super, 0x%x != 0x%x\n"), |
| agno, mp->m_sb.sb_features_ro_compat, |
| sb->sb_features_ro_compat); |
| } else { |
| do_warn( |
| _("will fix ro compat feature mismatch in AG %u super, 0x%x != 0x%x\n"), |
| agno, mp->m_sb.sb_features_ro_compat, |
| sb->sb_features_ro_compat); |
| dirty = true; |
| } |
| } |
| |
| /* |
| * Log incompat feature bits are set and cleared from the primary super |
| * as needed to protect against log replay on old kernels finding log |
| * records that they cannot handle. Secondary sb resyncs performed as |
| * part of a geometry update to the primary sb (e.g. growfs, label/uuid |
| * changes) will copy the log incompat feature bits, but it's not a |
| * corruption for a secondary to have a bit set that is clear in the |
| * primary super. |
| */ |
| if (mp->m_sb.sb_features_log_incompat != sb->sb_features_log_incompat) { |
| if (no_modify) { |
| do_log( |
| _("would sync log incompat feature in AG %u super, 0x%x != 0x%x\n"), |
| agno, mp->m_sb.sb_features_log_incompat, |
| sb->sb_features_log_incompat); |
| } else { |
| do_warn( |
| _("will sync log incompat feature in AG %u super, 0x%x != 0x%x\n"), |
| agno, mp->m_sb.sb_features_log_incompat, |
| sb->sb_features_log_incompat); |
| dirty = true; |
| } |
| } |
| |
| if (!dirty) |
| return 0; |
| |
| sb->sb_features_compat = mp->m_sb.sb_features_compat; |
| sb->sb_features_ro_compat = mp->m_sb.sb_features_ro_compat; |
| sb->sb_features_incompat = mp->m_sb.sb_features_incompat; |
| sb->sb_features_log_incompat = mp->m_sb.sb_features_log_incompat; |
| return XR_AG_SB_SEC; |
| } |
| |
| /* |
| * Possible fields that may have been set at mkfs time, |
| * sb_inoalignmt, sb_unit, sb_width and sb_dirblklog. |
| * The quota inode fields in the secondaries should be zero. |
| * Likewise, the sb_flags and sb_shared_vn should also be |
| * zero and the shared version bit should be cleared for |
| * current mkfs's. |
| * |
| * And everything else in the buffer beyond either sb_width, |
| * sb_dirblklog (v2 dirs), or sb_logsectsize can be zeroed. |
| * |
| * Note: contrary to the name, this routine is called for all |
| * superblocks, not just the secondary superblocks. |
| */ |
| static int |
| secondary_sb_whack( |
| struct xfs_mount *mp, |
| struct xfs_buf *sbuf, |
| struct xfs_sb *sb, |
| xfs_agnumber_t i) |
| { |
| struct xfs_dsb *dsb = sbuf->b_addr; |
| int do_bzero = 0; |
| int size; |
| char *ip; |
| int rval = 0; |
| uuid_t tmpuuid; |
| |
| rval = do_bzero = 0; |
| |
| /* |
| * Check for garbage beyond the last valid field. |
| * Use field addresses instead so this code will still |
| * work against older filesystems when the superblock |
| * gets rev'ed again with new fields appended. |
| * |
| * size is the size of data which is valid for this sb. |
| */ |
| if (xfs_sb_version_hasmetauuid(sb)) |
| size = offsetof(xfs_sb_t, sb_meta_uuid) |
| + sizeof(sb->sb_meta_uuid); |
| else if (xfs_sb_version_hascrc(sb)) |
| size = offsetof(xfs_sb_t, sb_lsn) |
| + sizeof(sb->sb_lsn); |
| else if (xfs_sb_version_hasmorebits(sb)) |
| size = offsetof(xfs_sb_t, sb_bad_features2) |
| + sizeof(sb->sb_bad_features2); |
| else if (xfs_sb_version_haslogv2(sb)) |
| size = offsetof(xfs_sb_t, sb_logsunit) |
| + sizeof(sb->sb_logsunit); |
| else if (xfs_sb_version_hassector(sb)) |
| size = offsetof(xfs_sb_t, sb_logsectsize) |
| + sizeof(sb->sb_logsectsize); |
| else /* only support dirv2 or more recent */ |
| size = offsetof(xfs_sb_t, sb_dirblklog) |
| + sizeof(sb->sb_dirblklog); |
| |
| /* Check the buffer we read from disk for garbage outside size */ |
| for (ip = (char *)sbuf->b_addr + size; |
| ip < (char *)sbuf->b_addr + mp->m_sb.sb_sectsize; |
| ip++) { |
| if (*ip) { |
| do_bzero = 1; |
| break; |
| } |
| } |
| if (do_bzero) { |
| rval |= XR_AG_SB_SEC; |
| if (!no_modify) { |
| do_warn( |
| _("zeroing unused portion of %s superblock (AG #%u)\n"), |
| !i ? _("primary") : _("secondary"), i); |
| /* |
| * zero both the in-memory sb and the disk buffer, |
| * because the former was read from disk and |
| * may contain newer version fields that shouldn't |
| * be set, and the latter is never updated past |
| * the last field - just zap them both. |
| */ |
| memcpy(&tmpuuid, &sb->sb_meta_uuid, sizeof(uuid_t)); |
| memset((void *)((intptr_t)sb + size), 0, |
| mp->m_sb.sb_sectsize - size); |
| memset((char *)sbuf->b_addr + size, 0, |
| mp->m_sb.sb_sectsize - size); |
| /* Preserve meta_uuid so we don't fail uuid checks */ |
| memcpy(&sb->sb_meta_uuid, &tmpuuid, sizeof(uuid_t)); |
| } else |
| do_warn( |
| _("would zero unused portion of %s superblock (AG #%u)\n"), |
| !i ? _("primary") : _("secondary"), i); |
| } |
| |
| /* |
| * now look for the fields we can manipulate directly. |
| * if we did a zero and that zero could have included |
| * the field in question, just silently reset it. otherwise, |
| * complain. |
| * |
| * for now, just zero the flags field since only |
| * the readonly flag is used |
| */ |
| if (sb->sb_flags) { |
| if (!no_modify) |
| sb->sb_flags = 0; |
| if (!do_bzero) { |
| rval |= XR_AG_SB; |
| do_warn(_("bad flags field in superblock %d\n"), i); |
| } else |
| rval |= XR_AG_SB_SEC; |
| } |
| |
| /* |
| * quota inodes and flags in secondary superblocks are never set by |
| * mkfs. However, they could be set in a secondary if a fs with quotas |
| * was growfs'ed since growfs copies the new primary into the |
| * secondaries. |
| * |
| * Also, the in-core inode flags now have different meaning to the |
| * on-disk flags, and so libxfs_sb_to_disk cannot directly write the |
| * sb_gquotino/sb_pquotino fields without specific sb_qflags being set. |
| * Hence we need to zero those fields directly in the sb buffer here. |
| */ |
| |
| if (sb->sb_inprogress == 1 && sb->sb_uquotino != NULLFSINO) { |
| if (!no_modify) |
| sb->sb_uquotino = 0; |
| if (!do_bzero) { |
| rval |= XR_AG_SB; |
| do_warn( |
| _("non-null user quota inode field in superblock %d\n"), |
| i); |
| |
| } else |
| rval |= XR_AG_SB_SEC; |
| } |
| |
| if (sb->sb_inprogress == 1 && sb->sb_gquotino != NULLFSINO) { |
| if (!no_modify) { |
| sb->sb_gquotino = 0; |
| dsb->sb_gquotino = 0; |
| } |
| if (!do_bzero) { |
| rval |= XR_AG_SB; |
| do_warn( |
| _("non-null group quota inode field in superblock %d\n"), |
| i); |
| |
| } else |
| rval |= XR_AG_SB_SEC; |
| } |
| |
| /* |
| * Note that sb_pquotino is not considered a valid sb field for pre-v5 |
| * superblocks. If it is anything other than 0 it is considered garbage |
| * data beyond the valid sb and explicitly zeroed above. |
| */ |
| if (xfs_has_pquotino(mp) && |
| sb->sb_inprogress == 1 && sb->sb_pquotino != NULLFSINO) { |
| if (!no_modify) { |
| sb->sb_pquotino = 0; |
| dsb->sb_pquotino = 0; |
| } |
| if (!do_bzero) { |
| rval |= XR_AG_SB; |
| do_warn( |
| _("non-null project quota inode field in superblock %d\n"), |
| i); |
| |
| } else |
| rval |= XR_AG_SB_SEC; |
| } |
| |
| if (sb->sb_inprogress == 1 && sb->sb_qflags) { |
| if (!no_modify) |
| sb->sb_qflags = 0; |
| if (!do_bzero) { |
| rval |= XR_AG_SB; |
| do_warn(_("non-null quota flags in superblock %d\n"), |
| i); |
| } else |
| rval |= XR_AG_SB_SEC; |
| } |
| |
| /* |
| * if the secondaries agree on a stripe unit/width or inode |
| * alignment, those fields ought to be valid since they are |
| * written at mkfs time (and the corresponding sb version bits |
| * are set). |
| */ |
| if (!xfs_sb_version_hasalign(sb) && sb->sb_inoalignmt != 0) { |
| if (!no_modify) |
| sb->sb_inoalignmt = 0; |
| if (!do_bzero) { |
| rval |= XR_AG_SB; |
| do_warn( |
| _("bad inode alignment field in superblock %d\n"), |
| i); |
| } else |
| rval |= XR_AG_SB_SEC; |
| } |
| |
| if (!xfs_sb_version_hasdalign(sb) && |
| (sb->sb_unit != 0 || sb->sb_width != 0)) { |
| if (!no_modify) |
| sb->sb_unit = sb->sb_width = 0; |
| if (!do_bzero) { |
| rval |= XR_AG_SB; |
| do_warn( |
| _("bad stripe unit/width fields in superblock %d\n"), |
| i); |
| } else |
| rval |= XR_AG_SB_SEC; |
| } |
| |
| if (!xfs_sb_version_hassector(sb) && |
| (sb->sb_sectsize != BBSIZE || sb->sb_sectlog != BBSHIFT || |
| sb->sb_logsectsize != 0 || sb->sb_logsectlog != 0)) { |
| if (!no_modify) { |
| sb->sb_sectsize = BBSIZE; |
| sb->sb_sectlog = BBSHIFT; |
| sb->sb_logsectsize = 0; |
| sb->sb_logsectlog = 0; |
| } |
| if (!do_bzero) { |
| rval |= XR_AG_SB; |
| do_warn( |
| _("bad log/data device sector size fields in superblock %d\n"), |
| i); |
| } else |
| rval |= XR_AG_SB_SEC; |
| } |
| |
| rval |= check_v5_feature_mismatch(mp, i, sb); |
| |
| if (xfs_sb_version_needsrepair(sb)) { |
| if (i == 0) { |
| if (!no_modify) |
| do_warn( |
| _("clearing needsrepair flag and regenerating metadata\n")); |
| else |
| do_warn( |
| _("would clear needsrepair flag and regenerate metadata\n")); |
| /* |
| * If needsrepair is set on the primary super, there's |
| * a possibility that repair crashed during an upgrade. |
| * Set features_changed to ensure that the secondary |
| * supers are rewritten with the new feature bits once |
| * we've finished the upgrade. |
| */ |
| features_changed = true; |
| } else { |
| /* |
| * Quietly clear needsrepair on the secondary supers as |
| * part of ensuring them. If needsrepair is set on the |
| * primary, it will be cleared at the end of repair |
| * once we've flushed all other dirty blocks to disk. |
| */ |
| sb->sb_features_incompat &= |
| ~XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR; |
| rval |= XR_AG_SB_SEC; |
| } |
| } |
| |
| return(rval); |
| } |
| |
| /* |
| * verify and reset the ag header if required. |
| * |
| * lower 4 bits of rval are set depending on what got modified. |
| * (see agheader.h for more details) |
| * |
| * NOTE -- this routine does not tell the user that it has |
| * altered things. Rather, it is up to the caller to do so |
| * using the bits encoded into the return value. |
| */ |
| |
| int |
| verify_set_agheader(xfs_mount_t *mp, struct xfs_buf *sbuf, xfs_sb_t *sb, |
| xfs_agf_t *agf, xfs_agi_t *agi, xfs_agnumber_t i) |
| { |
| int rval = 0; |
| int status = XR_OK; |
| int status_sb = XR_OK; |
| |
| status = verify_sb(sbuf->b_addr, sb, (i == 0)); |
| |
| if (status != XR_OK) { |
| do_warn(_("bad on-disk superblock %d - %s\n"), |
| i, err_string(status)); |
| } |
| |
| status_sb = compare_sb(mp, sb); |
| |
| if (status_sb != XR_OK) { |
| do_warn(_("primary/secondary superblock %d conflict - %s\n"), |
| i, err_string(status_sb)); |
| } |
| |
| if (status != XR_OK || status_sb != XR_OK) { |
| if (!no_modify) { |
| *sb = mp->m_sb; |
| |
| /* |
| * clear the more transient fields |
| */ |
| sb->sb_inprogress = 1; |
| |
| sb->sb_icount = 0; |
| sb->sb_ifree = 0; |
| sb->sb_fdblocks = 0; |
| sb->sb_frextents = 0; |
| |
| sb->sb_qflags = 0; |
| } |
| |
| rval |= XR_AG_SB; |
| } |
| |
| rval |= secondary_sb_whack(mp, sbuf, sb, i); |
| |
| rval |= verify_set_agf(mp, agf, i); |
| rval |= verify_set_agi(mp, agi, i); |
| |
| return(rval); |
| } |