/* * Copyright (c) 2000-2001 Silicon Graphics, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it would be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * Further, this software is distributed without any warranty that it is * free of the rightful claim of any third person regarding infringement * or the like. Any license provided herein, whether implied or * otherwise, applies only to this software file. Patent licenses, if * any, provided herein do not apply to combinations of this program with * other software, or any other product whatsoever. * * You should have received a copy of the GNU General Public License along * with this program; if not, write the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston MA 02111-1307, USA. * * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, * Mountain View, CA 94043, or: * * http://www.sgi.com * * For further information regarding this notice, see: * * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/ */ #include #include "avl.h" #include "globals.h" #include "agheader.h" #include "incore.h" #include "protos.h" #include "err_protos.h" #include "dir.h" #include "dir2.h" #include "dinode.h" #include "scan.h" #include "versions.h" #include "attr_repair.h" #include "bmap.h" /* * inode clearing routines */ /* * return the offset into the inode where the attribute fork starts */ /* ARGSUSED */ int calc_attr_offset(xfs_mount_t *mp, xfs_dinode_t *dino) { xfs_dinode_core_t *dinoc = &dino->di_core; int offset = ((__psint_t) &dino->di_u) - (__psint_t)dino; /* * don't worry about alignment when calculating offset * because the data fork is already 8-byte aligned */ switch (dinoc->di_format) { case XFS_DINODE_FMT_DEV: offset += sizeof(xfs_dev_t); break; case XFS_DINODE_FMT_LOCAL: offset += INT_GET(dinoc->di_size, ARCH_CONVERT); break; case XFS_DINODE_FMT_UUID: offset += sizeof(uuid_t); break; case XFS_DINODE_FMT_EXTENTS: offset += INT_GET(dinoc->di_nextents, ARCH_CONVERT) * sizeof(xfs_bmbt_rec_32_t); break; case XFS_DINODE_FMT_BTREE: offset += INT_GET(dino->di_u.di_bmbt.bb_numrecs, ARCH_CONVERT) * sizeof(xfs_bmbt_rec_32_t); break; default: do_error(_("Unknown inode format.\n")); abort(); break; } return(offset); } /* ARGSUSED */ int clear_dinode_attr(xfs_mount_t *mp, xfs_dinode_t *dino, xfs_ino_t ino_num) { xfs_dinode_core_t *dinoc = &dino->di_core; ASSERT(dinoc->di_forkoff != 0); if (!no_modify) fprintf(stderr, _("clearing inode %llu attributes\n"), (unsigned long long)ino_num); else fprintf(stderr, _("would have cleared inode %llu attributes\n"), (unsigned long long)ino_num); if (INT_GET(dinoc->di_anextents, ARCH_CONVERT) != 0) { if (no_modify) return(1); INT_ZERO(dinoc->di_anextents, ARCH_CONVERT); } if (dinoc->di_aformat != XFS_DINODE_FMT_EXTENTS) { if (no_modify) return(1); dinoc->di_aformat = XFS_DINODE_FMT_EXTENTS; } /* get rid of the fork by clearing forkoff */ /* Originally, when the attr repair code was added, the fork was cleared * by turning it into shortform status. This meant clearing the * hdr.totsize/count fields and also changing aformat to LOCAL * (vs EXTENTS). Over various fixes, the aformat and forkoff have * been updated to not show an attribute fork at all, however. * It could be possible that resetting totsize/count are not needed, * but just to be safe, leave it in for now. */ if (!no_modify) { xfs_attr_shortform_t *asf = (xfs_attr_shortform_t *) XFS_DFORK_APTR_ARCH(dino, ARCH_CONVERT); INT_SET(asf->hdr.totsize, ARCH_CONVERT, sizeof(xfs_attr_sf_hdr_t)); INT_SET(asf->hdr.count, ARCH_CONVERT, 0); dinoc->di_forkoff = 0; /* got to do this after asf is set */ } /* * always returns 1 since the fork gets zapped */ return(1); } /* ARGSUSED */ int clear_dinode_core(xfs_dinode_core_t *dinoc, xfs_ino_t ino_num) { int dirty = 0; if (INT_GET(dinoc->di_magic, ARCH_CONVERT) != XFS_DINODE_MAGIC) { dirty = 1; if (no_modify) return(1); INT_SET(dinoc->di_magic, ARCH_CONVERT, XFS_DINODE_MAGIC); } if (!XFS_DINODE_GOOD_VERSION(dinoc->di_version) || (!fs_inode_nlink && dinoc->di_version > XFS_DINODE_VERSION_1)) { dirty = 1; if (no_modify) return(1); dinoc->di_version = (fs_inode_nlink) ? XFS_DINODE_VERSION_2 : XFS_DINODE_VERSION_1; } if (INT_GET(dinoc->di_mode, ARCH_CONVERT) != 0) { dirty = 1; if (no_modify) return(1); INT_ZERO(dinoc->di_mode, ARCH_CONVERT); } if (INT_GET(dinoc->di_flags, ARCH_CONVERT) != 0) { dirty = 1; if (no_modify) return(1); INT_ZERO(dinoc->di_flags, ARCH_CONVERT); } if (INT_GET(dinoc->di_dmevmask, ARCH_CONVERT) != 0) { dirty = 1; if (no_modify) return(1); INT_ZERO(dinoc->di_dmevmask, ARCH_CONVERT); } if (dinoc->di_forkoff != 0) { dirty = 1; if (no_modify) return(1); dinoc->di_forkoff = 0; } if (dinoc->di_format != XFS_DINODE_FMT_EXTENTS) { dirty = 1; if (no_modify) return(1); dinoc->di_format = XFS_DINODE_FMT_EXTENTS; } if (dinoc->di_aformat != XFS_DINODE_FMT_EXTENTS) { dirty = 1; if (no_modify) return(1); dinoc->di_aformat = XFS_DINODE_FMT_EXTENTS; } if (INT_GET(dinoc->di_size, ARCH_CONVERT) != 0) { dirty = 1; if (no_modify) return(1); INT_ZERO(dinoc->di_size, ARCH_CONVERT); } if (INT_GET(dinoc->di_nblocks, ARCH_CONVERT) != 0) { dirty = 1; if (no_modify) return(1); INT_ZERO(dinoc->di_nblocks, ARCH_CONVERT); } if (INT_GET(dinoc->di_onlink, ARCH_CONVERT) != 0) { dirty = 1; if (no_modify) return(1); INT_ZERO(dinoc->di_onlink, ARCH_CONVERT); } if (INT_GET(dinoc->di_nextents, ARCH_CONVERT) != 0) { dirty = 1; if (no_modify) return(1); INT_ZERO(dinoc->di_nextents, ARCH_CONVERT); } if (INT_GET(dinoc->di_anextents, ARCH_CONVERT) != 0) { dirty = 1; if (no_modify) return(1); INT_ZERO(dinoc->di_anextents, ARCH_CONVERT); } if (dinoc->di_version > XFS_DINODE_VERSION_1 && INT_GET(dinoc->di_nlink, ARCH_CONVERT) != 0) { dirty = 1; if (no_modify) return(1); INT_ZERO(dinoc->di_nlink, ARCH_CONVERT); } return(dirty); } /* ARGSUSED */ int clear_dinode_unlinked(xfs_mount_t *mp, xfs_dinode_t *dino) { if (dino->di_next_unlinked != NULLAGINO) { if (!no_modify) dino->di_next_unlinked = NULLAGINO; return(1); } return(0); } /* * this clears the unlinked list too so it should not be called * until after the agi unlinked lists are walked in phase 3. * returns > zero if the inode has been altered while being cleared */ int clear_dinode(xfs_mount_t *mp, xfs_dinode_t *dino, xfs_ino_t ino_num) { int dirty; dirty = clear_dinode_core(&dino->di_core, ino_num); dirty += clear_dinode_unlinked(mp, dino); /* and clear the forks */ if (dirty && !no_modify) bzero(&dino->di_u, XFS_LITINO(mp)); return(dirty); } /* * misc. inode-related utility routines */ /* * returns 0 if inode number is valid, 1 if bogus */ int verify_inum(xfs_mount_t *mp, xfs_ino_t ino) { xfs_agnumber_t agno; xfs_agino_t agino; xfs_agblock_t agbno; xfs_sb_t *sbp = &mp->m_sb;; /* range check ag #, ag block. range-checking offset is pointless */ agno = XFS_INO_TO_AGNO(mp, ino); agino = XFS_INO_TO_AGINO(mp, ino); agbno = XFS_AGINO_TO_AGBNO(mp, agino); if (ino == 0 || ino == NULLFSINO) return(1); if (ino != XFS_AGINO_TO_INO(mp, agno, agino)) return(1); if (agno >= sbp->sb_agcount || (agno < sbp->sb_agcount && agbno >= sbp->sb_agblocks) || (agno == sbp->sb_agcount && agbno >= sbp->sb_dblocks - (sbp->sb_agcount-1) * sbp->sb_agblocks) || (agbno == 0)) return(1); return(0); } /* * have a separate routine to ensure that we don't accidentally * lose illegally set bits in the agino by turning it into an FSINO * to feed to the above routine */ int verify_aginum(xfs_mount_t *mp, xfs_agnumber_t agno, xfs_agino_t agino) { xfs_agblock_t agbno; xfs_sb_t *sbp = &mp->m_sb;; /* range check ag #, ag block. range-checking offset is pointless */ if (agino == 0 || agino == NULLAGINO) return(1); /* * agino's can't be too close to NULLAGINO because the min blocksize * is 9 bits and at most 1 bit of that gets used for the inode offset * so if the agino gets shifted by the # of offset bits and compared * to the legal agbno values, a bogus agino will be too large. there * will be extra bits set at the top that shouldn't be set. */ agbno = XFS_AGINO_TO_AGBNO(mp, agino); if (agno >= sbp->sb_agcount || (agno < sbp->sb_agcount && agbno >= sbp->sb_agblocks) || (agno == sbp->sb_agcount && agbno >= sbp->sb_dblocks - (sbp->sb_agcount-1) * sbp->sb_agblocks) || (agbno == 0)) return(1); return(0); } /* * return 1 if block number is good, 0 if out of range */ int verify_dfsbno(xfs_mount_t *mp, xfs_dfsbno_t fsbno) { xfs_agnumber_t agno; xfs_agblock_t agbno; xfs_sb_t *sbp = &mp->m_sb;; /* range check ag #, ag block. range-checking offset is pointless */ agno = XFS_FSB_TO_AGNO(mp, fsbno); agbno = XFS_FSB_TO_AGBNO(mp, fsbno); if (agno >= sbp->sb_agcount || (agno < sbp->sb_agcount && agbno >= sbp->sb_agblocks) || (agno == sbp->sb_agcount && agbno >= sbp->sb_dblocks - (sbp->sb_agcount-1) * sbp->sb_agblocks)) return(0); return(1); } int verify_agbno(xfs_mount_t *mp, xfs_agnumber_t agno, xfs_agblock_t agbno) { xfs_sb_t *sbp = &mp->m_sb;; /* range check ag #, ag block. range-checking offset is pointless */ if (agno >= sbp->sb_agcount || (agno < sbp->sb_agcount && agbno >= sbp->sb_agblocks) || (agno == sbp->sb_agcount && agbno >= sbp->sb_dblocks - (sbp->sb_agcount-1) * sbp->sb_agblocks)) return(0); return(1); } void convert_extent( xfs_bmbt_rec_32_t *rp, xfs_dfiloff_t *op, /* starting offset (blockno in file) */ xfs_dfsbno_t *sp, /* starting block (fs blockno) */ xfs_dfilblks_t *cp, /* blockcount */ int *fp) /* extent flag */ { xfs_bmbt_irec_t irec, *s = &irec; xfs_bmbt_rec_t rpcopy, *p = &rpcopy; memcpy(&rpcopy, rp, sizeof(rpcopy)); /* Just use the extent parsing routine from the kernel */ libxfs_bmbt_disk_get_all(p, s); if (fs_has_extflgbit) { if (s->br_state == XFS_EXT_UNWRITTEN) { *fp = 1; } else { *fp = 0; } } else { *fp = 0; } *op = s->br_startoff; *sp = s->br_startblock; *cp = s->br_blockcount; } /* * return address of block fblock if it's within the range described * by the extent list. Otherwise, returns a null address. */ /* ARGSUSED */ xfs_dfsbno_t get_bmbt_reclist( xfs_mount_t *mp, xfs_bmbt_rec_32_t *rp, int numrecs, xfs_dfiloff_t fblock) { int i; xfs_dfilblks_t cnt; xfs_dfiloff_t off_bno; xfs_dfsbno_t start; int flag; for (i = 0; i < numrecs; i++, rp++) { convert_extent(rp, &off_bno, &start, &cnt, &flag); if (off_bno >= fblock && off_bno + cnt < fblock) return(start + fblock - off_bno); } return(NULLDFSBNO); } /* * return 1 if inode should be cleared, 0 otherwise * if check_dups should be set to 1, that implies that * the primary purpose of this call is to see if the * file overlaps with any duplicate extents (in the * duplicate extent list). */ /* ARGSUSED */ int process_bmbt_reclist_int( xfs_mount_t *mp, xfs_bmbt_rec_32_t *rp, int numrecs, int type, xfs_ino_t ino, xfs_drfsbno_t *tot, blkmap_t **blkmapp, xfs_dfiloff_t *first_key, xfs_dfiloff_t *last_key, int check_dups, int whichfork) { xfs_dfsbno_t b; xfs_drtbno_t ext; xfs_dfilblks_t c; /* count */ xfs_dfilblks_t cp = 0; /* prev count */ xfs_dfsbno_t s; /* start */ xfs_dfsbno_t sp = 0; /* prev start */ xfs_dfiloff_t o = 0; /* offset */ xfs_dfiloff_t op = 0; /* prev offset */ char *ftype; char *forkname; int i; int state; int flag; /* extent flag */ int pwe; /* partially-written extent */ if (whichfork == XFS_DATA_FORK) forkname = _("data"); else forkname = _("attr"); if (type == XR_INO_RTDATA) ftype = _("real-time"); else ftype = _("regular"); for (i = 0; i < numrecs; i++, rp++) { convert_extent(rp, &o, &s, &c, &flag); if (i == 0) *last_key = *first_key = o; else *last_key = o; if (i > 0 && op + cp > o) { do_warn( _("bmap rec out of order, inode %llu entry %d " "[o s c] [%llu %llu %llu], %d [%llu %llu %llu]\n"), ino, i, o, s, c, i-1, op, sp, cp); return(1); } op = o; cp = c; sp = s; /* * check numeric validity of the extent */ if (c == 0) { do_warn( _("zero length extent (off = %llu, fsbno = %llu) in ino %llu\n"), o, s, ino); return(1); } if (type == XR_INO_RTDATA) { if (s >= mp->m_sb.sb_rblocks) { do_warn( _("inode %llu - bad rt extent start block number %llu, offset %llu\n"), ino, s, o); return(1); } if (s + c - 1 >= mp->m_sb.sb_rblocks) { do_warn( _("inode %llu - bad rt extent last block number %llu, offset %llu\n"), ino, s + c - 1, o); return(1); } if (s + c - 1 < s) { do_warn( _("inode %llu - bad rt extent overflows - start %llu, end %llu, " "offset %llu\n"), ino, s, s + c - 1, o); return(1); } } else { if (!verify_dfsbno(mp, s)) { do_warn( _("inode %llu - bad extent starting block number %llu, offset %llu\n"), ino, s, o); return(1); } if (!verify_dfsbno(mp, s + c - 1)) { do_warn( _("inode %llu - bad extent last block number %llu, offset %llu\n"), ino, s + c - 1, o); return(1); } if (s + c - 1 < s) { do_warn( _("inode %llu - bad extent overflows - start %llu, end %llu, " "offset %llu\n"), ino, s, s + c - 1, o); return(1); } if (o >= fs_max_file_offset) { do_warn( _("inode %llu - extent offset too large - start %llu, count %llu, " "offset %llu\n"), ino, s, c, o); return(1); } } /* * realtime file data fork */ if (type == XR_INO_RTDATA && whichfork == XFS_DATA_FORK) { /* * XXX - verify that the blocks listed in the record * are multiples of an extent */ if (XFS_SB_VERSION_HASEXTFLGBIT(&mp->m_sb) == 0 && (s % mp->m_sb.sb_rextsize != 0 || c % mp->m_sb.sb_rextsize != 0)) { do_warn( _("malformed rt inode extent [%llu %llu] (fs rtext size = %u)\n"), s, c, mp->m_sb.sb_rextsize); return(1); } /* * XXX - set the appropriate number of extents */ for (b = s; b < s + c; b += mp->m_sb.sb_rextsize) { ext = (xfs_drtbno_t) b / mp->m_sb.sb_rextsize; if (XFS_SB_VERSION_HASEXTFLGBIT(&mp->m_sb) && flag && (b % mp->m_sb.sb_rextsize != 0)) { pwe = 1; } else { pwe = 0; } if (check_dups == 1) { if (search_rt_dup_extent(mp, ext) && !pwe) { do_warn( _("data fork in rt ino %llu claims dup rt extent, off - %llu, " "start - %llu, count %llu\n"), ino, o, s, c); return(1); } continue; } state = get_rtbno_state(mp, ext); switch (state) { case XR_E_FREE: /* XXX - turn this back on after we run process_rtbitmap() in phase2 do_warn( _("%s fork in rt ino %llu claims free rt block %llu\n"), forkname, ino, ext); */ /* fall through ... */ case XR_E_UNKNOWN: set_rtbno_state(mp, ext, XR_E_INUSE); break; case XR_E_BAD_STATE: do_error( _("bad state in rt block map %llu\n"), ext); abort(); break; case XR_E_FS_MAP: case XR_E_INO: case XR_E_INUSE_FS: do_error( _("%s fork in rt inode %llu found metadata block %llu in %s bmap\n"), forkname, ino, ext, ftype); case XR_E_INUSE: if (pwe) break; case XR_E_MULT: set_rtbno_state(mp, ext, XR_E_MULT); do_warn( _("%s fork in rt inode %llu claims used rt block %llu\n"), forkname, ino, ext); return(1); case XR_E_FREE1: default: do_error( _("illegal state %d in %s block map %llu\n"), state, ftype, b); } } /* * bump up the block counter */ *tot += c; /* * skip rest of loop processing since that's * all for regular file forks and attr forks */ continue; } /* * regular file data fork or attribute fork */ if (blkmapp && *blkmapp) blkmap_set_ext(blkmapp, o, s, c); for (b = s; b < s + c; b++) { if (check_dups == 1) { /* * if we're just checking the bmap for dups, * return if we find one, otherwise, continue * checking each entry without setting the * block bitmap */ if (search_dup_extent(mp, XFS_FSB_TO_AGNO(mp, b), XFS_FSB_TO_AGBNO(mp, b))) { do_warn( _("%s fork in ino %llu claims dup extent, off - %llu, " "start - %llu, cnt %llu\n"), forkname, ino, o, s, c); return(1); } continue; } /* FIX FOR BUG 653709 -- EKN * realtime attribute fork, should be valid block number * in regular data space, not realtime partion. */ if (type == XR_INO_RTDATA && whichfork == XFS_ATTR_FORK) { if (mp->m_sb.sb_agcount < XFS_FSB_TO_AGNO(mp, b)) return(1); } state = get_fsbno_state(mp, b); switch (state) { case XR_E_FREE: case XR_E_FREE1: do_warn( _("%s fork in ino %llu claims free block %llu\n"), forkname, ino, (__uint64_t) b); /* fall through ... */ case XR_E_UNKNOWN: set_fsbno_state(mp, b, XR_E_INUSE); break; case XR_E_BAD_STATE: do_error(_("bad state in block map %llu\n"), b); abort(); break; case XR_E_FS_MAP: case XR_E_INO: case XR_E_INUSE_FS: do_warn( _("%s fork in inode %llu claims metadata block %llu\n"), forkname, ino, (__uint64_t) b); return(1); case XR_E_INUSE: case XR_E_MULT: set_fsbno_state(mp, b, XR_E_MULT); do_warn( _("%s fork in %s inode %llu claims used block %llu\n"), forkname, ftype, ino, (__uint64_t) b); return(1); default: do_error( _("illegal state %d in block map %llu\n"), state, b); abort(); } } *tot += c; } return(0); } /* * return 1 if inode should be cleared, 0 otherwise, sets block bitmap * as a side-effect */ int process_bmbt_reclist( xfs_mount_t *mp, xfs_bmbt_rec_32_t *rp, int numrecs, int type, xfs_ino_t ino, xfs_drfsbno_t *tot, blkmap_t **blkmapp, xfs_dfiloff_t *first_key, xfs_dfiloff_t *last_key, int whichfork) { return(process_bmbt_reclist_int(mp, rp, numrecs, type, ino, tot, blkmapp, first_key, last_key, 0, whichfork)); } /* * return 1 if inode should be cleared, 0 otherwise, does not set * block bitmap */ int scan_bmbt_reclist( xfs_mount_t *mp, xfs_bmbt_rec_32_t *rp, int numrecs, int type, xfs_ino_t ino, xfs_drfsbno_t *tot, int whichfork) { xfs_dfiloff_t first_key = 0; xfs_dfiloff_t last_key = 0; return(process_bmbt_reclist_int(mp, rp, numrecs, type, ino, tot, NULL, &first_key, &last_key, 1, whichfork)); } /* * these two are meant for routines that read and work with inodes * one at a time where the inodes may be in any order (like walking * the unlinked lists to look for inodes). the caller is responsible * for writing/releasing the buffer. */ xfs_buf_t * get_agino_buf(xfs_mount_t *mp, xfs_agnumber_t agno, xfs_agino_t agino, xfs_dinode_t **dipp) { ino_tree_node_t *irec; xfs_buf_t *bp; int size; if ((irec = find_inode_rec(agno, agino)) == NULL) return(NULL); size = XFS_FSB_TO_BB(mp, MAX(1, XFS_INODES_PER_CHUNK/inodes_per_block)); bp = libxfs_readbuf(mp->m_dev, XFS_AGB_TO_DADDR(mp, agno, XFS_AGINO_TO_AGBNO(mp, irec->ino_startnum)), size, 0); if (!bp) { do_warn(_("cannot read inode (%u/%u), disk block %lld\n"), agno, irec->ino_startnum, XFS_AGB_TO_DADDR(mp, agno, XFS_AGINO_TO_AGBNO(mp, irec->ino_startnum))); return(NULL); } *dipp = XFS_MAKE_IPTR(mp, bp, agino - XFS_OFFBNO_TO_AGINO(mp, XFS_AGINO_TO_AGBNO(mp, irec->ino_startnum), 0)); return(bp); } /* * these next routines return the filesystem blockno of the * block containing the block "bno" in the file whose bmap * tree (or extent list) is rooted by "rootblock". * * the next routines are utility routines for the third * routine, get_bmapi(). */ /* ARGSUSED */ xfs_dfsbno_t getfunc_extlist(xfs_mount_t *mp, xfs_ino_t ino, xfs_dinode_t *dip, xfs_dfiloff_t bno, int whichfork) { xfs_dfiloff_t fbno; xfs_dfilblks_t bcnt; xfs_dfsbno_t fsbno; xfs_dfsbno_t final_fsbno = NULLDFSBNO; xfs_bmbt_rec_32_t *rootblock = (xfs_bmbt_rec_32_t *) XFS_DFORK_PTR_ARCH(dip, whichfork, ARCH_CONVERT); xfs_extnum_t nextents = XFS_DFORK_NEXTENTS_ARCH(dip, whichfork, ARCH_CONVERT); int i; int flag; for (i = 0; i < nextents; i++) { convert_extent(rootblock + i, &fbno, &fsbno, &bcnt, &flag); if (fbno <= bno && bno < fbno + bcnt) { final_fsbno = bno - fbno + fsbno; break; } } return(final_fsbno); } xfs_dfsbno_t getfunc_btree(xfs_mount_t *mp, xfs_ino_t ino, xfs_dinode_t *dip, xfs_dfiloff_t bno, int whichfork) { int i; int prev_level; int flag; int found; xfs_bmbt_rec_32_t *rec; xfs_bmbt_ptr_t *pp; xfs_bmbt_key_t *key; xfs_bmdr_key_t *rkey; xfs_bmdr_ptr_t *rp; xfs_dfiloff_t fbno; xfs_dfsbno_t fsbno; xfs_dfilblks_t bcnt; xfs_buf_t *bp; xfs_dfsbno_t final_fsbno = NULLDFSBNO; xfs_bmbt_block_t *block; xfs_bmdr_block_t *rootblock = (xfs_bmdr_block_t *) XFS_DFORK_PTR_ARCH(dip, whichfork, ARCH_CONVERT); ASSERT(rootblock->bb_level != 0); /* * deal with root block, it's got a slightly different * header structure than interior nodes. We know that * a btree should have at least 2 levels otherwise it * would be an extent list. */ rkey = XFS_BTREE_KEY_ADDR( XFS_DFORK_SIZE_ARCH(dip, mp, whichfork, ARCH_CONVERT), xfs_bmdr, rootblock, 1, XFS_BTREE_BLOCK_MAXRECS(XFS_DFORK_SIZE_ARCH(dip, mp, whichfork, ARCH_CONVERT), xfs_bmdr, 1)); rp = XFS_BTREE_PTR_ADDR( XFS_DFORK_SIZE_ARCH(dip, mp, whichfork, ARCH_CONVERT), xfs_bmdr, rootblock, 1, XFS_BTREE_BLOCK_MAXRECS(XFS_DFORK_SIZE_ARCH(dip, mp, whichfork, ARCH_CONVERT), xfs_bmdr, 1)); for (found = -1, i = 0; i < rootblock->bb_numrecs - 1; i++) { if (rkey[i].br_startoff <= bno && bno < rkey[i+1].br_startoff) { found = i; break; } } if (i == rootblock->bb_numrecs - 1 && bno >= rkey[i].br_startoff) found = i; ASSERT(found != -1); fsbno = INT_GET(rp[found], ARCH_CONVERT); ASSERT(verify_dfsbno(mp, fsbno)); bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), XFS_FSB_TO_BB(mp, 1), 0); if (!bp) { do_error(_("cannot read bmap block %llu\n"), fsbno); return(NULLDFSBNO); } block = XFS_BUF_TO_BMBT_BLOCK(bp); /* * ok, now traverse any interior btree nodes */ prev_level = rootblock->bb_level; while (INT_GET(block->bb_level, ARCH_CONVERT) > 0) { ASSERT(INT_GET(block->bb_level, ARCH_CONVERT) < prev_level); prev_level = INT_GET(block->bb_level, ARCH_CONVERT); if (INT_GET(block->bb_numrecs, ARCH_CONVERT) > mp->m_bmap_dmxr[1]) { do_warn(_("# of bmap records in inode %llu exceeds max " "(%u, max - %u)\n"), ino, INT_GET(block->bb_numrecs, ARCH_CONVERT), mp->m_bmap_dmxr[1]); libxfs_putbuf(bp); return(NULLDFSBNO); } if (verbose && INT_GET(block->bb_numrecs, ARCH_CONVERT) < mp->m_bmap_dmnr[1]) { do_warn(_("- # of bmap records in inode %llu less than " "minimum (%u, min - %u), proceeding ...\n"), ino, INT_GET(block->bb_numrecs, ARCH_CONVERT), mp->m_bmap_dmnr[1]); } key = XFS_BTREE_KEY_ADDR(mp->m_sb.sb_blocksize, xfs_bmbt, block, 1, mp->m_bmap_dmxr[1]); pp = XFS_BTREE_PTR_ADDR(mp->m_sb.sb_blocksize, xfs_bmbt, block, 1, mp->m_bmap_dmxr[1]); for ( found = -1, i = 0; i < INT_GET(block->bb_numrecs, ARCH_CONVERT) - 1; i++) { if (INT_GET(key[i].br_startoff, ARCH_CONVERT) <= bno && bno < INT_GET(key[i+1].br_startoff, ARCH_CONVERT)) { found = i; break; } } if (i == INT_GET(block->bb_numrecs, ARCH_CONVERT) - 1 && bno >= INT_GET(key[i].br_startoff, ARCH_CONVERT)) found = i; ASSERT(found != -1); fsbno = INT_GET(pp[found], ARCH_CONVERT); ASSERT(verify_dfsbno(mp, fsbno)); /* * release current btree block and read in the * next btree block to be traversed */ libxfs_putbuf(bp); bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), XFS_FSB_TO_BB(mp, 1), 0); if (!bp) { do_error(_("cannot read bmap block %llu\n"), fsbno); return(NULLDFSBNO); } block = XFS_BUF_TO_BMBT_BLOCK(bp); } /* * current block must be a leaf block */ ASSERT(INT_GET(block->bb_level, ARCH_CONVERT) == 0); if (INT_GET(block->bb_numrecs, ARCH_CONVERT) > mp->m_bmap_dmxr[0]) { do_warn(_("# of bmap records in inode %llu greater than " "maximum (%u, max - %u)\n"), ino, INT_GET(block->bb_numrecs, ARCH_CONVERT), mp->m_bmap_dmxr[0]); libxfs_putbuf(bp); return(NULLDFSBNO); } if (verbose && INT_GET(block->bb_numrecs, ARCH_CONVERT) < mp->m_bmap_dmnr[0]) do_warn(_("- # of bmap records in inode %llu less than minimum " "(%u, min - %u), continuing...\n"), ino, INT_GET(block->bb_numrecs, ARCH_CONVERT), mp->m_bmap_dmnr[0]); rec = (xfs_bmbt_rec_32_t *)XFS_BTREE_REC_ADDR(mp->m_sb.sb_blocksize, xfs_bmbt, block, 1, mp->m_bmap_dmxr[0]); for (i = 0; i < INT_GET(block->bb_numrecs, ARCH_CONVERT); i++) { convert_extent(rec + i, &fbno, &fsbno, &bcnt, &flag); if (fbno <= bno && bno < fbno + bcnt) { final_fsbno = bno - fbno + fsbno; break; } } libxfs_putbuf(bp); if (final_fsbno == NULLDFSBNO) do_warn(_("could not map block %llu\n"), bno); return(final_fsbno); } /* * this could be smarter. maybe we should have an open inode * routine that would get the inode buffer and return back * an inode handle. I'm betting for the moment that this * is used only by the directory and attribute checking code * and that the avl tree find and buffer cache search are * relatively cheap. If they're too expensive, we'll just * have to fix this and add an inode handle to the da btree * cursor. * * caller is responsible for checking doubly referenced blocks * and references to holes */ xfs_dfsbno_t get_bmapi(xfs_mount_t *mp, xfs_dinode_t *dino_p, xfs_ino_t ino_num, xfs_dfiloff_t bno, int whichfork) { xfs_dfsbno_t fsbno; switch (XFS_DFORK_FORMAT_ARCH(dino_p, whichfork, ARCH_CONVERT)) { case XFS_DINODE_FMT_EXTENTS: fsbno = getfunc_extlist(mp, ino_num, dino_p, bno, whichfork); break; case XFS_DINODE_FMT_BTREE: fsbno = getfunc_btree(mp, ino_num, dino_p, bno, whichfork); break; case XFS_DINODE_FMT_LOCAL: do_error(_("get_bmapi() called for local inode %llu\n"), ino_num); fsbno = NULLDFSBNO; break; default: /* * shouldn't happen */ do_error(_("bad inode format for inode %llu\n"), ino_num); fsbno = NULLDFSBNO; } return(fsbno); } /* * higher level inode processing stuff starts here: * first, one utility routine for each type of inode */ /* * return 1 if inode should be cleared, 0 otherwise */ /* ARGSUSED */ int process_btinode( xfs_mount_t *mp, xfs_agnumber_t agno, xfs_agino_t ino, xfs_dinode_t *dip, int type, int *dirty, xfs_drfsbno_t *tot, __uint64_t *nex, blkmap_t **blkmapp, int whichfork, int check_dups) { xfs_bmdr_block_t *dib; xfs_dfiloff_t last_key; xfs_dfiloff_t first_key = 0; xfs_ino_t lino; xfs_bmbt_ptr_t *pp; xfs_bmbt_key_t *pkey; char *forkname; int i; bmap_cursor_t cursor; dib = (xfs_bmdr_block_t *)XFS_DFORK_PTR_ARCH(dip, whichfork, ARCH_CONVERT); lino = XFS_AGINO_TO_INO(mp, agno, ino); *tot = 0; *nex = 0; if (whichfork == XFS_DATA_FORK) forkname = _("data"); else forkname = _("attr"); if (INT_GET(dib->bb_level, ARCH_CONVERT) == 0) { /* * This should never happen since a btree inode * has to have at least one other block in the * bmap in addition to the root block in the * inode's data fork. * * XXX - if we were going to fix up the inode, * we'd try to treat the fork as an interior * node and see if we could get an accurate * level value from one of the blocks pointed * to by the pointers in the fork. For now * though, we just bail (and blow out the inode). */ do_warn(_("bad level 0 in inode %llu bmap btree root block\n"), XFS_AGINO_TO_INO(mp, agno, ino)); return(1); } /* * use bmdr/dfork_dsize since the root block is in the data fork */ init_bm_cursor(&cursor, INT_GET(dib->bb_level, ARCH_CONVERT) + 1); if (XFS_BMDR_SPACE_CALC(INT_GET(dib->bb_numrecs, ARCH_CONVERT)) > ((whichfork == XFS_DATA_FORK) ? XFS_DFORK_DSIZE_ARCH(dip, mp, ARCH_CONVERT) : XFS_DFORK_ASIZE_ARCH(dip, mp, ARCH_CONVERT))) { do_warn( _("indicated size of %s btree root (%d bytes) greater than space in " "inode %llu %s fork\n"), forkname, XFS_BMDR_SPACE_CALC(INT_GET(dib->bb_numrecs, ARCH_CONVERT)), lino, forkname); return(1); } pp = XFS_BTREE_PTR_ADDR( XFS_DFORK_SIZE_ARCH(dip, mp, whichfork, ARCH_CONVERT), xfs_bmdr, dib, 1, XFS_BTREE_BLOCK_MAXRECS( XFS_DFORK_SIZE_ARCH(dip, mp, whichfork, ARCH_CONVERT), xfs_bmdr, 0)); pkey = XFS_BTREE_KEY_ADDR( XFS_DFORK_SIZE_ARCH(dip, mp, whichfork, ARCH_CONVERT), xfs_bmdr, dib, 1, XFS_BTREE_BLOCK_MAXRECS( XFS_DFORK_SIZE_ARCH(dip, mp, whichfork, ARCH_CONVERT), xfs_bmdr, 0)); last_key = NULLDFILOFF; for (i = 0; i < INT_GET(dib->bb_numrecs, ARCH_CONVERT); i++) { /* * XXX - if we were going to do more to fix up the inode * btree, we'd do it right here. For now, if there's a * problem, we'll bail out and presumably clear the inode. */ if (!verify_dfsbno(mp, INT_GET(pp[i], ARCH_CONVERT))) { do_warn(_("bad bmap btree ptr 0x%llx in ino %llu\n"), INT_GET(pp[i], ARCH_CONVERT), lino); return(1); } if (scan_lbtree((xfs_dfsbno_t)INT_GET(pp[i], ARCH_CONVERT), INT_GET(dib->bb_level, ARCH_CONVERT), scanfunc_bmap, type, whichfork, lino, tot, nex, blkmapp, &cursor, 1, check_dups)) return(1); /* * fix key (offset) mismatches between the keys in root * block records and the first key of each child block. * fixes cases where entries have been shifted between * blocks but the parent hasn't been updated */ if (check_dups == 0 && cursor.level[INT_GET(dib->bb_level, ARCH_CONVERT)-1].first_key != INT_GET(pkey[i].br_startoff, ARCH_CONVERT)) { if (!no_modify) { do_warn( _("correcting key in bmbt root (was %llu, now %llu) in inode " "%llu %s fork\n"), INT_GET(pkey[i].br_startoff, ARCH_CONVERT), cursor.level[INT_GET(dib->bb_level, ARCH_CONVERT)-1].first_key, XFS_AGINO_TO_INO(mp, agno, ino), forkname); *dirty = 1; INT_SET(pkey[i].br_startoff, ARCH_CONVERT, cursor.level[INT_GET(dib->bb_level, ARCH_CONVERT)-1].first_key); } else { do_warn( _("bad key in bmbt root (is %llu, would reset to %llu) in inode " "%llu %s fork\n"), INT_GET(pkey[i].br_startoff, ARCH_CONVERT), cursor.level[INT_GET(dib->bb_level, ARCH_CONVERT)-1].first_key, XFS_AGINO_TO_INO(mp, agno, ino), forkname); } } /* * make sure that keys are in ascending order. blow out * inode if the ordering doesn't hold */ if (check_dups == 0) { if (last_key != NULLDFILOFF && last_key >= cursor.level[INT_GET(dib->bb_level, ARCH_CONVERT)-1].first_key) { do_warn( _("out of order bmbt root key %llu in inode %llu %s fork\n"), first_key, XFS_AGINO_TO_INO(mp, agno, ino), forkname); return(1); } last_key = cursor.level[INT_GET(dib->bb_level, ARCH_CONVERT)-1].first_key; } } /* * Check that the last child block's forward sibling pointer * is NULL. */ if (check_dups == 0 && cursor.level[0].right_fsbno != NULLDFSBNO) { do_warn( _("bad fwd (right) sibling pointer (saw %llu should be NULLDFSBNO)\n"), cursor.level[0].right_fsbno); do_warn( _("\tin inode %u (%s fork) bmap btree block %llu\n"), XFS_AGINO_TO_INO(mp, agno, ino), forkname, cursor.level[0].fsbno); return(1); } return(0); } /* * return 1 if inode should be cleared, 0 otherwise */ /* ARGSUSED */ int process_exinode( xfs_mount_t *mp, xfs_agnumber_t agno, xfs_agino_t ino, xfs_dinode_t *dip, int type, int *dirty, xfs_drfsbno_t *tot, __uint64_t *nex, blkmap_t **blkmapp, int whichfork, int check_dups) { xfs_ino_t lino; xfs_bmbt_rec_32_t *rp; xfs_dfiloff_t first_key; xfs_dfiloff_t last_key; lino = XFS_AGINO_TO_INO(mp, agno, ino); rp = (xfs_bmbt_rec_32_t *)XFS_DFORK_PTR_ARCH(dip, whichfork, ARCH_CONVERT); *tot = 0; *nex = XFS_DFORK_NEXTENTS_ARCH(dip, whichfork, ARCH_CONVERT); /* * XXX - if we were going to fix up the btree record, * we'd do it right here. For now, if there's a problem, * we'll bail out and presumably clear the inode. */ if (check_dups == 0) return(process_bmbt_reclist(mp, rp, *nex, type, lino, tot, blkmapp, &first_key, &last_key, whichfork)); else return(scan_bmbt_reclist(mp, rp, *nex, type, lino, tot, whichfork)); } /* * return 1 if inode should be cleared, 0 otherwise */ /* ARGSUSED */ int process_lclinode( xfs_mount_t *mp, xfs_agnumber_t agno, xfs_agino_t ino, xfs_dinode_t *dip, int type, int *dirty, xfs_drfsbno_t *tot, __uint64_t *nex, blkmap_t **blkmapp, int whichfork, int check_dups) { xfs_attr_shortform_t *asf; xfs_dinode_core_t *dic; xfs_ino_t lino; *tot = 0; *nex = 0; /* local inodes have 0 extents */ dic = &dip->di_core; lino = XFS_AGINO_TO_INO(mp, agno, ino); if (whichfork == XFS_DATA_FORK && INT_GET(dic->di_size, ARCH_CONVERT) > XFS_DFORK_DSIZE_ARCH(dip, mp, ARCH_CONVERT)) { do_warn( _("local inode %llu data fork is too large (size = %lld, max = %d)\n"), lino, INT_GET(dic->di_size, ARCH_CONVERT), XFS_DFORK_DSIZE_ARCH(dip, mp, ARCH_CONVERT)); return(1); } else if (whichfork == XFS_ATTR_FORK) { asf = (xfs_attr_shortform_t *) XFS_DFORK_APTR_ARCH(dip, ARCH_CONVERT); if (INT_GET(asf->hdr.totsize, ARCH_CONVERT) > XFS_DFORK_ASIZE_ARCH(dip, mp, ARCH_CONVERT)) { do_warn( _("local inode %llu attr fork too large (size %d, max = %d)\n"), lino, INT_GET(asf->hdr.totsize, ARCH_CONVERT), XFS_DFORK_ASIZE_ARCH(dip, mp, ARCH_CONVERT)); return(1); } if (INT_GET(asf->hdr.totsize, ARCH_CONVERT) < sizeof(xfs_attr_sf_hdr_t)) { do_warn( _("local inode %llu attr too small (size = %d, min size = %d)\n"), lino, INT_GET(asf->hdr.totsize, ARCH_CONVERT), sizeof(xfs_attr_sf_hdr_t)); return(1); } } return(0); } int process_symlink_extlist(xfs_mount_t *mp, xfs_ino_t lino, xfs_dinode_t *dino) { xfs_dfsbno_t start; /* start */ xfs_dfilblks_t cnt; /* count */ xfs_dfiloff_t offset; /* offset */ xfs_dfiloff_t expected_offset; xfs_bmbt_rec_32_t *rp; int numrecs; int i; int max_blocks; int whichfork = XFS_DATA_FORK; int flag; if (INT_GET(dino->di_core.di_size, ARCH_CONVERT) <= XFS_DFORK_SIZE_ARCH(dino, mp, whichfork, ARCH_CONVERT)) { if (dino->di_core.di_format == XFS_DINODE_FMT_LOCAL) { return(0); } else { do_warn( _("mismatch between format (%d) and size (%lld) in symlink ino %llu\n"), dino->di_core.di_format, INT_GET(dino->di_core.di_size, ARCH_CONVERT), lino); return(1); } } else if (dino->di_core.di_format == XFS_DINODE_FMT_LOCAL) { do_warn( _("mismatch between format (%d) and size (%lld) in symlink inode %llu\n"), dino->di_core.di_format, INT_GET(dino->di_core.di_size, ARCH_CONVERT), lino); return(1); } rp = (xfs_bmbt_rec_32_t *)XFS_DFORK_PTR_ARCH(dino, whichfork, ARCH_CONVERT); numrecs = XFS_DFORK_NEXTENTS_ARCH(dino, whichfork, ARCH_CONVERT); /* * the max # of extents in a symlink inode is equal to the * number of max # of blocks required to store the symlink */ if (numrecs > max_symlink_blocks) { do_warn( _("bad number of extents (%d) in symlink %llu data fork\n"), numrecs, lino); return(1); } max_blocks = max_symlink_blocks; expected_offset = 0; for (i = 0; numrecs > 0; i++, numrecs--) { convert_extent(rp, &offset, &start, &cnt, &flag); if (offset != expected_offset) { do_warn( _("bad extent #%d offset (%llu) in symlink %llu data fork\n"), i, offset, lino); return(1); } if (cnt == 0 || cnt > max_blocks) { do_warn( _("bad extent #%d count (%llu) in symlink %llu data fork\n"), i, cnt, lino); return(1); } max_blocks -= cnt; expected_offset += cnt; } return(0); } /* * takes a name and length and returns 1 if the name contains * a \0, returns 0 otherwise */ int null_check(char *name, int length) { int i; ASSERT(length < MAXPATHLEN); for (i = 0; i < length; i++, name++) { if (*name == '\0') return(1); } return(0); } /* * like usual, returns 0 if everything's ok and 1 if something's * bogus */ int process_symlink(xfs_mount_t *mp, xfs_ino_t lino, xfs_dinode_t *dino, blkmap_t *blkmap) { xfs_dfsbno_t fsbno; xfs_dinode_core_t *dinoc = &dino->di_core; xfs_buf_t *bp = NULL; char *symlink, *cptr, *buf_data; int i, size, amountdone; char data[MAXPATHLEN]; /* * check size against kernel symlink limits. we know * size is consistent with inode storage format -- e.g. * the inode is structurally ok so we don't have to check * for that */ if (INT_GET(dinoc->di_size, ARCH_CONVERT) >= MAXPATHLEN) { do_warn(_("symlink in inode %llu too long (%lld chars)\n"), lino, INT_GET(dinoc->di_size, ARCH_CONVERT)); return(1); } /* * have to check symlink component by component. * get symlink contents into data area */ symlink = &data[0]; if (INT_GET(dinoc->di_size, ARCH_CONVERT) <= XFS_DFORK_DSIZE_ARCH(dino, mp, ARCH_CONVERT)) { /* * local symlink, just copy the symlink out of the * inode into the data area */ bcopy((char *)XFS_DFORK_DPTR_ARCH(dino, ARCH_CONVERT), symlink, INT_GET(dinoc->di_size, ARCH_CONVERT)); } else { /* * stored in a meta-data file, have to bmap one block * at a time and copy the symlink into the data area */ i = size = amountdone = 0; cptr = symlink; while (amountdone < INT_GET(dinoc->di_size, ARCH_CONVERT)) { fsbno = blkmap_get(blkmap, i); if (fsbno != NULLDFSBNO) bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), XFS_FSB_TO_BB(mp, 1), 0); if (!bp || fsbno == NULLDFSBNO) { do_warn( _("cannot read inode %llu, file block %d, disk block %llu\n"), lino, i, fsbno); return(1); } buf_data = (char *)XFS_BUF_PTR(bp); size = MIN(INT_GET(dinoc->di_size, ARCH_CONVERT) - amountdone, (int)XFS_FSB_TO_BB(mp, 1)*BBSIZE); bcopy(buf_data, cptr, size); cptr += size; amountdone += size; i++; libxfs_putbuf(bp); } } data[INT_GET(dinoc->di_size, ARCH_CONVERT)] = '\0'; /* * check for nulls */ if (null_check(symlink, (int) INT_GET(dinoc->di_size, ARCH_CONVERT))) { do_warn( _("found illegal null character in symlink inode %llu\n"), lino); return(1); } /* * check for any component being too long */ if (INT_GET(dinoc->di_size, ARCH_CONVERT) >= MAXNAMELEN) { cptr = strchr(symlink, '/'); while (cptr != NULL) { if (cptr - symlink >= MAXNAMELEN) { do_warn( _("component of symlink in inode %llu too long\n"), lino); return(1); } symlink = cptr + 1; cptr = strchr(symlink, '/'); } if (strlen(symlink) >= MAXNAMELEN) { do_warn( _("component of symlink in inode %llu too long\n"), lino); return(1); } } return(0); } /* * called to process the set of misc inode special inode types * that have no associated data storage (fifos, pipes, devices, etc.). */ /* ARGSUSED */ int process_misc_ino_types(xfs_mount_t *mp, xfs_dinode_t *dino, xfs_ino_t lino, int type) { /* * disallow mountpoint inodes until such time as the * kernel actually allows them to be created (will * probably require a superblock version rev, sigh). */ if (type == XR_INO_MOUNTPOINT) { do_warn(_("inode %llu has bad inode type (IFMNT)\n"), lino); return(1); } /* * must also have a zero size */ if (INT_GET(dino->di_core.di_size, ARCH_CONVERT) != 0) { switch (type) { case XR_INO_CHRDEV: do_warn(_("size of character device inode %llu != 0 " "(%lld bytes)\n"), lino, INT_GET(dino->di_core.di_size, ARCH_CONVERT)); break; case XR_INO_BLKDEV: do_warn(_("size of block device inode %llu != 0 " "(%lld bytes)\n"), lino, INT_GET(dino->di_core.di_size, ARCH_CONVERT)); break; case XR_INO_SOCK: do_warn(_("size of socket inode %llu != 0 " "(%lld bytes)\n"), lino, INT_GET(dino->di_core.di_size, ARCH_CONVERT)); break; case XR_INO_FIFO: do_warn(_("size of fifo inode %llu != 0 " "(%lld bytes)\n"), lino, INT_GET(dino->di_core.di_size, ARCH_CONVERT)); break; default: do_warn(_("Internal error - process_misc_ino_types, " "illegal type %d\n"), type); abort(); } return(1); } return(0); } int process_misc_ino_types_blocks(xfs_drfsbno_t totblocks, xfs_ino_t lino, int type) { /* * you can not enforce all misc types have zero data fork blocks * by checking dino->di_core.di_nblocks because atotblocks (attribute * blocks) are part of nblocks. We must check this later when atotblocks * has been calculated or by doing a simple check that anExtents == 0. * We must also guarantee that totblocks is 0. Thus nblocks checking * will be done later in process_dinode_int for misc types. */ if (totblocks != 0) { switch (type) { case XR_INO_CHRDEV: do_warn( _("size of character device inode %llu != 0 (%llu blocks)\n"), lino, totblocks); break; case XR_INO_BLKDEV: do_warn( _("size of block device inode %llu != 0 (%llu blocks)\n"), lino, totblocks); break; case XR_INO_SOCK: do_warn( _("size of socket inode %llu != 0 (%llu blocks)\n"), lino, totblocks); break; case XR_INO_FIFO: do_warn( _("size of fifo inode %llu != 0 (%llu blocks)\n"), lino, totblocks); break; default: return(0); } return(1); } return (0); } /* * returns 0 if the inode is ok, 1 if the inode is corrupt * check_dups can be set to 1 *only* when called by the * first pass of the duplicate block checking of phase 4. * *dirty is set > 0 if the dinode has been altered and * needs to be written out. * * for detailed, info, look at process_dinode() comments. */ /* ARGSUSED */ int process_dinode_int(xfs_mount_t *mp, xfs_dinode_t *dino, xfs_agnumber_t agno, xfs_agino_t ino, int was_free, /* 1 if inode is currently free */ int *dirty, /* out == > 0 if inode is now dirty */ int *cleared, /* out == 1 if inode was cleared */ int *used, /* out == 1 if inode is in use */ int verify_mode, /* 1 == verify but don't modify inode */ int uncertain, /* 1 == inode is uncertain */ int ino_discovery, /* 1 == check dirs for unknown inodes */ int check_dups, /* 1 == check if inode claims * duplicate blocks */ int extra_attr_check, /* 1 == do attribute format and value checks */ int *isa_dir, /* out == 1 if inode is a directory */ xfs_ino_t *parent) /* out -- parent if ino is a dir */ { xfs_drfsbno_t totblocks = 0; xfs_drfsbno_t atotblocks = 0; xfs_dinode_core_t *dinoc; char *rstring; int type; int rtype; int do_rt; int err; int retval = 0; __uint64_t nextents; __uint64_t anextents; xfs_ino_t lino; const int is_free = 0; const int is_used = 1; int repair = 0; blkmap_t *ablkmap = NULL; blkmap_t *dblkmap = NULL; static char okfmts[] = { 0, /* free inode */ 1 << XFS_DINODE_FMT_DEV, /* FIFO */ 1 << XFS_DINODE_FMT_DEV, /* CHR */ 0, /* type 3 unused */ (1 << XFS_DINODE_FMT_LOCAL) | (1 << XFS_DINODE_FMT_EXTENTS) | (1 << XFS_DINODE_FMT_BTREE), /* DIR */ 0, /* type 5 unused */ 1 << XFS_DINODE_FMT_DEV, /* BLK */ 0, /* type 7 unused */ (1 << XFS_DINODE_FMT_EXTENTS) | (1 << XFS_DINODE_FMT_BTREE), /* REG */ 0, /* type 9 unused */ (1 << XFS_DINODE_FMT_LOCAL) | (1 << XFS_DINODE_FMT_EXTENTS), /* LNK */ 0, /* type 11 unused */ 1 << XFS_DINODE_FMT_DEV, /* SOCK */ 0, /* type 13 unused */ 1 << XFS_DINODE_FMT_UUID, /* MNT */ 0 /* type 15 unused */ }; retval = 0; totblocks = atotblocks = 0; *dirty = *isa_dir = *cleared = 0; *used = is_used; type = rtype = XR_INO_UNKNOWN; rstring = NULL; do_rt = 0; dinoc = &dino->di_core; lino = XFS_AGINO_TO_INO(mp, agno, ino); /* * if in verify mode, don't modify the inode. * * if correcting, reset stuff that has known values * * if in uncertain mode, be silent on errors since we're * trying to find out if these are inodes as opposed * to assuming that they are. Just return the appropriate * return code in that case. */ if (INT_GET(dinoc->di_magic, ARCH_CONVERT) != XFS_DINODE_MAGIC) { retval++; if (!verify_mode) { do_warn(_("bad magic number 0x%x on inode %llu, "), INT_GET(dinoc->di_magic, ARCH_CONVERT), lino); if (!no_modify) { do_warn(_("resetting magic number\n")); *dirty = 1; INT_SET(dinoc->di_magic, ARCH_CONVERT, XFS_DINODE_MAGIC); } else { do_warn(_("would reset magic number\n")); } } else if (!uncertain) { do_warn(_("bad magic number 0x%x on inode %llu\n"), INT_GET(dinoc->di_magic, ARCH_CONVERT), lino); } } if (!XFS_DINODE_GOOD_VERSION(dinoc->di_version) || (!fs_inode_nlink && dinoc->di_version > XFS_DINODE_VERSION_1)) { retval++; if (!verify_mode) { do_warn(_("bad version number 0x%x on inode %llu, "), dinoc->di_version, lino); if (!no_modify) { do_warn(_("resetting version number\n")); *dirty = 1; dinoc->di_version = (fs_inode_nlink) ? XFS_DINODE_VERSION_2 : XFS_DINODE_VERSION_1; } else { do_warn(_("would reset version number\n")); } } else if (!uncertain) { do_warn(_("bad version number 0x%x on inode %llu\n"), dinoc->di_version, lino); } } /* * blow out of here if the inode size is < 0 */ if (INT_GET(dinoc->di_size, ARCH_CONVERT) < 0) { retval++; if (!verify_mode) { do_warn(_("bad (negative) size %lld on inode %llu\n"), INT_GET(dinoc->di_size, ARCH_CONVERT), lino); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); *cleared = 1; } else { *dirty = 1; *cleared = 1; } *used = is_free; } else if (!uncertain) { do_warn(_("bad (negative) size %lld on inode %llu\n"), INT_GET(dinoc->di_size, ARCH_CONVERT), lino); } return(1); } /* * was_free value is not meaningful if we're in verify mode */ if (!verify_mode && INT_GET(dinoc->di_mode, ARCH_CONVERT) == 0 && was_free == 1) { /* * easy case, inode free -- inode and map agree, clear * it just in case to ensure that format, etc. are * set correctly */ if (!no_modify) { err = clear_dinode(mp, dino, lino); if (err) { *dirty = 1; *cleared = 1; } } *used = is_free; return(0); } else if (!verify_mode && INT_GET(dinoc->di_mode, ARCH_CONVERT) == 0 && was_free == 0) { /* * the inode looks free but the map says it's in use. * clear the inode just to be safe and mark the inode * free. */ do_warn(_("imap claims a free inode %llu is in use, "), lino); if (!no_modify) { do_warn(_("correcting imap and clearing inode\n")); err = clear_dinode(mp, dino, lino); if (err) { retval++; *dirty = 1; *cleared = 1; } } else { do_warn(_("would correct imap and clear inode\n")); *dirty = 1; *cleared = 1; } *used = is_free; return(retval > 0 ? 1 : 0); } /* * because of the lack of any write ordering guarantee, it's * possible that the core got updated but the forks didn't. * so rather than be ambitious (and probably incorrect), * if there's an inconsistency, we get conservative and * just pitch the file. blow off checking formats of * free inodes since technically any format is legal * as we reset the inode when we re-use it. */ if (INT_GET(dinoc->di_mode, ARCH_CONVERT) != 0 && ((((INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT) >> 12) > 15) || dinoc->di_format < XFS_DINODE_FMT_DEV || dinoc->di_format > XFS_DINODE_FMT_UUID || (!(okfmts[(INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT) >> 12] & (1 << dinoc->di_format))))) { /* bad inode format */ retval++; if (!uncertain) do_warn(_("bad inode format in inode %llu\n"), lino); if (!verify_mode) { if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } } *cleared = 1; *used = is_free; return(retval > 0 ? 1 : 0); } if (verify_mode) return(retval > 0 ? 1 : 0); /* * clear the next unlinked field if necessary on a good * inode only during phase 4 -- when checking for inodes * referencing duplicate blocks. then it's safe because * we've done the inode discovery and have found all the inodes * we're going to find. check_dups is set to 1 only during * phase 4. Ugly. */ if (check_dups && !no_modify) *dirty += clear_dinode_unlinked(mp, dino); /* set type and map type info */ switch (INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT) { case S_IFDIR: type = XR_INO_DIR; *isa_dir = 1; break; case S_IFREG: if (INT_GET(dinoc->di_flags, ARCH_CONVERT) & XFS_DIFLAG_REALTIME) type = XR_INO_RTDATA; else if (lino == mp->m_sb.sb_rbmino) type = XR_INO_RTBITMAP; else if (lino == mp->m_sb.sb_rsumino) type = XR_INO_RTSUM; else type = XR_INO_DATA; break; case S_IFLNK: type = XR_INO_SYMLINK; break; case S_IFCHR: type = XR_INO_CHRDEV; break; case S_IFBLK: type = XR_INO_BLKDEV; break; case S_IFSOCK: type = XR_INO_SOCK; break; case S_IFIFO: type = XR_INO_FIFO; break; default: type = XR_INO_UNKNOWN; do_warn(_("Unexpected inode type %#o inode %llu\n"), (int) (INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT), lino); abort(); break; } /* * type checks for root, realtime inodes, and quota inodes */ if (lino == mp->m_sb.sb_rootino && type != XR_INO_DIR) { do_warn(_("bad inode type for root inode %llu, "), lino); type = XR_INO_DIR; if (!no_modify) { do_warn(_("resetting to directory\n")); INT_MOD_EXPR(dinoc->di_mode, ARCH_CONVERT, &= ~(INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT)); INT_MOD_EXPR(dinoc->di_mode, ARCH_CONVERT, |= INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFDIR); } else { do_warn(_("would reset to directory\n")); } } else if (lino == mp->m_sb.sb_rsumino) { do_rt = 1; rstring = _("summary"); rtype = XR_INO_RTSUM; } else if (lino == mp->m_sb.sb_rbmino) { do_rt = 1; rstring = _("bitmap"); rtype = XR_INO_RTBITMAP; } else if (lino == mp->m_sb.sb_uquotino) { if (type != XR_INO_DATA) { do_warn(_("user quota inode has bad type 0x%x\n"), INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; mp->m_sb.sb_uquotino = NULLFSINO; return(1); } } else if (lino == mp->m_sb.sb_gquotino) { if (type != XR_INO_DATA) { do_warn(_("group quota inode has bad type 0x%x\n"), INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; mp->m_sb.sb_gquotino = NULLFSINO; return(1); } } if (do_rt && type != rtype) { type = XR_INO_DATA; do_warn(_("bad inode type for realtime %s inode %llu, "), rstring, lino); if (!no_modify) { do_warn(_("resetting to regular file\n")); INT_MOD_EXPR(dinoc->di_mode, ARCH_CONVERT, &= ~(INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT)); INT_MOD_EXPR(dinoc->di_mode, ARCH_CONVERT, |= INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFREG); } else { do_warn(_("would reset to regular file\n")); } } /* * only realtime inodes should have extsize set */ if (type != XR_INO_RTDATA && INT_GET(dinoc->di_extsize, ARCH_CONVERT) != 0) { do_warn( _("bad non-zero extent size value %u for non-realtime inode %llu, "), INT_GET(dinoc->di_extsize, ARCH_CONVERT), lino); if (!no_modify) { do_warn(_("resetting to zero\n")); INT_ZERO(dinoc->di_extsize, ARCH_CONVERT); *dirty = 1; } else { do_warn(_("would reset to zero\n")); } } /* * for realtime inodes, check sizes to see that * they are consistent with the # of realtime blocks. * also, verify that they contain only one extent and * are extent format files. If anything's wrong, clear * the inode -- we'll recreate it in phase 6. */ if (do_rt && ((lino == mp->m_sb.sb_rbmino && INT_GET(dinoc->di_size, ARCH_CONVERT) != mp->m_sb.sb_rbmblocks * mp->m_sb.sb_blocksize) || (lino == mp->m_sb.sb_rsumino && INT_GET(dinoc->di_size, ARCH_CONVERT) != mp->m_rsumsize))) { do_warn(_("bad size %llu for realtime %s inode %llu\n"), INT_GET(dinoc->di_size, ARCH_CONVERT), rstring, lino); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; return(1); } if (do_rt && mp->m_sb.sb_rblocks == 0 && INT_GET(dinoc->di_nextents, ARCH_CONVERT) != 0) { do_warn(_("bad # of extents (%u) for realtime %s inode %llu\n"), INT_GET(dinoc->di_nextents, ARCH_CONVERT), rstring, lino); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; return(1); } /* * Setup nextents and anextents for blkmap_alloc calls. */ nextents = INT_GET(dinoc->di_nextents, ARCH_CONVERT); if (nextents > INT_GET(dinoc->di_nblocks, ARCH_CONVERT) || nextents > XFS_MAX_INCORE_EXTENTS) nextents = 1; anextents = INT_GET(dinoc->di_anextents, ARCH_CONVERT); if (anextents > INT_GET(dinoc->di_nblocks, ARCH_CONVERT) || anextents > XFS_MAX_INCORE_EXTENTS) anextents = 1; /* * general size/consistency checks: * * if the size <= size of the data fork, directories must be * local inodes unlike regular files which would be extent inodes. * all the other mentioned types have to have a zero size value. * * if the size and format don't match, get out now rather than * risk trying to process a non-existent extents or btree * type data fork. */ switch (type) { case XR_INO_DIR: if (INT_GET(dinoc->di_size, ARCH_CONVERT) <= XFS_DFORK_DSIZE_ARCH(dino, mp, ARCH_CONVERT) && (dinoc->di_format != XFS_DINODE_FMT_LOCAL)) { do_warn( _("mismatch between format (%d) and size (%lld) in directory ino %llu\n"), dinoc->di_format, INT_GET(dinoc->di_size, ARCH_CONVERT), lino); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; return(1); } if (dinoc->di_format != XFS_DINODE_FMT_LOCAL) dblkmap = blkmap_alloc(nextents); break; case XR_INO_SYMLINK: if (process_symlink_extlist(mp, lino, dino)) { do_warn(_("bad data fork in symlink %llu\n"), lino); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; return(1); } if (dinoc->di_format != XFS_DINODE_FMT_LOCAL) dblkmap = blkmap_alloc(nextents); break; case XR_INO_CHRDEV: /* fall through to FIFO case ... */ case XR_INO_BLKDEV: /* fall through to FIFO case ... */ case XR_INO_SOCK: /* fall through to FIFO case ... */ case XR_INO_MOUNTPOINT: /* fall through to FIFO case ... */ case XR_INO_FIFO: if (process_misc_ino_types(mp, dino, lino, type)) { if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; return(1); } break; case XR_INO_RTDATA: /* * if we have no realtime blocks, any inode claiming * to be a real-time file is bogus */ if (mp->m_sb.sb_rblocks == 0) { do_warn( _("found inode %llu claiming to be a real-time file\n"), lino); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; return(1); } break; case XR_INO_RTBITMAP: if (INT_GET(dinoc->di_size, ARCH_CONVERT) != (__int64_t)mp->m_sb.sb_rbmblocks * mp->m_sb.sb_blocksize) { do_warn( _("realtime bitmap inode %llu has bad size %lld (should be %lld)\n"), lino, INT_GET(dinoc->di_size, ARCH_CONVERT), (__int64_t) mp->m_sb.sb_rbmblocks * mp->m_sb.sb_blocksize); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; return(1); } dblkmap = blkmap_alloc(nextents); break; case XR_INO_RTSUM: if (INT_GET(dinoc->di_size, ARCH_CONVERT) != mp->m_rsumsize) { do_warn( _("realtime summary inode %llu has bad size %lld (should be %d)\n"), lino, INT_GET(dinoc->di_size, ARCH_CONVERT), mp->m_rsumsize); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; return(1); } dblkmap = blkmap_alloc(nextents); break; default: break; } /* * check for illegal values of forkoff */ err = 0; if (dinoc->di_forkoff != 0) { switch (dinoc->di_format) { case XFS_DINODE_FMT_DEV: if (dinoc->di_forkoff != (roundup(sizeof(xfs_dev_t), 8) >> 3)) { do_warn( _("bad attr fork offset %d in dev inode %llu, should be %d\n"), (int) dinoc->di_forkoff, lino, (int) (roundup(sizeof(xfs_dev_t), 8) >> 3)); err = 1; } break; case XFS_DINODE_FMT_UUID: if (dinoc->di_forkoff != (roundup(sizeof(uuid_t), 8) >> 3)) { do_warn( _("bad attr fork offset %d in uuid inode %llu, should be %d\n"), (int) dinoc->di_forkoff, lino, (int)(roundup(sizeof(uuid_t), 8) >> 3)); err = 1; } break; case XFS_DINODE_FMT_LOCAL: /* fall through ... */ case XFS_DINODE_FMT_EXTENTS: /* fall through ... */ case XFS_DINODE_FMT_BTREE: if (dinoc->di_forkoff != mp->m_attroffset >> 3) { do_warn( _("bad attr fork offset %d in inode %llu, should be %d\n"), (int) dinoc->di_forkoff, lino, (int) (mp->m_attroffset >> 3)); err = 1; } break; default: do_error(_("unexpected inode format %d\n"), (int) dinoc->di_format); break; } } if (err) { if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; blkmap_free(dblkmap); return(1); } /* * check data fork -- if it's bad, clear the inode */ nextents = 0; switch (dinoc->di_format) { case XFS_DINODE_FMT_LOCAL: err = process_lclinode(mp, agno, ino, dino, type, dirty, &totblocks, &nextents, &dblkmap, XFS_DATA_FORK, check_dups); break; case XFS_DINODE_FMT_EXTENTS: err = process_exinode(mp, agno, ino, dino, type, dirty, &totblocks, &nextents, &dblkmap, XFS_DATA_FORK, check_dups); break; case XFS_DINODE_FMT_BTREE: err = process_btinode(mp, agno, ino, dino, type, dirty, &totblocks, &nextents, &dblkmap, XFS_DATA_FORK, check_dups); break; case XFS_DINODE_FMT_DEV: /* fall through */ case XFS_DINODE_FMT_UUID: err = 0; break; default: do_error(_("unknown format %d, ino %llu (mode = %d)\n"), dinoc->di_format, lino, INT_GET(dinoc->di_mode, ARCH_CONVERT)); } if (err) { /* * problem in the data fork, clear out the inode * and get out */ do_warn(_("bad data fork in inode %llu\n"), lino); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; blkmap_free(dblkmap); return(1); } if (check_dups) { /* * if check_dups was non-zero, we have to * re-process data fork to set bitmap since the * bitmap wasn't set the first time through */ switch (dinoc->di_format) { case XFS_DINODE_FMT_LOCAL: err = process_lclinode(mp, agno, ino, dino, type, dirty, &totblocks, &nextents, &dblkmap, XFS_DATA_FORK, 0); break; case XFS_DINODE_FMT_EXTENTS: err = process_exinode(mp, agno, ino, dino, type, dirty, &totblocks, &nextents, &dblkmap, XFS_DATA_FORK, 0); break; case XFS_DINODE_FMT_BTREE: err = process_btinode(mp, agno, ino, dino, type, dirty, &totblocks, &nextents, &dblkmap, XFS_DATA_FORK, 0); break; case XFS_DINODE_FMT_DEV: /* fall through */ case XFS_DINODE_FMT_UUID: err = 0; break; default: do_error(_("unknown format %d, ino %llu (mode = %d)\n"), dinoc->di_format, lino, INT_GET(dinoc->di_mode, ARCH_CONVERT)); } if (no_modify && err != 0) { *cleared = 1; *used = is_free; *isa_dir = 0; blkmap_free(dblkmap); return(1); } ASSERT(err == 0); } /* * check attribute fork if necessary. attributes are * always stored in the regular filesystem. */ if (!XFS_DFORK_Q_ARCH(dino, ARCH_CONVERT) && dinoc->di_aformat != XFS_DINODE_FMT_EXTENTS) { do_warn(_("bad attribute format %d in inode %llu, "), dinoc->di_aformat, lino); if (!no_modify) { do_warn(_("resetting value\n")); dinoc->di_aformat = XFS_DINODE_FMT_EXTENTS; *dirty = 1; } else do_warn(_("would reset value\n")); anextents = 0; } else if (XFS_DFORK_Q_ARCH(dino, ARCH_CONVERT)) { switch (dinoc->di_aformat) { case XFS_DINODE_FMT_LOCAL: anextents = 0; err = process_lclinode(mp, agno, ino, dino, type, dirty, &atotblocks, &anextents, &ablkmap, XFS_ATTR_FORK, check_dups); break; case XFS_DINODE_FMT_EXTENTS: ablkmap = blkmap_alloc(anextents); anextents = 0; err = process_exinode(mp, agno, ino, dino, type, dirty, &atotblocks, &anextents, &ablkmap, XFS_ATTR_FORK, check_dups); break; case XFS_DINODE_FMT_BTREE: ablkmap = blkmap_alloc(anextents); anextents = 0; err = process_btinode(mp, agno, ino, dino, type, dirty, &atotblocks, &anextents, &ablkmap, XFS_ATTR_FORK, check_dups); break; default: anextents = 0; do_warn(_("illegal attribute format %d, ino %llu\n"), dinoc->di_aformat, lino); err = 1; break; } if (err) { /* * clear the attribute fork if necessary. we can't * clear the inode because we've already put the * inode space info into the blockmap. * * XXX - put the inode onto the "move it" list and * log the the attribute scrubbing */ do_warn(_("bad attribute fork in inode %llu"), lino); if (!no_modify) { if (delete_attr_ok) { do_warn(_(", clearing attr fork\n")); *dirty += clear_dinode_attr(mp, dino, lino); } else { do_warn("\n"); *dirty += clear_dinode(mp, dino, lino); } ASSERT(*dirty > 0); } else { do_warn(_(", would clear attr fork\n")); } atotblocks = 0; anextents = 0; if (delete_attr_ok) { if (!no_modify) dinoc->di_aformat = XFS_DINODE_FMT_LOCAL; } else { *cleared = 1; *used = is_free; *isa_dir = 0; blkmap_free(dblkmap); blkmap_free(ablkmap); } return(1); } else if (check_dups) { switch (dinoc->di_aformat) { case XFS_DINODE_FMT_LOCAL: err = process_lclinode(mp, agno, ino, dino, type, dirty, &atotblocks, &anextents, &ablkmap, XFS_ATTR_FORK, 0); break; case XFS_DINODE_FMT_EXTENTS: err = process_exinode(mp, agno, ino, dino, type, dirty, &atotblocks, &anextents, &ablkmap, XFS_ATTR_FORK, 0); break; case XFS_DINODE_FMT_BTREE: err = process_btinode(mp, agno, ino, dino, type, dirty, &atotblocks, &anextents, &ablkmap, XFS_ATTR_FORK, 0); break; default: do_error( _("illegal attribute fmt %d, ino %llu\n"), dinoc->di_aformat, lino); } if (no_modify && err != 0) { *cleared = 1; *used = is_free; *isa_dir = 0; blkmap_free(dblkmap); blkmap_free(ablkmap); return(1); } ASSERT(err == 0); } /* * do attribute semantic-based consistency checks now */ /* get this only in phase 3, not in both phase 3 and 4 */ if (extra_attr_check) { if ((err = process_attributes(mp, lino, dino, ablkmap, &repair))) { do_warn( _("problem with attribute contents in inode %llu\n"), lino); if(!repair) { /* clear attributes if not done already */ if (!no_modify) { *dirty += clear_dinode_attr( mp, dino, lino); dinoc->di_aformat = XFS_DINODE_FMT_LOCAL; } else { do_warn( _("would clear attr fork\n")); } atotblocks = 0; anextents = 0; } else { *dirty = 1; /* it's been repaired */ } } } blkmap_free(ablkmap); } else anextents = 0; /* * enforce totblocks is 0 for misc types */ if (process_misc_ino_types_blocks(totblocks, lino, type)) { if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; blkmap_free(dblkmap); return(1); } /* * correct space counters if required */ if (totblocks + atotblocks != INT_GET(dinoc->di_nblocks, ARCH_CONVERT)) { if (!no_modify) { do_warn( _("correcting nblocks for inode %llu, was %llu - counted %llu\n"), lino, INT_GET(dinoc->di_nblocks, ARCH_CONVERT), totblocks + atotblocks); *dirty = 1; INT_SET(dinoc->di_nblocks, ARCH_CONVERT, totblocks + atotblocks); } else { do_warn( _("bad nblocks %llu for inode %llu, would reset to %llu\n"), INT_GET(dinoc->di_nblocks, ARCH_CONVERT), lino, totblocks + atotblocks); } } if (nextents > MAXEXTNUM) { do_warn(_("too many data fork extents (%llu) in inode %llu\n"), nextents, lino); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; blkmap_free(dblkmap); return(1); } if (nextents != INT_GET(dinoc->di_nextents, ARCH_CONVERT)) { if (!no_modify) { do_warn( _("correcting nextents for inode %llu, was %d - counted %llu\n"), lino, INT_GET(dinoc->di_nextents, ARCH_CONVERT), nextents); *dirty = 1; INT_SET(dinoc->di_nextents, ARCH_CONVERT, (xfs_extnum_t) nextents); } else { do_warn( _("bad nextents %d for inode %llu, would reset to %llu\n"), INT_GET(dinoc->di_nextents, ARCH_CONVERT), lino, nextents); } } if (anextents > MAXAEXTNUM) { do_warn(_("too many attr fork extents (%llu) in inode %llu\n"), anextents, lino); if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; blkmap_free(dblkmap); return(1); } if (anextents != INT_GET(dinoc->di_anextents, ARCH_CONVERT)) { if (!no_modify) { do_warn( _("correcting anextents for inode %llu, was %d - counted %llu\n"), lino, INT_GET(dinoc->di_anextents, ARCH_CONVERT), anextents); *dirty = 1; INT_SET(dinoc->di_anextents, ARCH_CONVERT, (xfs_aextnum_t) anextents); } else { do_warn( _("bad anextents %d for inode %llu, would reset to %llu\n"), INT_GET(dinoc->di_anextents, ARCH_CONVERT), lino, anextents); } } /* * do any semantic type-based checking here */ switch (type) { case XR_INO_DIR: if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) err = process_dir2(mp, lino, dino, ino_discovery, dirty, "", parent, dblkmap); else err = process_dir(mp, lino, dino, ino_discovery, dirty, "", parent, dblkmap); if (err) do_warn( _("problem with directory contents in inode %llu\n"), lino); break; case XR_INO_RTBITMAP: /* process_rtbitmap XXX */ err = 0; break; case XR_INO_RTSUM: /* process_rtsummary XXX */ err = 0; break; case XR_INO_SYMLINK: if ((err = process_symlink(mp, lino, dino, dblkmap))) do_warn(_("problem with symbolic link in inode %llu\n"), lino); break; case XR_INO_DATA: /* fall through to FIFO case ... */ case XR_INO_RTDATA: /* fall through to FIFO case ... */ case XR_INO_CHRDEV: /* fall through to FIFO case ... */ case XR_INO_BLKDEV: /* fall through to FIFO case ... */ case XR_INO_SOCK: /* fall through to FIFO case ... */ case XR_INO_FIFO: err = 0; break; default: printf(_("Unexpected inode type\n")); abort(); } blkmap_free(dblkmap); if (err) { /* * problem in the inode type-specific semantic * checking, clear out the inode and get out */ if (!no_modify) { *dirty += clear_dinode(mp, dino, lino); ASSERT(*dirty > 0); } *cleared = 1; *used = is_free; *isa_dir = 0; return(1); } /* * check nlinks feature, if it's a version 1 inode, * just leave nlinks alone. even if it's set wrong, * it'll be reset when read in. */ if (dinoc->di_version > XFS_DINODE_VERSION_1 && !fs_inode_nlink) { /* * do we have a fs/inode version mismatch with a valid * version 2 inode here that has to stay version 2 or * lose links? */ if (INT_GET(dinoc->di_nlink, ARCH_CONVERT) > XFS_MAXLINK_1) { /* * yes. are nlink inodes allowed? */ if (fs_inode_nlink_allowed) { /* * yes, update status variable which will * cause sb to be updated later. */ fs_inode_nlink = 1; do_warn( _("version 2 inode %llu claims > %u links, "), lino, XFS_MAXLINK_1); if (!no_modify) { do_warn( _("updating superblock version number\n")); } else { do_warn( _("would update superblock version number\n")); } } else { /* * no, have to convert back to onlinks * even if we lose some links */ do_warn( _("WARNING: version 2 inode %llu claims > %u links, "), lino, XFS_MAXLINK_1); if (!no_modify) { do_warn( _("converting back to version 1,\n\tthis may destroy %d links\n"), INT_GET(dinoc->di_nlink, ARCH_CONVERT) - XFS_MAXLINK_1); dinoc->di_version = XFS_DINODE_VERSION_1; INT_SET(dinoc->di_nlink, ARCH_CONVERT, XFS_MAXLINK_1); INT_SET(dinoc->di_onlink, ARCH_CONVERT, XFS_MAXLINK_1); *dirty = 1; } else { do_warn( _("would convert back to version 1,\n\tthis might destroy %d links\n"), INT_GET(dinoc->di_nlink, ARCH_CONVERT) - XFS_MAXLINK_1); } } } else { /* * do we have a v2 inode that we could convert back * to v1 without losing any links? if we do and * we have a mismatch between superblock bits and the * version bit, alter the version bit in this case. * * the case where we lost links was handled above. */ do_warn(_("found version 2 inode %llu, "), lino); if (!no_modify) { do_warn(_("converting back to version 1\n")); dinoc->di_version = XFS_DINODE_VERSION_1; INT_SET(dinoc->di_onlink, ARCH_CONVERT, INT_GET(dinoc->di_nlink, ARCH_CONVERT)); *dirty = 1; } else { do_warn(_("would convert back to version 1\n")); } } } /* * ok, if it's still a version 2 inode, it's going * to stay a version 2 inode. it should have a zero * onlink field, so clear it. */ if (dinoc->di_version > XFS_DINODE_VERSION_1 && INT_GET(dinoc->di_onlink, ARCH_CONVERT) > 0 && fs_inode_nlink > 0) { if (!no_modify) { do_warn( _("clearing obsolete nlink field in version 2 inode %llu, was %d, now 0\n"), lino, INT_GET(dinoc->di_onlink, ARCH_CONVERT)); INT_ZERO(dinoc->di_onlink, ARCH_CONVERT); *dirty = 1; } else { do_warn( _("would clear obsolete nlink field in version 2 inode %llu, currently %d\n"), lino, INT_GET(dinoc->di_onlink, ARCH_CONVERT)); *dirty = 1; } } return(retval > 0 ? 1 : 0); } /* * returns 1 if inode is used, 0 if free. * performs any necessary salvaging actions. * note that we leave the generation count alone * because nothing we could set it to would be * guaranteed to be correct so the best guess for * the correct value is just to leave it alone. * * The trick is detecting empty files. For those, * the core and the forks should all be in the "empty" * or zero-length state -- a zero or possibly minimum length * (in the case of dirs) extent list -- although inline directories * and symlinks might be handled differently. So it should be * possible to sanity check them against each other. * * If the forks are an empty extent list though, then forget it. * The file is toast anyway since we can't recover its storage. * * Parameters: * Ins: * mp -- mount structure * dino -- pointer to on-disk inode structure * agno/ino -- inode numbers * free -- whether the map thinks the inode is free (1 == free) * ino_discovery -- whether we should examine directory * contents to discover new inodes * check_dups -- whether we should check to see if the * inode references duplicate blocks * if so, we compare the inode's claimed * blocks against the contents of the * duplicate extent list but we don't * set the bitmap. If not, we set the * bitmap and try and detect multiply * claimed blocks using the bitmap. * Outs: * dirty -- whether we changed the inode (1 == yes) * cleared -- whether we cleared the inode (1 == yes). In * no modify mode, if we would have cleared it * used -- 1 if the inode is used, 0 if free. In no modify * mode, whether the inode should be used or free * isa_dir -- 1 if the inode is a directory, 0 if not. In * no modify mode, if the inode would be a dir or not. * * Return value -- 0 if the inode is good, 1 if it is/was corrupt */ int process_dinode(xfs_mount_t *mp, xfs_dinode_t *dino, xfs_agnumber_t agno, xfs_agino_t ino, int was_free, int *dirty, int *cleared, int *used, int ino_discovery, int check_dups, int extra_attr_check, int *isa_dir, xfs_ino_t *parent) { const int verify_mode = 0; const int uncertain = 0; #ifdef XR_INODE_TRACE fprintf(stderr, "processing inode %d/%d\n", agno, ino); #endif return(process_dinode_int(mp, dino, agno, ino, was_free, dirty, cleared, used, verify_mode, uncertain, ino_discovery, check_dups, extra_attr_check, isa_dir, parent)); } /* * a more cursory check, check inode core, *DON'T* check forks * this basically just verifies whether the inode is an inode * and whether or not it has been totally trashed. returns 0 * if the inode passes the cursory sanity check, 1 otherwise. */ int verify_dinode(xfs_mount_t *mp, xfs_dinode_t *dino, xfs_agnumber_t agno, xfs_agino_t ino) { xfs_ino_t parent; int cleared = 0; int used = 0; int dirty = 0; int isa_dir = 0; const int verify_mode = 1; const int check_dups = 0; const int ino_discovery = 0; const int uncertain = 0; return(process_dinode_int(mp, dino, agno, ino, 0, &dirty, &cleared, &used, verify_mode, uncertain, ino_discovery, check_dups, 0, &isa_dir, &parent)); } /* * like above only for inode on the uncertain list. it sets * the uncertain flag which makes process_dinode_int quieter. * returns 0 if the inode passes the cursory sanity check, 1 otherwise. */ int verify_uncertain_dinode(xfs_mount_t *mp, xfs_dinode_t *dino, xfs_agnumber_t agno, xfs_agino_t ino) { xfs_ino_t parent; int cleared = 0; int used = 0; int dirty = 0; int isa_dir = 0; const int verify_mode = 1; const int check_dups = 0; const int ino_discovery = 0; const int uncertain = 1; return(process_dinode_int(mp, dino, agno, ino, 0, &dirty, &cleared, &used, verify_mode, uncertain, ino_discovery, check_dups, 0, &isa_dir, &parent)); }