| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (C) 2018-2023 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <djwong@kernel.org> |
| */ |
| #include "xfs.h" |
| #include "xfs_fs.h" |
| #include "xfs_shared.h" |
| #include "xfs_format.h" |
| #include "xfs_trans_resv.h" |
| #include "xfs_mount.h" |
| #include "xfs_defer.h" |
| #include "xfs_btree.h" |
| #include "xfs_bit.h" |
| #include "xfs_log_format.h" |
| #include "xfs_trans.h" |
| #include "xfs_sb.h" |
| #include "xfs_inode.h" |
| #include "xfs_icache.h" |
| #include "xfs_inode_buf.h" |
| #include "xfs_inode_fork.h" |
| #include "xfs_ialloc.h" |
| #include "xfs_da_format.h" |
| #include "xfs_reflink.h" |
| #include "xfs_alloc.h" |
| #include "xfs_rmap.h" |
| #include "xfs_rmap_btree.h" |
| #include "xfs_bmap.h" |
| #include "xfs_bmap_btree.h" |
| #include "xfs_bmap_util.h" |
| #include "xfs_dir2.h" |
| #include "xfs_dir2_priv.h" |
| #include "xfs_quota_defs.h" |
| #include "xfs_quota.h" |
| #include "xfs_ag.h" |
| #include "xfs_rtbitmap.h" |
| #include "xfs_attr_leaf.h" |
| #include "xfs_log_priv.h" |
| #include "xfs_health.h" |
| #include "scrub/xfs_scrub.h" |
| #include "scrub/scrub.h" |
| #include "scrub/common.h" |
| #include "scrub/btree.h" |
| #include "scrub/trace.h" |
| #include "scrub/repair.h" |
| |
| /* |
| * Inode Record Repair |
| * =================== |
| * |
| * Roughly speaking, inode problems can be classified based on whether or not |
| * they trip the dinode verifiers. If those trip, then we won't be able to |
| * xfs_iget ourselves the inode. |
| * |
| * Therefore, the xrep_dinode_* functions fix anything that will cause the |
| * inode buffer verifier or the dinode verifier. The xrep_inode_* functions |
| * fix things on live incore inodes. The inode repair functions make decisions |
| * with security and usability implications when reviving a file: |
| * |
| * - Files with zero di_mode or a garbage di_mode are converted to regular file |
| * that only root can read. This file may not actually contain user data, |
| * if the file was not previously a regular file. Setuid and setgid bits |
| * are cleared. |
| * |
| * - Zero-size directories can be truncated to look empty. It is necessary to |
| * run the bmapbtd and directory repair functions to fully rebuild the |
| * directory. |
| * |
| * - Zero-size symbolic link targets can be truncated to '?'. It is necessary |
| * to run the bmapbtd and symlink repair functions to salvage the symlink. |
| * |
| * - Invalid extent size hints will be removed. |
| * |
| * - Quotacheck will be scheduled if we repaired an inode that was so badly |
| * damaged that the ondisk inode had to be rebuilt. |
| * |
| * - Invalid user, group, or project IDs (aka -1U) will be reset to zero. |
| * Setuid and setgid bits are cleared. |
| * |
| * - Data and attr forks are reset to extents format with zero extents if the |
| * fork data is inconsistent. It is necessary to run the bmapbtd or bmapbta |
| * repair functions to recover the space mapping. |
| * |
| * - ACLs will not be recovered if the attr fork is zapped or the extended |
| * attribute structure itself requires salvaging. |
| * |
| * - If the attr fork is zapped, the user and group ids are reset to root and |
| * the setuid and setgid bits are removed. |
| */ |
| |
| /* |
| * All the information we need to repair the ondisk inode if we can't iget the |
| * incore inode. We don't allocate this buffer unless we're going to perform |
| * a repair to the ondisk inode cluster buffer. |
| */ |
| struct xrep_inode { |
| /* Inode mapping that we saved from the initial lookup attempt. */ |
| struct xfs_imap imap; |
| |
| struct xfs_scrub *sc; |
| |
| /* Blocks in use on the data device by data extents or bmbt blocks. */ |
| xfs_rfsblock_t data_blocks; |
| |
| /* Blocks in use on the rt device. */ |
| xfs_rfsblock_t rt_blocks; |
| |
| /* Blocks in use by the attr fork. */ |
| xfs_rfsblock_t attr_blocks; |
| |
| /* Number of data device extents for the data fork. */ |
| xfs_extnum_t data_extents; |
| |
| /* |
| * Number of realtime device extents for the data fork. If |
| * data_extents and rt_extents indicate that the data fork has extents |
| * on both devices, we'll just back away slowly. |
| */ |
| xfs_extnum_t rt_extents; |
| |
| /* Number of (data device) extents for the attr fork. */ |
| xfs_aextnum_t attr_extents; |
| |
| /* Sick state to set after zapping parts of the inode. */ |
| unsigned int ino_sick_mask; |
| |
| /* Must we remove all access from this file? */ |
| bool zap_acls; |
| }; |
| |
| /* |
| * Setup function for inode repair. @imap contains the ondisk inode mapping |
| * information so that we can correct the ondisk inode cluster buffer if |
| * necessary to make iget work. |
| */ |
| int |
| xrep_setup_inode( |
| struct xfs_scrub *sc, |
| const struct xfs_imap *imap) |
| { |
| struct xrep_inode *ri; |
| |
| sc->buf = kzalloc(sizeof(struct xrep_inode), XCHK_GFP_FLAGS); |
| if (!sc->buf) |
| return -ENOMEM; |
| |
| ri = sc->buf; |
| memcpy(&ri->imap, imap, sizeof(struct xfs_imap)); |
| ri->sc = sc; |
| return 0; |
| } |
| |
| /* |
| * Make sure this ondisk inode can pass the inode buffer verifier. This is |
| * not the same as the dinode verifier. |
| */ |
| STATIC void |
| xrep_dinode_buf_core( |
| struct xfs_scrub *sc, |
| struct xfs_buf *bp, |
| unsigned int ioffset) |
| { |
| struct xfs_dinode *dip = xfs_buf_offset(bp, ioffset); |
| struct xfs_trans *tp = sc->tp; |
| struct xfs_mount *mp = sc->mp; |
| xfs_agino_t agino; |
| bool crc_ok = false; |
| bool magic_ok = false; |
| bool unlinked_ok = false; |
| |
| agino = be32_to_cpu(dip->di_next_unlinked); |
| |
| if (xfs_verify_agino_or_null(bp->b_pag, agino)) |
| unlinked_ok = true; |
| |
| if (dip->di_magic == cpu_to_be16(XFS_DINODE_MAGIC) && |
| xfs_dinode_good_version(mp, dip->di_version)) |
| magic_ok = true; |
| |
| if (xfs_verify_cksum((char *)dip, mp->m_sb.sb_inodesize, |
| XFS_DINODE_CRC_OFF)) |
| crc_ok = true; |
| |
| if (magic_ok && unlinked_ok && crc_ok) |
| return; |
| |
| if (!magic_ok) { |
| dip->di_magic = cpu_to_be16(XFS_DINODE_MAGIC); |
| dip->di_version = 3; |
| } |
| if (!unlinked_ok) |
| dip->di_next_unlinked = cpu_to_be32(NULLAGINO); |
| xfs_dinode_calc_crc(mp, dip); |
| xfs_trans_buf_set_type(tp, bp, XFS_BLFT_DINO_BUF); |
| xfs_trans_log_buf(tp, bp, ioffset, |
| ioffset + sizeof(struct xfs_dinode) - 1); |
| } |
| |
| /* Make sure this inode cluster buffer can pass the inode buffer verifier. */ |
| STATIC void |
| xrep_dinode_buf( |
| struct xfs_scrub *sc, |
| struct xfs_buf *bp) |
| { |
| struct xfs_mount *mp = sc->mp; |
| int i; |
| int ni; |
| |
| ni = XFS_BB_TO_FSB(mp, bp->b_length) * mp->m_sb.sb_inopblock; |
| for (i = 0; i < ni; i++) |
| xrep_dinode_buf_core(sc, bp, i << mp->m_sb.sb_inodelog); |
| } |
| |
| /* Reinitialize things that never change in an inode. */ |
| STATIC void |
| xrep_dinode_header( |
| struct xfs_scrub *sc, |
| struct xfs_dinode *dip) |
| { |
| trace_xrep_dinode_header(sc, dip); |
| |
| dip->di_magic = cpu_to_be16(XFS_DINODE_MAGIC); |
| if (!xfs_dinode_good_version(sc->mp, dip->di_version)) |
| dip->di_version = 3; |
| dip->di_ino = cpu_to_be64(sc->sm->sm_ino); |
| uuid_copy(&dip->di_uuid, &sc->mp->m_sb.sb_meta_uuid); |
| dip->di_gen = cpu_to_be32(sc->sm->sm_gen); |
| } |
| |
| /* Turn di_mode into /something/ recognizable. */ |
| STATIC void |
| xrep_dinode_mode( |
| struct xrep_inode *ri, |
| struct xfs_dinode *dip) |
| { |
| struct xfs_scrub *sc = ri->sc; |
| uint16_t mode = be16_to_cpu(dip->di_mode); |
| |
| trace_xrep_dinode_mode(sc, dip); |
| |
| if (mode == 0 || xfs_mode_to_ftype(mode) != XFS_DIR3_FT_UNKNOWN) |
| return; |
| |
| /* bad mode, so we set it to a file that only root can read */ |
| mode = S_IFREG; |
| dip->di_mode = cpu_to_be16(mode); |
| dip->di_uid = 0; |
| dip->di_gid = 0; |
| ri->zap_acls = true; |
| } |
| |
| /* Fix any conflicting flags that the verifiers complain about. */ |
| STATIC void |
| xrep_dinode_flags( |
| struct xfs_scrub *sc, |
| struct xfs_dinode *dip, |
| bool isrt) |
| { |
| struct xfs_mount *mp = sc->mp; |
| uint64_t flags2 = be64_to_cpu(dip->di_flags2); |
| uint16_t flags = be16_to_cpu(dip->di_flags); |
| uint16_t mode = be16_to_cpu(dip->di_mode); |
| |
| trace_xrep_dinode_flags(sc, dip); |
| |
| if (isrt) |
| flags |= XFS_DIFLAG_REALTIME; |
| else |
| flags &= ~XFS_DIFLAG_REALTIME; |
| |
| /* |
| * For regular files on a reflink filesystem, set the REFLINK flag to |
| * protect shared extents. A later stage will actually check those |
| * extents and clear the flag if possible. |
| */ |
| if (xfs_has_reflink(mp) && S_ISREG(mode)) |
| flags2 |= XFS_DIFLAG2_REFLINK; |
| else |
| flags2 &= ~(XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE); |
| if (flags & XFS_DIFLAG_REALTIME) |
| flags2 &= ~XFS_DIFLAG2_REFLINK; |
| if (!xfs_has_bigtime(mp)) |
| flags2 &= ~XFS_DIFLAG2_BIGTIME; |
| if (!xfs_has_large_extent_counts(mp)) |
| flags2 &= ~XFS_DIFLAG2_NREXT64; |
| if (flags2 & XFS_DIFLAG2_NREXT64) |
| dip->di_nrext64_pad = 0; |
| else if (dip->di_version >= 3) |
| dip->di_v3_pad = 0; |
| dip->di_flags = cpu_to_be16(flags); |
| dip->di_flags2 = cpu_to_be64(flags2); |
| } |
| |
| /* |
| * Blow out symlink; now it points nowhere. We don't have to worry about |
| * incore state because this inode is failing the verifiers. |
| */ |
| STATIC void |
| xrep_dinode_zap_symlink( |
| struct xrep_inode *ri, |
| struct xfs_dinode *dip) |
| { |
| struct xfs_scrub *sc = ri->sc; |
| char *p; |
| |
| trace_xrep_dinode_zap_symlink(sc, dip); |
| |
| dip->di_format = XFS_DINODE_FMT_LOCAL; |
| dip->di_size = cpu_to_be64(1); |
| p = XFS_DFORK_PTR(dip, XFS_DATA_FORK); |
| *p = '?'; |
| ri->ino_sick_mask |= XFS_SICK_INO_SYMLINK_ZAPPED; |
| } |
| |
| /* |
| * Blow out dir, make the parent point to the root. In the future repair will |
| * reconstruct this directory for us. Note that there's no in-core directory |
| * inode because the sf verifier tripped, so we don't have to worry about the |
| * dentry cache. |
| */ |
| STATIC void |
| xrep_dinode_zap_dir( |
| struct xrep_inode *ri, |
| struct xfs_dinode *dip) |
| { |
| struct xfs_scrub *sc = ri->sc; |
| struct xfs_mount *mp = sc->mp; |
| struct xfs_dir2_sf_hdr *sfp; |
| int i8count; |
| |
| trace_xrep_dinode_zap_dir(sc, dip); |
| |
| dip->di_format = XFS_DINODE_FMT_LOCAL; |
| i8count = mp->m_sb.sb_rootino > XFS_DIR2_MAX_SHORT_INUM; |
| sfp = XFS_DFORK_PTR(dip, XFS_DATA_FORK); |
| sfp->count = 0; |
| sfp->i8count = i8count; |
| xfs_dir2_sf_put_parent_ino(sfp, mp->m_sb.sb_rootino); |
| dip->di_size = cpu_to_be64(xfs_dir2_sf_hdr_size(i8count)); |
| ri->ino_sick_mask |= XFS_SICK_INO_DIR_ZAPPED; |
| } |
| |
| /* Make sure we don't have a garbage file size. */ |
| STATIC void |
| xrep_dinode_size( |
| struct xrep_inode *ri, |
| struct xfs_dinode *dip) |
| { |
| struct xfs_scrub *sc = ri->sc; |
| uint64_t size = be64_to_cpu(dip->di_size); |
| uint16_t mode = be16_to_cpu(dip->di_mode); |
| |
| trace_xrep_dinode_size(sc, dip); |
| |
| switch (mode & S_IFMT) { |
| case S_IFIFO: |
| case S_IFCHR: |
| case S_IFBLK: |
| case S_IFSOCK: |
| /* di_size can't be nonzero for special files */ |
| dip->di_size = 0; |
| break; |
| case S_IFREG: |
| /* Regular files can't be larger than 2^63-1 bytes. */ |
| dip->di_size = cpu_to_be64(size & ~(1ULL << 63)); |
| break; |
| case S_IFLNK: |
| /* |
| * Truncate ridiculously oversized symlinks. If the size is |
| * zero, reset it to point to the current directory. Both of |
| * these conditions trigger dinode verifier errors, so there |
| * is no in-core state to reset. |
| */ |
| if (size > XFS_SYMLINK_MAXLEN) |
| dip->di_size = cpu_to_be64(XFS_SYMLINK_MAXLEN); |
| else if (size == 0) |
| xrep_dinode_zap_symlink(ri, dip); |
| break; |
| case S_IFDIR: |
| /* |
| * Directories can't have a size larger than 32G. If the size |
| * is zero, reset it to an empty directory. Both of these |
| * conditions trigger dinode verifier errors, so there is no |
| * in-core state to reset. |
| */ |
| if (size > XFS_DIR2_SPACE_SIZE) |
| dip->di_size = cpu_to_be64(XFS_DIR2_SPACE_SIZE); |
| else if (size == 0) |
| xrep_dinode_zap_dir(ri, dip); |
| break; |
| } |
| } |
| |
| /* Fix extent size hints. */ |
| STATIC void |
| xrep_dinode_extsize_hints( |
| struct xfs_scrub *sc, |
| struct xfs_dinode *dip) |
| { |
| struct xfs_mount *mp = sc->mp; |
| uint64_t flags2 = be64_to_cpu(dip->di_flags2); |
| uint16_t flags = be16_to_cpu(dip->di_flags); |
| uint16_t mode = be16_to_cpu(dip->di_mode); |
| |
| xfs_failaddr_t fa; |
| |
| trace_xrep_dinode_extsize_hints(sc, dip); |
| |
| fa = xfs_inode_validate_extsize(mp, be32_to_cpu(dip->di_extsize), |
| mode, flags); |
| if (fa) { |
| dip->di_extsize = 0; |
| dip->di_flags &= ~cpu_to_be16(XFS_DIFLAG_EXTSIZE | |
| XFS_DIFLAG_EXTSZINHERIT); |
| } |
| |
| if (dip->di_version < 3) |
| return; |
| |
| fa = xfs_inode_validate_cowextsize(mp, be32_to_cpu(dip->di_cowextsize), |
| mode, flags, flags2); |
| if (fa) { |
| dip->di_cowextsize = 0; |
| dip->di_flags2 &= ~cpu_to_be64(XFS_DIFLAG2_COWEXTSIZE); |
| } |
| } |
| |
| /* Count extents and blocks for an inode given an rmap. */ |
| STATIC int |
| xrep_dinode_walk_rmap( |
| struct xfs_btree_cur *cur, |
| const struct xfs_rmap_irec *rec, |
| void *priv) |
| { |
| struct xrep_inode *ri = priv; |
| int error = 0; |
| |
| if (xchk_should_terminate(ri->sc, &error)) |
| return error; |
| |
| /* We only care about this inode. */ |
| if (rec->rm_owner != ri->sc->sm->sm_ino) |
| return 0; |
| |
| if (rec->rm_flags & XFS_RMAP_ATTR_FORK) { |
| ri->attr_blocks += rec->rm_blockcount; |
| if (!(rec->rm_flags & XFS_RMAP_BMBT_BLOCK)) |
| ri->attr_extents++; |
| |
| return 0; |
| } |
| |
| ri->data_blocks += rec->rm_blockcount; |
| if (!(rec->rm_flags & XFS_RMAP_BMBT_BLOCK)) |
| ri->data_extents++; |
| |
| return 0; |
| } |
| |
| /* Count extents and blocks for an inode from all AG rmap data. */ |
| STATIC int |
| xrep_dinode_count_ag_rmaps( |
| struct xrep_inode *ri, |
| struct xfs_perag *pag) |
| { |
| struct xfs_btree_cur *cur; |
| struct xfs_buf *agf; |
| int error; |
| |
| error = xfs_alloc_read_agf(pag, ri->sc->tp, 0, &agf); |
| if (error) |
| return error; |
| |
| cur = xfs_rmapbt_init_cursor(ri->sc->mp, ri->sc->tp, agf, pag); |
| error = xfs_rmap_query_all(cur, xrep_dinode_walk_rmap, ri); |
| xfs_btree_del_cursor(cur, error); |
| xfs_trans_brelse(ri->sc->tp, agf); |
| return error; |
| } |
| |
| /* Count extents and blocks for a given inode from all rmap data. */ |
| STATIC int |
| xrep_dinode_count_rmaps( |
| struct xrep_inode *ri) |
| { |
| struct xfs_perag *pag; |
| xfs_agnumber_t agno; |
| int error; |
| |
| if (!xfs_has_rmapbt(ri->sc->mp) || xfs_has_realtime(ri->sc->mp)) |
| return -EOPNOTSUPP; |
| |
| for_each_perag(ri->sc->mp, agno, pag) { |
| error = xrep_dinode_count_ag_rmaps(ri, pag); |
| if (error) { |
| xfs_perag_rele(pag); |
| return error; |
| } |
| } |
| |
| /* Can't have extents on both the rt and the data device. */ |
| if (ri->data_extents && ri->rt_extents) |
| return -EFSCORRUPTED; |
| |
| trace_xrep_dinode_count_rmaps(ri->sc, |
| ri->data_blocks, ri->rt_blocks, ri->attr_blocks, |
| ri->data_extents, ri->rt_extents, ri->attr_extents); |
| return 0; |
| } |
| |
| /* Return true if this extents-format ifork looks like garbage. */ |
| STATIC bool |
| xrep_dinode_bad_extents_fork( |
| struct xfs_scrub *sc, |
| struct xfs_dinode *dip, |
| unsigned int dfork_size, |
| int whichfork) |
| { |
| struct xfs_bmbt_irec new; |
| struct xfs_bmbt_rec *dp; |
| xfs_extnum_t nex; |
| bool isrt; |
| unsigned int i; |
| |
| nex = xfs_dfork_nextents(dip, whichfork); |
| if (nex > dfork_size / sizeof(struct xfs_bmbt_rec)) |
| return true; |
| |
| dp = XFS_DFORK_PTR(dip, whichfork); |
| |
| isrt = dip->di_flags & cpu_to_be16(XFS_DIFLAG_REALTIME); |
| for (i = 0; i < nex; i++, dp++) { |
| xfs_failaddr_t fa; |
| |
| xfs_bmbt_disk_get_all(dp, &new); |
| fa = xfs_bmap_validate_extent_raw(sc->mp, isrt, whichfork, |
| &new); |
| if (fa) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Return true if this btree-format ifork looks like garbage. */ |
| STATIC bool |
| xrep_dinode_bad_bmbt_fork( |
| struct xfs_scrub *sc, |
| struct xfs_dinode *dip, |
| unsigned int dfork_size, |
| int whichfork) |
| { |
| struct xfs_bmdr_block *dfp; |
| xfs_extnum_t nex; |
| unsigned int i; |
| unsigned int dmxr; |
| unsigned int nrecs; |
| unsigned int level; |
| |
| nex = xfs_dfork_nextents(dip, whichfork); |
| if (nex <= dfork_size / sizeof(struct xfs_bmbt_rec)) |
| return true; |
| |
| if (dfork_size < sizeof(struct xfs_bmdr_block)) |
| return true; |
| |
| dfp = XFS_DFORK_PTR(dip, whichfork); |
| nrecs = be16_to_cpu(dfp->bb_numrecs); |
| level = be16_to_cpu(dfp->bb_level); |
| |
| if (nrecs == 0 || XFS_BMDR_SPACE_CALC(nrecs) > dfork_size) |
| return true; |
| if (level == 0 || level >= XFS_BM_MAXLEVELS(sc->mp, whichfork)) |
| return true; |
| |
| dmxr = xfs_bmdr_maxrecs(dfork_size, 0); |
| for (i = 1; i <= nrecs; i++) { |
| struct xfs_bmbt_key *fkp; |
| xfs_bmbt_ptr_t *fpp; |
| xfs_fileoff_t fileoff; |
| xfs_fsblock_t fsbno; |
| |
| fkp = XFS_BMDR_KEY_ADDR(dfp, i); |
| fileoff = be64_to_cpu(fkp->br_startoff); |
| if (!xfs_verify_fileoff(sc->mp, fileoff)) |
| return true; |
| |
| fpp = XFS_BMDR_PTR_ADDR(dfp, i, dmxr); |
| fsbno = be64_to_cpu(*fpp); |
| if (!xfs_verify_fsbno(sc->mp, fsbno)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * Check the data fork for things that will fail the ifork verifiers or the |
| * ifork formatters. |
| */ |
| STATIC bool |
| xrep_dinode_check_dfork( |
| struct xfs_scrub *sc, |
| struct xfs_dinode *dip, |
| uint16_t mode) |
| { |
| void *dfork_ptr; |
| int64_t data_size; |
| unsigned int fmt; |
| unsigned int dfork_size; |
| |
| /* |
| * Verifier functions take signed int64_t, so check for bogus negative |
| * values first. |
| */ |
| data_size = be64_to_cpu(dip->di_size); |
| if (data_size < 0) |
| return true; |
| |
| fmt = XFS_DFORK_FORMAT(dip, XFS_DATA_FORK); |
| switch (mode & S_IFMT) { |
| case S_IFIFO: |
| case S_IFCHR: |
| case S_IFBLK: |
| case S_IFSOCK: |
| if (fmt != XFS_DINODE_FMT_DEV) |
| return true; |
| break; |
| case S_IFREG: |
| if (fmt == XFS_DINODE_FMT_LOCAL) |
| return true; |
| fallthrough; |
| case S_IFLNK: |
| case S_IFDIR: |
| switch (fmt) { |
| case XFS_DINODE_FMT_LOCAL: |
| case XFS_DINODE_FMT_EXTENTS: |
| case XFS_DINODE_FMT_BTREE: |
| break; |
| default: |
| return true; |
| } |
| break; |
| default: |
| return true; |
| } |
| |
| dfork_size = XFS_DFORK_SIZE(dip, sc->mp, XFS_DATA_FORK); |
| dfork_ptr = XFS_DFORK_PTR(dip, XFS_DATA_FORK); |
| |
| switch (fmt) { |
| case XFS_DINODE_FMT_DEV: |
| break; |
| case XFS_DINODE_FMT_LOCAL: |
| /* dir/symlink structure cannot be larger than the fork */ |
| if (data_size > dfork_size) |
| return true; |
| /* directory structure must pass verification. */ |
| if (S_ISDIR(mode) && |
| xfs_dir2_sf_verify(sc->mp, dfork_ptr, data_size) != NULL) |
| return true; |
| /* symlink structure must pass verification. */ |
| if (S_ISLNK(mode) && |
| xfs_symlink_shortform_verify(dfork_ptr, data_size) != NULL) |
| return true; |
| break; |
| case XFS_DINODE_FMT_EXTENTS: |
| if (xrep_dinode_bad_extents_fork(sc, dip, dfork_size, |
| XFS_DATA_FORK)) |
| return true; |
| break; |
| case XFS_DINODE_FMT_BTREE: |
| if (xrep_dinode_bad_bmbt_fork(sc, dip, dfork_size, |
| XFS_DATA_FORK)) |
| return true; |
| break; |
| default: |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void |
| xrep_dinode_set_data_nextents( |
| struct xfs_dinode *dip, |
| xfs_extnum_t nextents) |
| { |
| if (xfs_dinode_has_large_extent_counts(dip)) |
| dip->di_big_nextents = cpu_to_be64(nextents); |
| else |
| dip->di_nextents = cpu_to_be32(nextents); |
| } |
| |
| static void |
| xrep_dinode_set_attr_nextents( |
| struct xfs_dinode *dip, |
| xfs_extnum_t nextents) |
| { |
| if (xfs_dinode_has_large_extent_counts(dip)) |
| dip->di_big_anextents = cpu_to_be32(nextents); |
| else |
| dip->di_anextents = cpu_to_be16(nextents); |
| } |
| |
| /* Reset the data fork to something sane. */ |
| STATIC void |
| xrep_dinode_zap_dfork( |
| struct xrep_inode *ri, |
| struct xfs_dinode *dip, |
| uint16_t mode) |
| { |
| struct xfs_scrub *sc = ri->sc; |
| |
| trace_xrep_dinode_zap_dfork(sc, dip); |
| |
| ri->ino_sick_mask |= XFS_SICK_INO_BMBTD_ZAPPED; |
| |
| xrep_dinode_set_data_nextents(dip, 0); |
| ri->data_blocks = 0; |
| ri->rt_blocks = 0; |
| |
| /* Special files always get reset to DEV */ |
| switch (mode & S_IFMT) { |
| case S_IFIFO: |
| case S_IFCHR: |
| case S_IFBLK: |
| case S_IFSOCK: |
| dip->di_format = XFS_DINODE_FMT_DEV; |
| dip->di_size = 0; |
| return; |
| } |
| |
| /* |
| * If we have data extents, reset to an empty map and hope the user |
| * will run the bmapbtd checker next. |
| */ |
| if (ri->data_extents || ri->rt_extents || S_ISREG(mode)) { |
| dip->di_format = XFS_DINODE_FMT_EXTENTS; |
| return; |
| } |
| |
| /* Otherwise, reset the local format to the minimum. */ |
| switch (mode & S_IFMT) { |
| case S_IFLNK: |
| xrep_dinode_zap_symlink(ri, dip); |
| break; |
| case S_IFDIR: |
| xrep_dinode_zap_dir(ri, dip); |
| break; |
| } |
| } |
| |
| /* |
| * Check the attr fork for things that will fail the ifork verifiers or the |
| * ifork formatters. |
| */ |
| STATIC bool |
| xrep_dinode_check_afork( |
| struct xfs_scrub *sc, |
| struct xfs_dinode *dip) |
| { |
| struct xfs_attr_sf_hdr *afork_ptr; |
| size_t attr_size; |
| unsigned int afork_size; |
| |
| if (XFS_DFORK_BOFF(dip) == 0) |
| return dip->di_aformat != XFS_DINODE_FMT_EXTENTS || |
| xfs_dfork_attr_extents(dip) != 0; |
| |
| afork_size = XFS_DFORK_SIZE(dip, sc->mp, XFS_ATTR_FORK); |
| afork_ptr = XFS_DFORK_PTR(dip, XFS_ATTR_FORK); |
| |
| switch (XFS_DFORK_FORMAT(dip, XFS_ATTR_FORK)) { |
| case XFS_DINODE_FMT_LOCAL: |
| /* Fork has to be large enough to extract the xattr size. */ |
| if (afork_size < sizeof(struct xfs_attr_sf_hdr)) |
| return true; |
| |
| /* xattr structure cannot be larger than the fork */ |
| attr_size = be16_to_cpu(afork_ptr->totsize); |
| if (attr_size > afork_size) |
| return true; |
| |
| /* xattr structure must pass verification. */ |
| return xfs_attr_shortform_verify(afork_ptr, attr_size) != NULL; |
| case XFS_DINODE_FMT_EXTENTS: |
| if (xrep_dinode_bad_extents_fork(sc, dip, afork_size, |
| XFS_ATTR_FORK)) |
| return true; |
| break; |
| case XFS_DINODE_FMT_BTREE: |
| if (xrep_dinode_bad_bmbt_fork(sc, dip, afork_size, |
| XFS_ATTR_FORK)) |
| return true; |
| break; |
| default: |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * Reset the attr fork to empty. Since the attr fork could have contained |
| * ACLs, make the file readable only by root. |
| */ |
| STATIC void |
| xrep_dinode_zap_afork( |
| struct xrep_inode *ri, |
| struct xfs_dinode *dip, |
| uint16_t mode) |
| { |
| struct xfs_scrub *sc = ri->sc; |
| |
| trace_xrep_dinode_zap_afork(sc, dip); |
| |
| ri->ino_sick_mask |= XFS_SICK_INO_BMBTA_ZAPPED; |
| |
| dip->di_aformat = XFS_DINODE_FMT_EXTENTS; |
| xrep_dinode_set_attr_nextents(dip, 0); |
| ri->attr_blocks = 0; |
| |
| /* |
| * If the data fork is in btree format, removing the attr fork entirely |
| * might cause verifier failures if the next level down in the bmbt |
| * could now fit in the data fork area. |
| */ |
| if (dip->di_format != XFS_DINODE_FMT_BTREE) |
| dip->di_forkoff = 0; |
| dip->di_mode = cpu_to_be16(mode & ~0777); |
| dip->di_uid = 0; |
| dip->di_gid = 0; |
| } |
| |
| /* Make sure the fork offset is a sensible value. */ |
| STATIC void |
| xrep_dinode_ensure_forkoff( |
| struct xrep_inode *ri, |
| struct xfs_dinode *dip, |
| uint16_t mode) |
| { |
| struct xfs_bmdr_block *bmdr; |
| struct xfs_scrub *sc = ri->sc; |
| xfs_extnum_t attr_extents, data_extents; |
| size_t bmdr_minsz = XFS_BMDR_SPACE_CALC(1); |
| unsigned int lit_sz = XFS_LITINO(sc->mp); |
| unsigned int afork_min, dfork_min; |
| |
| trace_xrep_dinode_ensure_forkoff(sc, dip); |
| |
| /* |
| * Before calling this function, xrep_dinode_core ensured that both |
| * forks actually fit inside their respective literal areas. If this |
| * was not the case, the fork was reset to FMT_EXTENTS with zero |
| * records. If the rmapbt scan found attr or data fork blocks, this |
| * will be noted in the dinode_stats, and we must leave enough room |
| * for the bmap repair code to reconstruct the mapping structure. |
| * |
| * First, compute the minimum space required for the attr fork. |
| */ |
| switch (dip->di_aformat) { |
| case XFS_DINODE_FMT_LOCAL: |
| /* |
| * If we still have a shortform xattr structure at all, that |
| * means the attr fork area was exactly large enough to fit |
| * the sf structure. |
| */ |
| afork_min = XFS_DFORK_SIZE(dip, sc->mp, XFS_ATTR_FORK); |
| break; |
| case XFS_DINODE_FMT_EXTENTS: |
| attr_extents = xfs_dfork_attr_extents(dip); |
| if (attr_extents) { |
| /* |
| * We must maintain sufficient space to hold the entire |
| * extent map array in the data fork. Note that we |
| * previously zapped the fork if it had no chance of |
| * fitting in the inode. |
| */ |
| afork_min = sizeof(struct xfs_bmbt_rec) * attr_extents; |
| } else if (ri->attr_extents > 0) { |
| /* |
| * The attr fork thinks it has zero extents, but we |
| * found some xattr extents. We need to leave enough |
| * empty space here so that the incore attr fork will |
| * get created (and hence trigger the attr fork bmap |
| * repairer). |
| */ |
| afork_min = bmdr_minsz; |
| } else { |
| /* No extents on disk or found in rmapbt. */ |
| afork_min = 0; |
| } |
| break; |
| case XFS_DINODE_FMT_BTREE: |
| /* Must have space for btree header and key/pointers. */ |
| bmdr = XFS_DFORK_PTR(dip, XFS_ATTR_FORK); |
| afork_min = XFS_BMAP_BROOT_SPACE(sc->mp, bmdr); |
| break; |
| default: |
| /* We should never see any other formats. */ |
| afork_min = 0; |
| break; |
| } |
| |
| /* Compute the minimum space required for the data fork. */ |
| switch (dip->di_format) { |
| case XFS_DINODE_FMT_DEV: |
| dfork_min = sizeof(__be32); |
| break; |
| case XFS_DINODE_FMT_UUID: |
| dfork_min = sizeof(uuid_t); |
| break; |
| case XFS_DINODE_FMT_LOCAL: |
| /* |
| * If we still have a shortform data fork at all, that means |
| * the data fork area was large enough to fit whatever was in |
| * there. |
| */ |
| dfork_min = be64_to_cpu(dip->di_size); |
| break; |
| case XFS_DINODE_FMT_EXTENTS: |
| data_extents = xfs_dfork_data_extents(dip); |
| if (data_extents) { |
| /* |
| * We must maintain sufficient space to hold the entire |
| * extent map array in the data fork. Note that we |
| * previously zapped the fork if it had no chance of |
| * fitting in the inode. |
| */ |
| dfork_min = sizeof(struct xfs_bmbt_rec) * data_extents; |
| } else if (ri->data_extents > 0 || ri->rt_extents > 0) { |
| /* |
| * The data fork thinks it has zero extents, but we |
| * found some data extents. We need to leave enough |
| * empty space here so that the data fork bmap repair |
| * will recover the mappings. |
| */ |
| dfork_min = bmdr_minsz; |
| } else { |
| /* No extents on disk or found in rmapbt. */ |
| dfork_min = 0; |
| } |
| break; |
| case XFS_DINODE_FMT_BTREE: |
| /* Must have space for btree header and key/pointers. */ |
| bmdr = XFS_DFORK_PTR(dip, XFS_DATA_FORK); |
| dfork_min = XFS_BMAP_BROOT_SPACE(sc->mp, bmdr); |
| break; |
| default: |
| dfork_min = 0; |
| break; |
| } |
| |
| /* |
| * Round all values up to the nearest 8 bytes, because that is the |
| * precision of di_forkoff. |
| */ |
| afork_min = roundup(afork_min, 8); |
| dfork_min = roundup(dfork_min, 8); |
| bmdr_minsz = roundup(bmdr_minsz, 8); |
| |
| ASSERT(dfork_min <= lit_sz); |
| ASSERT(afork_min <= lit_sz); |
| |
| /* |
| * If the data fork was zapped and we don't have enough space for the |
| * recovery fork, move the attr fork up. |
| */ |
| if (dip->di_format == XFS_DINODE_FMT_EXTENTS && |
| xfs_dfork_data_extents(dip) == 0 && |
| (ri->data_extents > 0 || ri->rt_extents > 0) && |
| bmdr_minsz > XFS_DFORK_DSIZE(dip, sc->mp)) { |
| if (bmdr_minsz + afork_min > lit_sz) { |
| /* |
| * The attr for and the stub fork we need to recover |
| * the data fork won't both fit. Zap the attr fork. |
| */ |
| xrep_dinode_zap_afork(ri, dip, mode); |
| afork_min = bmdr_minsz; |
| } else { |
| void *before, *after; |
| |
| /* Otherwise, just slide the attr fork up. */ |
| before = XFS_DFORK_APTR(dip); |
| dip->di_forkoff = bmdr_minsz >> 3; |
| after = XFS_DFORK_APTR(dip); |
| memmove(after, before, XFS_DFORK_ASIZE(dip, sc->mp)); |
| } |
| } |
| |
| /* |
| * If the attr fork was zapped and we don't have enough space for the |
| * recovery fork, move the attr fork down. |
| */ |
| if (dip->di_aformat == XFS_DINODE_FMT_EXTENTS && |
| xfs_dfork_attr_extents(dip) == 0 && |
| ri->attr_extents > 0 && |
| bmdr_minsz > XFS_DFORK_ASIZE(dip, sc->mp)) { |
| if (dip->di_format == XFS_DINODE_FMT_BTREE) { |
| /* |
| * If the data fork is in btree format then we can't |
| * adjust forkoff because that runs the risk of |
| * violating the extents/btree format transition rules. |
| */ |
| } else if (bmdr_minsz + dfork_min > lit_sz) { |
| /* |
| * If we can't move the attr fork, too bad, we lose the |
| * attr fork and leak its blocks. |
| */ |
| xrep_dinode_zap_afork(ri, dip, mode); |
| } else { |
| /* |
| * Otherwise, just slide the attr fork down. The attr |
| * fork is empty, so we don't have any old contents to |
| * move here. |
| */ |
| dip->di_forkoff = (lit_sz - bmdr_minsz) >> 3; |
| } |
| } |
| } |
| |
| /* |
| * Zap the data/attr forks if we spot anything that isn't going to pass the |
| * ifork verifiers or the ifork formatters, because we need to get the inode |
| * into good enough shape that the higher level repair functions can run. |
| */ |
| STATIC void |
| xrep_dinode_zap_forks( |
| struct xrep_inode *ri, |
| struct xfs_dinode *dip) |
| { |
| struct xfs_scrub *sc = ri->sc; |
| xfs_extnum_t data_extents; |
| xfs_extnum_t attr_extents; |
| xfs_filblks_t nblocks; |
| uint16_t mode; |
| bool zap_datafork = false; |
| bool zap_attrfork = ri->zap_acls; |
| |
| trace_xrep_dinode_zap_forks(sc, dip); |
| |
| mode = be16_to_cpu(dip->di_mode); |
| |
| data_extents = xfs_dfork_data_extents(dip); |
| attr_extents = xfs_dfork_attr_extents(dip); |
| nblocks = be64_to_cpu(dip->di_nblocks); |
| |
| /* Inode counters don't make sense? */ |
| if (data_extents > nblocks) |
| zap_datafork = true; |
| if (attr_extents > nblocks) |
| zap_attrfork = true; |
| if (data_extents + attr_extents > nblocks) |
| zap_datafork = zap_attrfork = true; |
| |
| if (!zap_datafork) |
| zap_datafork = xrep_dinode_check_dfork(sc, dip, mode); |
| if (!zap_attrfork) |
| zap_attrfork = xrep_dinode_check_afork(sc, dip); |
| |
| /* Zap whatever's bad. */ |
| if (zap_attrfork) |
| xrep_dinode_zap_afork(ri, dip, mode); |
| if (zap_datafork) |
| xrep_dinode_zap_dfork(ri, dip, mode); |
| xrep_dinode_ensure_forkoff(ri, dip, mode); |
| |
| /* |
| * Zero di_nblocks if we don't have any extents at all to satisfy the |
| * buffer verifier. |
| */ |
| data_extents = xfs_dfork_data_extents(dip); |
| attr_extents = xfs_dfork_attr_extents(dip); |
| if (data_extents + attr_extents == 0) |
| dip->di_nblocks = 0; |
| } |
| |
| /* Inode didn't pass dinode verifiers, so fix the raw buffer and retry iget. */ |
| STATIC int |
| xrep_dinode_core( |
| struct xrep_inode *ri) |
| { |
| struct xfs_scrub *sc = ri->sc; |
| struct xfs_buf *bp; |
| struct xfs_dinode *dip; |
| xfs_ino_t ino = sc->sm->sm_ino; |
| int error; |
| int iget_error; |
| |
| /* Figure out what this inode had mapped in both forks. */ |
| error = xrep_dinode_count_rmaps(ri); |
| if (error) |
| return error; |
| |
| /* Read the inode cluster buffer. */ |
| error = xfs_trans_read_buf(sc->mp, sc->tp, sc->mp->m_ddev_targp, |
| ri->imap.im_blkno, ri->imap.im_len, XBF_UNMAPPED, &bp, |
| NULL); |
| if (error) |
| return error; |
| |
| /* Make sure we can pass the inode buffer verifier. */ |
| xrep_dinode_buf(sc, bp); |
| bp->b_ops = &xfs_inode_buf_ops; |
| |
| /* Fix everything the verifier will complain about. */ |
| dip = xfs_buf_offset(bp, ri->imap.im_boffset); |
| xrep_dinode_header(sc, dip); |
| xrep_dinode_mode(ri, dip); |
| xrep_dinode_flags(sc, dip, ri->rt_extents > 0); |
| xrep_dinode_size(ri, dip); |
| xrep_dinode_extsize_hints(sc, dip); |
| xrep_dinode_zap_forks(ri, dip); |
| |
| /* Write out the inode. */ |
| trace_xrep_dinode_fixed(sc, dip); |
| xfs_dinode_calc_crc(sc->mp, dip); |
| xfs_trans_buf_set_type(sc->tp, bp, XFS_BLFT_DINO_BUF); |
| xfs_trans_log_buf(sc->tp, bp, ri->imap.im_boffset, |
| ri->imap.im_boffset + sc->mp->m_sb.sb_inodesize - 1); |
| |
| /* |
| * In theory, we've fixed the ondisk inode record enough that we should |
| * be able to load the inode into the cache. Try to iget that inode |
| * now while we hold the AGI and the inode cluster buffer and take the |
| * IOLOCK so that we can continue with repairs without anyone else |
| * accessing the inode. If iget fails, we still need to commit the |
| * changes. |
| */ |
| iget_error = xchk_iget(sc, ino, &sc->ip); |
| if (!iget_error) |
| xchk_ilock(sc, XFS_IOLOCK_EXCL); |
| |
| /* |
| * Commit the inode cluster buffer updates and drop the AGI buffer that |
| * we've been holding since scrub setup. From here on out, repairs |
| * deal only with the cached inode. |
| */ |
| error = xrep_trans_commit(sc); |
| if (error) |
| return error; |
| |
| if (iget_error) |
| return iget_error; |
| |
| error = xchk_trans_alloc(sc, 0); |
| if (error) |
| return error; |
| |
| error = xrep_ino_dqattach(sc); |
| if (error) |
| return error; |
| |
| xchk_ilock(sc, XFS_ILOCK_EXCL); |
| if (ri->ino_sick_mask) |
| xfs_inode_mark_sick(sc->ip, ri->ino_sick_mask); |
| return 0; |
| } |
| |
| /* Fix everything xfs_dinode_verify cares about. */ |
| STATIC int |
| xrep_dinode_problems( |
| struct xrep_inode *ri) |
| { |
| struct xfs_scrub *sc = ri->sc; |
| int error; |
| |
| error = xrep_dinode_core(ri); |
| if (error) |
| return error; |
| |
| /* We had to fix a totally busted inode, schedule quotacheck. */ |
| if (XFS_IS_UQUOTA_ON(sc->mp)) |
| xrep_force_quotacheck(sc, XFS_DQTYPE_USER); |
| if (XFS_IS_GQUOTA_ON(sc->mp)) |
| xrep_force_quotacheck(sc, XFS_DQTYPE_GROUP); |
| if (XFS_IS_PQUOTA_ON(sc->mp)) |
| xrep_force_quotacheck(sc, XFS_DQTYPE_PROJ); |
| |
| return 0; |
| } |
| |
| /* |
| * Fix problems that the verifiers don't care about. In general these are |
| * errors that don't cause problems elsewhere in the kernel that we can easily |
| * detect, so we don't check them all that rigorously. |
| */ |
| |
| /* Make sure block and extent counts are ok. */ |
| STATIC int |
| xrep_inode_blockcounts( |
| struct xfs_scrub *sc) |
| { |
| struct xfs_ifork *ifp; |
| xfs_filblks_t count; |
| xfs_filblks_t acount; |
| xfs_extnum_t nextents; |
| int error; |
| |
| trace_xrep_inode_blockcounts(sc); |
| |
| /* Set data fork counters from the data fork mappings. */ |
| error = xfs_bmap_count_blocks(sc->tp, sc->ip, XFS_DATA_FORK, |
| &nextents, &count); |
| if (error) |
| return error; |
| if (xfs_is_reflink_inode(sc->ip)) { |
| /* |
| * data fork blockcount can exceed physical storage if a user |
| * reflinks the same block over and over again. |
| */ |
| ; |
| } else if (XFS_IS_REALTIME_INODE(sc->ip)) { |
| if (count >= sc->mp->m_sb.sb_rblocks) |
| return -EFSCORRUPTED; |
| } else { |
| if (count >= sc->mp->m_sb.sb_dblocks) |
| return -EFSCORRUPTED; |
| } |
| error = xrep_ino_ensure_extent_count(sc, XFS_DATA_FORK, nextents); |
| if (error) |
| return error; |
| sc->ip->i_df.if_nextents = nextents; |
| |
| /* Set attr fork counters from the attr fork mappings. */ |
| ifp = xfs_ifork_ptr(sc->ip, XFS_ATTR_FORK); |
| if (ifp) { |
| error = xfs_bmap_count_blocks(sc->tp, sc->ip, XFS_ATTR_FORK, |
| &nextents, &acount); |
| if (error) |
| return error; |
| if (count >= sc->mp->m_sb.sb_dblocks) |
| return -EFSCORRUPTED; |
| error = xrep_ino_ensure_extent_count(sc, XFS_ATTR_FORK, |
| nextents); |
| if (error) |
| return error; |
| ifp->if_nextents = nextents; |
| } else { |
| acount = 0; |
| } |
| |
| sc->ip->i_nblocks = count + acount; |
| return 0; |
| } |
| |
| /* Check for invalid uid/gid/prid. */ |
| STATIC void |
| xrep_inode_ids( |
| struct xfs_scrub *sc) |
| { |
| bool dirty = false; |
| |
| trace_xrep_inode_ids(sc); |
| |
| if (!uid_valid(VFS_I(sc->ip)->i_uid)) { |
| i_uid_write(VFS_I(sc->ip), 0); |
| dirty = true; |
| if (XFS_IS_UQUOTA_ON(sc->mp)) |
| xrep_force_quotacheck(sc, XFS_DQTYPE_USER); |
| } |
| |
| if (!gid_valid(VFS_I(sc->ip)->i_gid)) { |
| i_gid_write(VFS_I(sc->ip), 0); |
| dirty = true; |
| if (XFS_IS_GQUOTA_ON(sc->mp)) |
| xrep_force_quotacheck(sc, XFS_DQTYPE_GROUP); |
| } |
| |
| if (sc->ip->i_projid == -1U) { |
| sc->ip->i_projid = 0; |
| dirty = true; |
| if (XFS_IS_PQUOTA_ON(sc->mp)) |
| xrep_force_quotacheck(sc, XFS_DQTYPE_PROJ); |
| } |
| |
| /* strip setuid/setgid if we touched any of the ids */ |
| if (dirty) |
| VFS_I(sc->ip)->i_mode &= ~(S_ISUID | S_ISGID); |
| } |
| |
| static inline void |
| xrep_clamp_timestamp( |
| struct xfs_inode *ip, |
| struct timespec64 *ts) |
| { |
| ts->tv_nsec = clamp_t(long, ts->tv_nsec, 0, NSEC_PER_SEC); |
| *ts = timestamp_truncate(*ts, VFS_I(ip)); |
| } |
| |
| /* Nanosecond counters can't have more than 1 billion. */ |
| STATIC void |
| xrep_inode_timestamps( |
| struct xfs_inode *ip) |
| { |
| struct timespec64 tstamp; |
| struct inode *inode = VFS_I(ip); |
| |
| tstamp = inode_get_atime(inode); |
| xrep_clamp_timestamp(ip, &tstamp); |
| inode_set_atime_to_ts(inode, tstamp); |
| |
| tstamp = inode_get_mtime(inode); |
| xrep_clamp_timestamp(ip, &tstamp); |
| inode_set_mtime_to_ts(inode, tstamp); |
| |
| tstamp = inode_get_ctime(inode); |
| xrep_clamp_timestamp(ip, &tstamp); |
| inode_set_ctime_to_ts(inode, tstamp); |
| |
| xrep_clamp_timestamp(ip, &ip->i_crtime); |
| } |
| |
| /* Fix inode flags that don't make sense together. */ |
| STATIC void |
| xrep_inode_flags( |
| struct xfs_scrub *sc) |
| { |
| uint16_t mode; |
| |
| trace_xrep_inode_flags(sc); |
| |
| mode = VFS_I(sc->ip)->i_mode; |
| |
| /* Clear junk flags */ |
| if (sc->ip->i_diflags & ~XFS_DIFLAG_ANY) |
| sc->ip->i_diflags &= ~XFS_DIFLAG_ANY; |
| |
| /* NEWRTBM only applies to realtime bitmaps */ |
| if (sc->ip->i_ino == sc->mp->m_sb.sb_rbmino) |
| sc->ip->i_diflags |= XFS_DIFLAG_NEWRTBM; |
| else |
| sc->ip->i_diflags &= ~XFS_DIFLAG_NEWRTBM; |
| |
| /* These only make sense for directories. */ |
| if (!S_ISDIR(mode)) |
| sc->ip->i_diflags &= ~(XFS_DIFLAG_RTINHERIT | |
| XFS_DIFLAG_EXTSZINHERIT | |
| XFS_DIFLAG_PROJINHERIT | |
| XFS_DIFLAG_NOSYMLINKS); |
| |
| /* These only make sense for files. */ |
| if (!S_ISREG(mode)) |
| sc->ip->i_diflags &= ~(XFS_DIFLAG_REALTIME | |
| XFS_DIFLAG_EXTSIZE); |
| |
| /* These only make sense for non-rt files. */ |
| if (sc->ip->i_diflags & XFS_DIFLAG_REALTIME) |
| sc->ip->i_diflags &= ~XFS_DIFLAG_FILESTREAM; |
| |
| /* Immutable and append only? Drop the append. */ |
| if ((sc->ip->i_diflags & XFS_DIFLAG_IMMUTABLE) && |
| (sc->ip->i_diflags & XFS_DIFLAG_APPEND)) |
| sc->ip->i_diflags &= ~XFS_DIFLAG_APPEND; |
| |
| /* Clear junk flags. */ |
| if (sc->ip->i_diflags2 & ~XFS_DIFLAG2_ANY) |
| sc->ip->i_diflags2 &= ~XFS_DIFLAG2_ANY; |
| |
| /* No reflink flag unless we support it and it's a file. */ |
| if (!xfs_has_reflink(sc->mp) || !S_ISREG(mode)) |
| sc->ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; |
| |
| /* DAX only applies to files and dirs. */ |
| if (!(S_ISREG(mode) || S_ISDIR(mode))) |
| sc->ip->i_diflags2 &= ~XFS_DIFLAG2_DAX; |
| |
| /* No reflink files on the realtime device. */ |
| if (sc->ip->i_diflags & XFS_DIFLAG_REALTIME) |
| sc->ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; |
| } |
| |
| /* |
| * Fix size problems with block/node format directories. If we fail to find |
| * the extent list, just bail out and let the bmapbtd repair functions clean |
| * up that mess. |
| */ |
| STATIC void |
| xrep_inode_blockdir_size( |
| struct xfs_scrub *sc) |
| { |
| struct xfs_iext_cursor icur; |
| struct xfs_bmbt_irec got; |
| struct xfs_ifork *ifp; |
| xfs_fileoff_t off; |
| int error; |
| |
| trace_xrep_inode_blockdir_size(sc); |
| |
| error = xfs_iread_extents(sc->tp, sc->ip, XFS_DATA_FORK); |
| if (error) |
| return; |
| |
| /* Find the last block before 32G; this is the dir size. */ |
| ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK); |
| off = XFS_B_TO_FSB(sc->mp, XFS_DIR2_SPACE_SIZE); |
| if (!xfs_iext_lookup_extent_before(sc->ip, ifp, &off, &icur, &got)) { |
| /* zero-extents directory? */ |
| return; |
| } |
| |
| off = got.br_startoff + got.br_blockcount; |
| sc->ip->i_disk_size = min_t(loff_t, XFS_DIR2_SPACE_SIZE, |
| XFS_FSB_TO_B(sc->mp, off)); |
| } |
| |
| /* Fix size problems with short format directories. */ |
| STATIC void |
| xrep_inode_sfdir_size( |
| struct xfs_scrub *sc) |
| { |
| struct xfs_ifork *ifp; |
| |
| trace_xrep_inode_sfdir_size(sc); |
| |
| ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK); |
| sc->ip->i_disk_size = ifp->if_bytes; |
| } |
| |
| /* |
| * Fix any irregularities in a directory inode's size now that we can iterate |
| * extent maps and access other regular inode data. |
| */ |
| STATIC void |
| xrep_inode_dir_size( |
| struct xfs_scrub *sc) |
| { |
| trace_xrep_inode_dir_size(sc); |
| |
| switch (sc->ip->i_df.if_format) { |
| case XFS_DINODE_FMT_EXTENTS: |
| case XFS_DINODE_FMT_BTREE: |
| xrep_inode_blockdir_size(sc); |
| break; |
| case XFS_DINODE_FMT_LOCAL: |
| xrep_inode_sfdir_size(sc); |
| break; |
| } |
| } |
| |
| /* Fix extent size hint problems. */ |
| STATIC void |
| xrep_inode_extsize( |
| struct xfs_scrub *sc) |
| { |
| /* Fix misaligned extent size hints on a directory. */ |
| if ((sc->ip->i_diflags & XFS_DIFLAG_RTINHERIT) && |
| (sc->ip->i_diflags & XFS_DIFLAG_EXTSZINHERIT) && |
| xfs_extlen_to_rtxmod(sc->mp, sc->ip->i_extsize) > 0) { |
| sc->ip->i_extsize = 0; |
| sc->ip->i_diflags &= ~XFS_DIFLAG_EXTSZINHERIT; |
| } |
| } |
| |
| /* Fix any irregularities in an inode that the verifiers don't catch. */ |
| STATIC int |
| xrep_inode_problems( |
| struct xfs_scrub *sc) |
| { |
| int error; |
| |
| error = xrep_inode_blockcounts(sc); |
| if (error) |
| return error; |
| xrep_inode_timestamps(sc->ip); |
| xrep_inode_flags(sc); |
| xrep_inode_ids(sc); |
| /* |
| * We can now do a better job fixing the size of a directory now that |
| * we can scan the data fork extents than we could in xrep_dinode_size. |
| */ |
| if (S_ISDIR(VFS_I(sc->ip)->i_mode)) |
| xrep_inode_dir_size(sc); |
| xrep_inode_extsize(sc); |
| |
| trace_xrep_inode_fixed(sc); |
| xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE); |
| return xrep_roll_trans(sc); |
| } |
| |
| /* Repair an inode's fields. */ |
| int |
| xrep_inode( |
| struct xfs_scrub *sc) |
| { |
| int error = 0; |
| |
| /* |
| * No inode? That means we failed the _iget verifiers. Repair all |
| * the things that the inode verifiers care about, then retry _iget. |
| */ |
| if (!sc->ip) { |
| struct xrep_inode *ri = sc->buf; |
| |
| ASSERT(ri != NULL); |
| |
| error = xrep_dinode_problems(ri); |
| if (error) |
| return error; |
| |
| /* By this point we had better have a working incore inode. */ |
| if (!sc->ip) |
| return -EFSCORRUPTED; |
| } |
| |
| xfs_trans_ijoin(sc->tp, sc->ip, 0); |
| |
| /* If we found corruption of any kind, try to fix it. */ |
| if ((sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) || |
| (sc->sm->sm_flags & XFS_SCRUB_OFLAG_XCORRUPT)) { |
| error = xrep_inode_problems(sc); |
| if (error) |
| return error; |
| } |
| |
| /* See if we can clear the reflink flag. */ |
| if (xfs_is_reflink_inode(sc->ip)) { |
| error = xfs_reflink_clear_inode_flag(sc->ip, &sc->tp); |
| if (error) |
| return error; |
| } |
| |
| return xrep_defer_finish(sc); |
| } |