/* * Copyright (c) 2000-2004 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 "dir.h" #include "dir2.h" #include "dir_stack.h" #include "protos.h" #include "err_protos.h" #include "dinode.h" #include "versions.h" static cred_t zerocr; static int orphanage_entered; /* * Data structures and routines to keep track of directory entries * and whether their leaf entry has been seen */ typedef struct dir_hash_ent { struct dir_hash_ent *next; /* pointer to next entry */ xfs_dir2_leaf_entry_t ent; /* address and hash value */ short junkit; /* name starts with / */ short seen; /* have seen leaf entry */ } dir_hash_ent_t; typedef struct dir_hash_tab { int size; /* size of hash table */ dir_hash_ent_t *tab[1];/* actual hash table, variable size */ } dir_hash_tab_t; #define DIR_HASH_TAB_SIZE(n) \ (offsetof(dir_hash_tab_t, tab) + (sizeof(dir_hash_ent_t *) * (n))) #define DIR_HASH_FUNC(t,a) ((a) % (t)->size) /* * Track the contents of the freespace table in a directory. */ typedef struct freetab { int naents; /* expected number of data blocks */ int nents; /* number of data blocks processed */ struct freetab_ent { xfs_dir2_data_off_t v; short s; } ents[1]; } freetab_t; #define FREETAB_SIZE(n) \ (offsetof(freetab_t, ents) + (sizeof(struct freetab_ent) * (n))) #define DIR_HASH_CK_OK 0 #define DIR_HASH_CK_DUPLEAF 1 #define DIR_HASH_CK_BADHASH 2 #define DIR_HASH_CK_NODATA 3 #define DIR_HASH_CK_NOLEAF 4 #define DIR_HASH_CK_BADSTALE 5 #define DIR_HASH_CK_TOTAL 6 static void dir_hash_add( dir_hash_tab_t *hashtab, xfs_dahash_t hash, xfs_dir2_dataptr_t addr, int junk) { int i; dir_hash_ent_t *p; i = DIR_HASH_FUNC(hashtab, addr); if ((p = malloc(sizeof(*p))) == NULL) do_error(_("malloc failed in dir_hash_add (%u bytes)\n"), sizeof(*p)); p->next = hashtab->tab[i]; hashtab->tab[i] = p; if (!(p->junkit = junk)) p->ent.hashval = hash; p->ent.address = addr; p->seen = 0; } static int dir_hash_unseen( dir_hash_tab_t *hashtab) { int i; dir_hash_ent_t *p; for (i = 0; i < hashtab->size; i++) { for (p = hashtab->tab[i]; p; p = p->next) { if (p->seen == 0) return 1; } } return 0; } static int dir_hash_check( dir_hash_tab_t *hashtab, xfs_inode_t *ip, int seeval) { static char *seevalstr[DIR_HASH_CK_TOTAL]; static int done; if (!done) { seevalstr[DIR_HASH_CK_OK] = _("ok"); seevalstr[DIR_HASH_CK_DUPLEAF] = _("duplicate leaf"); seevalstr[DIR_HASH_CK_BADHASH] = _("hash value mismatch"); seevalstr[DIR_HASH_CK_NODATA] = _("no data entry"); seevalstr[DIR_HASH_CK_NOLEAF] = _("no leaf entry"); seevalstr[DIR_HASH_CK_BADSTALE] = _("bad stale count"); done = 1; } if (seeval == DIR_HASH_CK_OK && dir_hash_unseen(hashtab)) seeval = DIR_HASH_CK_NOLEAF; if (seeval == DIR_HASH_CK_OK) return 0; do_warn(_("bad hash table for directory inode %llu (%s): "), ip->i_ino, seevalstr[seeval]); if (!no_modify) do_warn(_("rebuilding\n")); else do_warn(_("would rebuild\n")); return 1; } static void dir_hash_done( dir_hash_tab_t *hashtab) { int i; dir_hash_ent_t *n; dir_hash_ent_t *p; for (i = 0; i < hashtab->size; i++) { for (p = hashtab->tab[i]; p; p = n) { n = p->next; free(p); } } free(hashtab); } static dir_hash_tab_t * dir_hash_init( xfs_fsize_t size) { dir_hash_tab_t *hashtab; int hsize; hsize = size / (16 * 4); if (hsize > 1024) hsize = 1024; else if (hsize < 16) hsize = 16; if ((hashtab = calloc(DIR_HASH_TAB_SIZE(hsize), 1)) == NULL) do_error(_("calloc failed in dir_hash_init\n")); hashtab->size = hsize; return hashtab; } static int dir_hash_see( dir_hash_tab_t *hashtab, xfs_dahash_t hash, xfs_dir2_dataptr_t addr) { int i; dir_hash_ent_t *p; i = DIR_HASH_FUNC(hashtab, addr); for (p = hashtab->tab[i]; p; p = p->next) { if (p->ent.address != addr) continue; if (p->seen) return DIR_HASH_CK_DUPLEAF; if (p->junkit == 0 && p->ent.hashval != hash) return DIR_HASH_CK_BADHASH; p->seen = 1; return DIR_HASH_CK_OK; } return DIR_HASH_CK_NODATA; } static int dir_hash_see_all( dir_hash_tab_t *hashtab, xfs_dir2_leaf_entry_t *ents, int count, int stale) { int i; int j; int rval; for (i = j = 0; i < count; i++) { if (INT_GET(ents[i].address, ARCH_CONVERT) == XFS_DIR2_NULL_DATAPTR) { j++; continue; } rval = dir_hash_see(hashtab, INT_GET(ents[i].hashval, ARCH_CONVERT), INT_GET(ents[i].address, ARCH_CONVERT)); if (rval != DIR_HASH_CK_OK) return rval; } return j == stale ? DIR_HASH_CK_OK : DIR_HASH_CK_BADSTALE; } /* * Version 1 or 2 directory routine wrappers */ static void dir_init(xfs_mount_t *mp, xfs_trans_t *tp, xfs_inode_t *dp, xfs_inode_t *pdp) { if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) libxfs_dir2_init(tp, dp, pdp); else libxfs_dir_init(tp, dp, pdp); } static int dir_createname(xfs_mount_t *mp, xfs_trans_t *tp, xfs_inode_t *pip, char *name, int namelen, xfs_ino_t inum, xfs_fsblock_t *first, xfs_bmap_free_t *flist, xfs_extlen_t total) { if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) return libxfs_dir2_createname(tp, pip, name, namelen, inum, first, flist, total); else return libxfs_dir_createname(tp, pip, name, namelen, inum, first, flist, total); } static int dir_lookup(xfs_mount_t *mp, xfs_trans_t *tp, xfs_inode_t *dp, char *name, int namelen, xfs_ino_t *inum) { if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) return libxfs_dir2_lookup(tp, dp, name, namelen, inum); else return libxfs_dir_lookup(tp, dp, name, namelen, inum); } static int dir_replace(xfs_mount_t *mp, xfs_trans_t *tp, xfs_inode_t *dp, char *name, int namelen, xfs_ino_t inum, xfs_fsblock_t *firstblock, xfs_bmap_free_t *flist, xfs_extlen_t total) { if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) return libxfs_dir2_replace(tp, dp, name, namelen, inum, firstblock, flist, total); else return libxfs_dir_replace(tp, dp, name, namelen, inum, firstblock, flist, total); } static int dir_removename(xfs_mount_t *mp, xfs_trans_t *tp, xfs_inode_t *dp, char *name, int namelen, xfs_ino_t inum, xfs_fsblock_t *firstblock, xfs_bmap_free_t *flist, xfs_extlen_t total) { if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) return libxfs_dir2_removename(tp, dp, name, namelen, inum, firstblock, flist, total); else return libxfs_dir_removename(tp, dp, name, namelen, inum, firstblock, flist, total); } static int dir_bogus_removename(xfs_mount_t *mp, xfs_trans_t *tp, xfs_inode_t *dp, char *name, xfs_fsblock_t *firstblock, xfs_bmap_free_t *flist, xfs_extlen_t total, xfs_dahash_t hashval, int namelen) { if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) return libxfs_dir2_bogus_removename(tp, dp, name, firstblock, flist, total, hashval, namelen); else return libxfs_dir_bogus_removename(tp, dp, name, firstblock, flist, total, hashval, namelen); } static void res_failed( int err) { if (err == ENOSPC) { do_error(_("ran out of disk space!\n")); } else do_error(_("xfs_trans_reserve returned %d\n"), err); } void mk_rbmino(xfs_mount_t *mp) { xfs_trans_t *tp; xfs_inode_t *ip; xfs_bmbt_irec_t *ep; xfs_fsblock_t first; int i; int nmap; int committed; int error; xfs_bmap_free_t flist; xfs_dfiloff_t bno; xfs_bmbt_irec_t map[XFS_BMAP_MAX_NMAP]; /* * first set up inode */ tp = libxfs_trans_alloc(mp, 0); if ((i = libxfs_trans_reserve(tp, 10, 0, 0, 0, 0))) res_failed(i); error = libxfs_trans_iget(mp, tp, mp->m_sb.sb_rbmino, 0, &ip); if (error) { do_error( _("couldn't iget realtime bitmap inode -- error - %d\n"), error); } bzero(&ip->i_d, sizeof(xfs_dinode_core_t)); ip->i_d.di_magic = XFS_DINODE_MAGIC; ip->i_d.di_mode = S_IFREG; ip->i_d.di_version = XFS_DINODE_VERSION_1; ip->i_d.di_format = XFS_DINODE_FMT_EXTENTS; ip->i_d.di_aformat = XFS_DINODE_FMT_EXTENTS; ip->i_d.di_nlink = 1; /* account for sb ptr */ /* * now the ifork */ ip->i_df.if_flags = XFS_IFEXTENTS; ip->i_df.if_bytes = ip->i_df.if_real_bytes = 0; ip->i_df.if_u1.if_extents = NULL; ip->i_d.di_size = mp->m_sb.sb_rbmblocks * mp->m_sb.sb_blocksize; /* * commit changes */ libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); libxfs_trans_ihold(tp, ip); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, NULL); /* * then allocate blocks for file and fill with zeroes (stolen * from mkfs) */ tp = libxfs_trans_alloc(mp, 0); if ((error = libxfs_trans_reserve(tp, mp->m_sb.sb_rbmblocks + (XFS_BM_MAXLEVELS(mp, XFS_DATA_FORK) - 1), 0, 0, 0, 0))) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); bno = 0; XFS_BMAP_INIT(&flist, &first); while (bno < mp->m_sb.sb_rbmblocks) { nmap = XFS_BMAP_MAX_NMAP; error = libxfs_bmapi(tp, ip, bno, (xfs_extlen_t)(mp->m_sb.sb_rbmblocks - bno), XFS_BMAPI_WRITE, &first, mp->m_sb.sb_rbmblocks, map, &nmap, &flist); if (error) { do_error( _("couldn't allocate realtime bitmap, error = %d\n"), error); } for (i = 0, ep = map; i < nmap; i++, ep++) { libxfs_device_zero(mp->m_dev, XFS_FSB_TO_DADDR(mp, ep->br_startblock), XFS_FSB_TO_BB(mp, ep->br_blockcount)); bno += ep->br_blockcount; } } error = libxfs_bmap_finish(&tp, &flist, first, &committed); if (error) { do_error( _("allocation of the realtime bitmap failed, error = %d\n"), error); } libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); } int fill_rbmino(xfs_mount_t *mp) { xfs_buf_t *bp; xfs_trans_t *tp; xfs_inode_t *ip; xfs_rtword_t *bmp; xfs_fsblock_t first; int nmap; int error; xfs_dfiloff_t bno; xfs_bmbt_irec_t map; bmp = btmcompute; bno = 0; tp = libxfs_trans_alloc(mp, 0); if ((error = libxfs_trans_reserve(tp, 10, 0, 0, 0, 0))) res_failed(error); error = libxfs_trans_iget(mp, tp, mp->m_sb.sb_rbmino, 0, &ip); if (error) { do_error( _("couldn't iget realtime bitmap inode -- error - %d\n"), error); } while (bno < mp->m_sb.sb_rbmblocks) { /* * fill the file one block at a time */ nmap = 1; error = libxfs_bmapi(tp, ip, bno, 1, XFS_BMAPI_WRITE, &first, 1, &map, &nmap, NULL); if (error || nmap != 1) { do_error( _("couldn't map realtime bitmap block %llu, error = %d\n"), bno, error); } ASSERT(map.br_startblock != HOLESTARTBLOCK); error = libxfs_trans_read_buf( mp, tp, mp->m_dev, XFS_FSB_TO_DADDR(mp, map.br_startblock), XFS_FSB_TO_BB(mp, 1), 1, &bp); if (error) { do_warn( _("can't access block %llu (fsbno %llu) of realtime bitmap inode %llu\n"), bno, map.br_startblock, mp->m_sb.sb_rbmino); return(1); } bcopy(bmp, XFS_BUF_PTR(bp), mp->m_sb.sb_blocksize); libxfs_trans_log_buf(tp, bp, 0, mp->m_sb.sb_blocksize - 1); bmp = (xfs_rtword_t *)((__psint_t) bmp + mp->m_sb.sb_blocksize); bno++; } libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); return(0); } int fill_rsumino(xfs_mount_t *mp) { xfs_buf_t *bp; xfs_trans_t *tp; xfs_inode_t *ip; xfs_suminfo_t *smp; xfs_fsblock_t first; int nmap; int error; xfs_dfiloff_t bno; xfs_dfiloff_t end_bno; xfs_bmbt_irec_t map; smp = sumcompute; bno = 0; end_bno = mp->m_rsumsize >> mp->m_sb.sb_blocklog; tp = libxfs_trans_alloc(mp, 0); if ((error = libxfs_trans_reserve(tp, 10, 0, 0, 0, 0))) res_failed(error); error = libxfs_trans_iget(mp, tp, mp->m_sb.sb_rsumino, 0, &ip); if (error) { do_error( _("couldn't iget realtime summary inode -- error - %d\n"), error); } while (bno < end_bno) { /* * fill the file one block at a time */ nmap = 1; error = libxfs_bmapi(tp, ip, bno, 1, XFS_BMAPI_WRITE, &first, 1, &map, &nmap, NULL); if (error || nmap != 1) { do_error( _("couldn't map realtime summary inode block %llu, error = %d\n"), bno, error); } ASSERT(map.br_startblock != HOLESTARTBLOCK); error = libxfs_trans_read_buf( mp, tp, mp->m_dev, XFS_FSB_TO_DADDR(mp, map.br_startblock), XFS_FSB_TO_BB(mp, 1), 1, &bp); if (error) { do_warn( _("can't access block %llu (fsbno %llu) of realtime summary inode %llu\n"), bno, map.br_startblock, mp->m_sb.sb_rsumino); return(1); } bcopy(smp, XFS_BUF_PTR(bp), mp->m_sb.sb_blocksize); libxfs_trans_log_buf(tp, bp, 0, mp->m_sb.sb_blocksize - 1); smp = (xfs_suminfo_t *)((__psint_t)smp + mp->m_sb.sb_blocksize); bno++; } libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); return(0); } void mk_rsumino(xfs_mount_t *mp) { xfs_trans_t *tp; xfs_inode_t *ip; xfs_bmbt_irec_t *ep; xfs_fsblock_t first; int i; int nmap; int committed; int error; int nsumblocks; xfs_bmap_free_t flist; xfs_dfiloff_t bno; xfs_bmbt_irec_t map[XFS_BMAP_MAX_NMAP]; /* * first set up inode */ tp = libxfs_trans_alloc(mp, 0); if ((i = libxfs_trans_reserve(tp, 10, XFS_ICHANGE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_MKDIR_LOG_COUNT))) res_failed(i); error = libxfs_trans_iget(mp, tp, mp->m_sb.sb_rsumino, 0, &ip); if (error) { do_error( _("couldn't iget realtime summary inode -- error - %d\n"), error); } bzero(&ip->i_d, sizeof(xfs_dinode_core_t)); ip->i_d.di_magic = XFS_DINODE_MAGIC; ip->i_d.di_mode = S_IFREG; ip->i_d.di_version = XFS_DINODE_VERSION_1; ip->i_d.di_format = XFS_DINODE_FMT_EXTENTS; ip->i_d.di_aformat = XFS_DINODE_FMT_EXTENTS; ip->i_d.di_nlink = 1; /* account for sb ptr */ /* * now the ifork */ ip->i_df.if_flags = XFS_IFEXTENTS; ip->i_df.if_bytes = ip->i_df.if_real_bytes = 0; ip->i_df.if_u1.if_extents = NULL; ip->i_d.di_size = mp->m_rsumsize; /* * commit changes */ libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); libxfs_trans_ihold(tp, ip); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); /* * then allocate blocks for file and fill with zeroes (stolen * from mkfs) */ tp = libxfs_trans_alloc(mp, 0); XFS_BMAP_INIT(&flist, &first); nsumblocks = mp->m_rsumsize >> mp->m_sb.sb_blocklog; if ((error = libxfs_trans_reserve(tp, mp->m_sb.sb_rbmblocks + (XFS_BM_MAXLEVELS(mp, XFS_DATA_FORK) - 1), BBTOB(128), 0, XFS_TRANS_PERM_LOG_RES, XFS_DEFAULT_PERM_LOG_COUNT))) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); bno = 0; XFS_BMAP_INIT(&flist, &first); while (bno < nsumblocks) { nmap = XFS_BMAP_MAX_NMAP; error = libxfs_bmapi(tp, ip, bno, (xfs_extlen_t)(nsumblocks - bno), XFS_BMAPI_WRITE, &first, nsumblocks, map, &nmap, &flist); if (error) { do_error( _("couldn't allocate realtime summary inode, error = %d\n"), error); } for (i = 0, ep = map; i < nmap; i++, ep++) { libxfs_device_zero(mp->m_dev, XFS_FSB_TO_DADDR(mp, ep->br_startblock), XFS_FSB_TO_BB(mp, ep->br_blockcount)); bno += ep->br_blockcount; } } error = libxfs_bmap_finish(&tp, &flist, first, &committed); if (error) { do_error( _("allocation of the realtime summary ino failed, error = %d\n"), error); } libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); } /* * makes a new root directory. */ void mk_root_dir(xfs_mount_t *mp) { xfs_trans_t *tp; xfs_inode_t *ip; int i; int error; const mode_t mode = 0755; tp = libxfs_trans_alloc(mp, 0); ip = NULL; if ((i = libxfs_trans_reserve(tp, 10, XFS_ICHANGE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_MKDIR_LOG_COUNT))) res_failed(i); error = libxfs_trans_iget(mp, tp, mp->m_sb.sb_rootino, 0, &ip); if (error) { do_error(_("could not iget root inode -- error - %d\n"), error); } /* * take care of the core -- initialization from xfs_ialloc() */ bzero(&ip->i_d, sizeof(xfs_dinode_core_t)); ip->i_d.di_magic = XFS_DINODE_MAGIC; ip->i_d.di_mode = (__uint16_t) mode|S_IFDIR; ip->i_d.di_version = XFS_DINODE_VERSION_1; ip->i_d.di_format = XFS_DINODE_FMT_EXTENTS; ip->i_d.di_aformat = XFS_DINODE_FMT_EXTENTS; ip->i_d.di_nlink = 1; /* account for . */ libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); /* * now the ifork */ ip->i_df.if_flags = XFS_IFEXTENTS; ip->i_df.if_bytes = ip->i_df.if_real_bytes = 0; ip->i_df.if_u1.if_extents = NULL; mp->m_rootip = ip; /* * initialize the directory */ dir_init(mp, tp, ip, ip); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); } /* * orphanage name == lost+found */ xfs_ino_t mk_orphanage(xfs_mount_t *mp) { xfs_ino_t ino; xfs_trans_t *tp; xfs_inode_t *ip; xfs_inode_t *pip; xfs_fsblock_t first; int i; int committed; int error; xfs_bmap_free_t flist; const int mode = 0755; const int uid = 0; const int gid = 0; int nres; tp = libxfs_trans_alloc(mp, 0); XFS_BMAP_INIT(&flist, &first); nres = XFS_MKDIR_SPACE_RES(mp, strlen(ORPHANAGE)); if ((i = libxfs_trans_reserve(tp, nres, XFS_MKDIR_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_MKDIR_LOG_COUNT))) res_failed(i); /* * use iget/ijoin instead of trans_iget because the ialloc * wrapper can commit the transaction and start a new one */ if ((i = libxfs_iget(mp, NULL, mp->m_sb.sb_rootino, 0, &pip, 0))) do_error(_("%d - couldn't iget root inode to make %s\n"), i, ORPHANAGE); error = libxfs_inode_alloc(&tp, pip, mode|S_IFDIR, 1, 0, &zerocr, &ip); if (error) { do_error(_("%s inode allocation failed %d\n"), ORPHANAGE, error); } ip->i_d.di_uid = uid; ip->i_d.di_gid = gid; ip->i_d.di_nlink++; /* account for . */ /* * now that we know the transaction will stay around, * add the root inode to it */ libxfs_trans_ijoin(tp, pip, 0); /* * create the actual entry */ if ((error = dir_createname(mp, tp, pip, ORPHANAGE, strlen(ORPHANAGE), ip->i_ino, &first, &flist, nres))) { do_warn( _("can't make %s, createname error %d, will try later\n"), ORPHANAGE, error); orphanage_entered = 0; } else orphanage_entered = 1; /* * bump up the link count in the root directory to account * for .. in the new directory */ pip->i_d.di_nlink++; libxfs_trans_log_inode(tp, pip, XFS_ILOG_CORE); dir_init(mp, tp, ip, pip); libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); error = libxfs_bmap_finish(&tp, &flist, first, &committed); if (error) { do_error(_("%s directory creation failed -- bmapf error %d\n"), ORPHANAGE, error); } ino = ip->i_ino; libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); /* need libxfs_iput here? - nathans TODO - possible memory leak? */ return(ino); } /* * move a file to the orphange. the orphanage is guaranteed * at this point to only have file in it whose name == file inode # */ void mv_orphanage(xfs_mount_t *mp, xfs_ino_t dir_ino, /* orphange inode # */ xfs_ino_t ino, /* inode # to be moved */ int isa_dir) /* 1 if inode is a directory */ { xfs_ino_t entry_ino_num; xfs_inode_t *dir_ino_p; xfs_inode_t *ino_p; xfs_trans_t *tp; xfs_fsblock_t first; xfs_bmap_free_t flist; int err; int committed; char fname[MAXPATHLEN + 1]; int nres; snprintf(fname, sizeof(fname), "%llu", (unsigned long long)ino); if ((err = libxfs_iget(mp, NULL, dir_ino, 0, &dir_ino_p, 0))) do_error(_("%d - couldn't iget orphanage inode\n"), err); tp = libxfs_trans_alloc(mp, 0); if ((err = libxfs_iget(mp, NULL, ino, 0, &ino_p, 0))) do_error(_("%d - couldn't iget disconnected inode\n"), err); if (isa_dir) { nres = XFS_DIRENTER_SPACE_RES(mp, strlen(fname)) + XFS_DIRENTER_SPACE_RES(mp, 2); if ((err = dir_lookup(mp, tp, ino_p, "..", 2, &entry_ino_num))) { ASSERT(err == ENOENT); if ((err = libxfs_trans_reserve(tp, nres, XFS_RENAME_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_RENAME_LOG_COUNT))) do_error( _("space reservation failed (%d), filesystem may be out of space\n"), err); libxfs_trans_ijoin(tp, dir_ino_p, 0); libxfs_trans_ijoin(tp, ino_p, 0); XFS_BMAP_INIT(&flist, &first); if ((err = dir_createname(mp, tp, dir_ino_p, fname, strlen(fname), ino, &first, &flist, nres))) do_error( _("name create failed in %s (%d), filesystem may be out of space\n"), ORPHANAGE, err); dir_ino_p->i_d.di_nlink++; libxfs_trans_log_inode(tp, dir_ino_p, XFS_ILOG_CORE); if ((err = dir_createname(mp, tp, ino_p, "..", 2, dir_ino, &first, &flist, nres))) do_error( _("creation of .. entry failed (%d), filesystem may be out of space\n"), err); ino_p->i_d.di_nlink++; libxfs_trans_log_inode(tp, ino_p, XFS_ILOG_CORE); if ((err = libxfs_bmap_finish(&tp, &flist, first, &committed))) do_error( _("bmap finish failed (err - %d), filesystem may be out of space\n"), err); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); } else { if ((err = libxfs_trans_reserve(tp, nres, XFS_RENAME_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_RENAME_LOG_COUNT))) do_error( _("space reservation failed (%d), filesystem may be out of space\n"), err); libxfs_trans_ijoin(tp, dir_ino_p, 0); libxfs_trans_ijoin(tp, ino_p, 0); XFS_BMAP_INIT(&flist, &first); if ((err = dir_createname(mp, tp, dir_ino_p, fname, strlen(fname), ino, &first, &flist, nres))) do_error( _("name create failed in %s (%d), filesystem may be out of space\n"), ORPHANAGE, err); dir_ino_p->i_d.di_nlink++; libxfs_trans_log_inode(tp, dir_ino_p, XFS_ILOG_CORE); /* * don't replace .. value if it already points * to us. that'll pop a libxfs/kernel ASSERT. */ if (entry_ino_num != dir_ino) { if ((err = dir_replace(mp, tp, ino_p, "..", 2, dir_ino, &first, &flist, nres))) do_error( _("name replace op failed (%d), filesystem may be out of space\n"), err); } if ((err = libxfs_bmap_finish(&tp, &flist, first, &committed))) do_error( _("bmap finish failed (%d), filesystem may be out of space\n"), err); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); } } else { /* * use the remove log reservation as that's * more accurate. we're only creating the * links, we're not doing the inode allocation * also accounted for in the create */ nres = XFS_DIRENTER_SPACE_RES(mp, strlen(fname)); if ((err = libxfs_trans_reserve(tp, nres, XFS_REMOVE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_REMOVE_LOG_COUNT))) do_error( _("space reservation failed (%d), filesystem may be out of space\n"), err); libxfs_trans_ijoin(tp, dir_ino_p, 0); libxfs_trans_ijoin(tp, ino_p, 0); XFS_BMAP_INIT(&flist, &first); if ((err = dir_createname(mp, tp, dir_ino_p, fname, strlen(fname), ino, &first, &flist, nres))) do_error( _("name create failed in %s (%d), filesystem may be out of space\n"), ORPHANAGE, err); ASSERT(err == 0); ino_p->i_d.di_nlink = 1; libxfs_trans_log_inode(tp, ino_p, XFS_ILOG_CORE); if ((err = libxfs_bmap_finish(&tp, &flist, first, &committed))) do_error( _("bmap finish failed (%d), filesystem may be out of space\n"), err); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); } } /* * like get_first_dblock_fsbno only it uses the simulation code instead * of raw I/O. * * Returns the fsbno of the first (leftmost) block in the directory leaf. * sets *bno to the directory block # corresponding to the returned fsbno. */ xfs_dfsbno_t map_first_dblock_fsbno(xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip, xfs_dablk_t *bno) { xfs_fsblock_t fblock; xfs_da_intnode_t *node; xfs_buf_t *bp; xfs_dablk_t da_bno; xfs_dfsbno_t fsbno; xfs_bmbt_irec_t map; int nmap; int i; int error; char *ftype; /* * traverse down left-side of tree until we hit the * left-most leaf block setting up the btree cursor along * the way. */ da_bno = 0; *bno = 0; i = -1; node = NULL; fblock = NULLFSBLOCK; ftype = _("dir"); nmap = 1; error = libxfs_bmapi(NULL, ip, (xfs_fileoff_t) da_bno, 1, XFS_BMAPI_METADATA, &fblock, 0, &map, &nmap, NULL); if (error || nmap != 1) { if (!no_modify) do_error( _("can't map block %d in %s inode %llu, xfs_bmapi returns %d, nmap = %d\n"), da_bno, ftype, ino, error, nmap); else { do_warn( _("can't map block %d in %s inode %llu, xfs_bmapi returns %d, nmap = %d\n"), da_bno, ftype, ino, error, nmap); return(NULLDFSBNO); } } if ((fsbno = map.br_startblock) == HOLESTARTBLOCK) { if (!no_modify) do_error(_("block %d in %s ino %llu doesn't exist\n"), da_bno, ftype, ino); else { do_warn(_("block %d in %s ino %llu doesn't exist\n"), da_bno, ftype, ino); return(NULLDFSBNO); } } if (ip->i_d.di_size <= XFS_LBSIZE(mp)) return(fsbno); if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) return(fsbno); do { /* * walk down left side of btree, release buffers as you * go. if the root block is a leaf (single-level btree), * just return it. * */ bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), XFS_FSB_TO_BB(mp, 1), 0); if (!bp) { do_warn( _("can't read block %u (fsbno %llu) for directory inode %llu\n"), da_bno, fsbno, ino); return(NULLDFSBNO); } node = (xfs_da_intnode_t *)XFS_BUF_PTR(bp); if (INT_GET(node->hdr.info.magic, ARCH_CONVERT) != XFS_DA_NODE_MAGIC) { libxfs_putbuf(bp); do_warn( _("bad dir/attr magic number in inode %llu, file bno = %u, fsbno = %llu\n"), ino, da_bno, fsbno); return(NULLDFSBNO); } if (i == -1) i = INT_GET(node->hdr.level, ARCH_CONVERT); da_bno = INT_GET(node->btree[0].before, ARCH_CONVERT); libxfs_putbuf(bp); bp = NULL; nmap = 1; error = libxfs_bmapi(NULL, ip, (xfs_fileoff_t) da_bno, 1, XFS_BMAPI_METADATA, &fblock, 0, &map, &nmap, NULL); if (error || nmap != 1) { if (!no_modify) do_error( _("can't map block %d in %s ino %llu, xfs_bmapi returns %d, nmap = %d\n"), da_bno, ftype, ino, error, nmap); else { do_warn( _("can't map block %d in %s ino %llu, xfs_bmapi returns %d, nmap = %d\n"), da_bno, ftype, ino, error, nmap); return(NULLDFSBNO); } } if ((fsbno = map.br_startblock) == HOLESTARTBLOCK) { if (!no_modify) do_error( _("block %d in %s inode %llu doesn't exist\n"), da_bno, ftype, ino); else { do_warn( _("block %d in %s inode %llu doesn't exist\n"), da_bno, ftype, ino); return(NULLDFSBNO); } } i--; } while(i > 0); *bno = da_bno; return(fsbno); } /* * scan longform directory and prune first bad entry. returns 1 if * it had to remove something, 0 if it made it all the way through * the directory. prune_lf_dir_entry does all the necessary bmap calls. * * hashval is an in/out -- starting hashvalue in, hashvalue of the * deleted entry (if there was one) out * * this routine can NOT be called if running in no modify mode */ int prune_lf_dir_entry(xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip, xfs_dahash_t *hashval) { xfs_dfsbno_t fsbno; int i; int index; int error; int namelen; xfs_bmap_free_t free_list; xfs_fsblock_t first_block; xfs_buf_t *bp; xfs_dir_leaf_name_t *namest; xfs_dir_leafblock_t *leaf; xfs_dir_leaf_entry_t *entry; xfs_trans_t *tp; xfs_dablk_t da_bno; xfs_fsblock_t fblock; int committed; int nmap; xfs_bmbt_irec_t map; char fname[MAXNAMELEN + 1]; char *ftype; int nres; /* * ok, this is kind of a schizoid routine. we use our * internal bmapi routines to walk the directory. when * we find a bogus entry, we release the buffer so * the simulation code doesn't deadlock and use the * sim code to remove the entry. That will cause an * extra bmap traversal to map the block but I think * that's preferable to hacking the bogus removename * function to be really different and then trying to * maintain both versions as time goes on. * * first, grab the dinode and find the right leaf block. */ ftype = _("dir"); da_bno = 0; bp = NULL; namest = NULL; fblock = NULLFSBLOCK; fsbno = map_first_dblock_fsbno(mp, ino, ip, &da_bno); /* * now go foward along the leaves of the btree looking * for an entry beginning with '/' */ do { bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), XFS_FSB_TO_BB(mp, 1), 0); if (!bp) { do_error( _("can't read directory inode %llu (leaf) block %u (fsbno %llu)\n"), ino, da_bno, fsbno); /* NOTREACHED */ } leaf = (xfs_dir_leafblock_t *)XFS_BUF_PTR(bp); ASSERT(INT_GET(leaf->hdr.info.magic, ARCH_CONVERT) == XFS_DIR_LEAF_MAGIC); entry = &leaf->entries[0]; for (index = -1, i = 0; i < INT_GET(leaf->hdr.count, ARCH_CONVERT) && index == -1; i++) { namest = XFS_DIR_LEAF_NAMESTRUCT(leaf, INT_GET(entry->nameidx, ARCH_CONVERT)); if (namest->name[0] != '/') entry++; else index = i; } /* * if we got a bogus entry, exit loop with a pointer to * the leaf block buffer. otherwise, keep trying blocks */ da_bno = INT_GET(leaf->hdr.info.forw, ARCH_CONVERT); if (index == -1) { if (bp != NULL) { libxfs_putbuf(bp); bp = NULL; } /* * map next leaf block unless we've run out */ if (da_bno != 0) { nmap = 1; error = libxfs_bmapi(NULL, ip, (xfs_fileoff_t) da_bno, 1, XFS_BMAPI_METADATA, &fblock, 0, &map, &nmap, NULL); if (error || nmap != 1) do_error( _("can't map block %d in directory %llu, xfs_bmapi returns %d, nmap = %d\n"), da_bno, ino, error, nmap); if ((fsbno = map.br_startblock) == HOLESTARTBLOCK) { do_error( _("%s ino %llu block %d doesn't exist\n"), ftype, ino, da_bno); } } } } while (da_bno != 0 && index == -1); /* * if we hit the edge of the tree with no bad entries, we're done * and the buffer was released. */ if (da_bno == 0 && index == -1) return(0); ASSERT(index >= 0); ASSERT(entry == &leaf->entries[index]); ASSERT(namest == XFS_DIR_LEAF_NAMESTRUCT(leaf, INT_GET(entry->nameidx, ARCH_CONVERT))); /* * snag the info we need out of the directory then release all buffers */ bcopy(namest->name, fname, entry->namelen); fname[entry->namelen] = '\0'; *hashval = INT_GET(entry->hashval, ARCH_CONVERT); namelen = entry->namelen; libxfs_putbuf(bp); /* * ok, now the hard part, blow away the index'th entry in this block * * allocate a remove transaction for it. that's not quite true since * we're only messing with one inode, not two but... */ tp = libxfs_trans_alloc(mp, XFS_TRANS_REMOVE); nres = XFS_REMOVE_SPACE_RES(mp); error = libxfs_trans_reserve(tp, nres, XFS_REMOVE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_REMOVE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); XFS_BMAP_INIT(&free_list, &first_block); error = dir_bogus_removename(mp, tp, ip, fname, &first_block, &free_list, nres, *hashval, namelen); if (error) { do_error( _("couldn't remove bogus entry \"%s\" in\n\tdirectory inode %llu, errno = %d\n"), fname, ino, error); /* NOTREACHED */ } error = libxfs_bmap_finish(&tp, &free_list, first_block, &committed); ASSERT(error == 0); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_SYNC, 0); return(1); } /* * process a leaf block, also checks for .. entry * and corrects it to match what we think .. should be */ void lf_block_dir_entry_check(xfs_mount_t *mp, xfs_ino_t ino, xfs_dir_leafblock_t *leaf, int *dirty, int *num_illegal, int *need_dot, dir_stack_t *stack, ino_tree_node_t *current_irec, int current_ino_offset) { xfs_dir_leaf_entry_t *entry; ino_tree_node_t *irec; xfs_ino_t lino; xfs_ino_t parent; xfs_dir_leaf_name_t *namest; int i; int junkit; int ino_offset; int nbad; char fname[MAXNAMELEN + 1]; entry = &leaf->entries[0]; *dirty = 0; nbad = 0; /* * look at each entry. reference inode pointed to by each * entry in the incore inode tree. * if not a directory, set reached flag, increment link count * if a directory and reached, mark entry as to be deleted. * if a directory, check to see if recorded parent * matches current inode #, * if so, then set reached flag, increment link count * of current and child dir inodes, push the child * directory inode onto the directory stack. * if current inode != parent, then mark entry to be deleted. * * return */ for (i = 0; i < INT_GET(leaf->hdr.count, ARCH_CONVERT); entry++, i++) { /* * snag inode #, update link counts, and make sure * this isn't a loop if the child is a directory */ namest = XFS_DIR_LEAF_NAMESTRUCT(leaf, INT_GET(entry->nameidx, ARCH_CONVERT)); /* * skip bogus entries (leading '/'). they'll be deleted * later */ if (namest->name[0] == '/') { nbad++; continue; } junkit = 0; XFS_DIR_SF_GET_DIRINO_ARCH(&namest->inumber, &lino, ARCH_CONVERT); bcopy(namest->name, fname, entry->namelen); fname[entry->namelen] = '\0'; ASSERT(lino != NULLFSINO); /* * skip the '..' entry since it's checked when the * directory is reached by something else. if it never * gets reached, it'll be moved to the orphanage and we'll * take care of it then. */ if (entry->namelen == 2 && namest->name[0] == '.' && namest->name[1] == '.') { continue; } ASSERT(no_modify || !verify_inum(mp, lino)); /* * special case the . entry. we know there's only one * '.' and only '.' points to itself because bogus entries * got trashed in phase 3 if there were > 1. * bump up link count for '.' but don't set reached * until we're actually reached by another directory * '..' is already accounted for or will be taken care * of when directory is moved to orphanage. */ if (ino == lino) { ASSERT(namest->name[0] == '.' && entry->namelen == 1); add_inode_ref(current_irec, current_ino_offset); *need_dot = 0; continue; } /* * special case the "lost+found" entry if pointing * to where we think lost+found should be. if that's * the case, that's the one we created in phase 6. * just skip it. no need to process it and it's .. * link is already accounted for. */ if (lino == orphanage_ino && strcmp(fname, ORPHANAGE) == 0) continue; /* * skip entries with bogus inumbers if we're in no modify mode */ if (no_modify && verify_inum(mp, lino)) continue; /* * ok, now handle the rest of the cases besides '.' and '..' */ irec = find_inode_rec(XFS_INO_TO_AGNO(mp, lino), XFS_INO_TO_AGINO(mp, lino)); if (irec == NULL) { nbad++; do_warn( _("entry \"%s\" in dir inode %llu points to non-existent inode, "), fname, ino); if (!no_modify) { namest->name[0] = '/'; *dirty = 1; do_warn(_("marking entry to be junked\n")); } else { do_warn(_("would junk entry\n")); } continue; } ino_offset = XFS_INO_TO_AGINO(mp, lino) - irec->ino_startnum; /* * if it's a free inode, blow out the entry. * by now, any inode that we think is free * really is free. */ if (is_inode_free(irec, ino_offset)) { /* * don't complain if this entry points to the old * and now-free lost+found inode */ if (verbose || no_modify || lino != old_orphanage_ino) do_warn( _("entry \"%s\" in dir inode %llu points to free inode %llu"), fname, ino, lino); nbad++; if (!no_modify) { if (verbose || lino != old_orphanage_ino) do_warn( _(", marking entry to be junked\n")); else do_warn("\n"); namest->name[0] = '/'; *dirty = 1; } else { do_warn(_(", would junk entry\n")); } continue; } /* * check easy case first, regular inode, just bump * the link count and continue */ if (!inode_isadir(irec, ino_offset)) { add_inode_reached(irec, ino_offset); continue; } parent = get_inode_parent(irec, ino_offset); ASSERT(parent != 0); /* * bump up the link counts in parent and child * directory but if the link doesn't agree with * the .. in the child, blow out the entry. * if the directory has already been reached, * blow away the entry also. */ if (is_inode_reached(irec, ino_offset)) { junkit = 1; do_warn( _("entry \"%s\" in dir %llu points to an already connected dir inode %llu,\n"), fname, ino, lino); } else if (parent == ino) { add_inode_reached(irec, ino_offset); add_inode_ref(current_irec, current_ino_offset); if (!is_inode_refchecked(lino, irec, ino_offset)) push_dir(stack, lino); } else { junkit = 1; do_warn( _("entry \"%s\" in dir ino %llu not consistent with .. value (%llu) in ino %llu,\n"), fname, ino, parent, lino); } if (junkit) { junkit = 0; nbad++; if (!no_modify) { namest->name[0] = '/'; *dirty = 1; if (verbose || lino != old_orphanage_ino) do_warn( _("\twill clear entry \"%s\"\n"), fname); } else { do_warn(_("\twould clear entry \"%s\"\n"), fname); } } } *num_illegal += nbad; } /* * succeeds or dies, inode never gets dirtied since all changes * happen in file blocks. the inode size and other core info * is already correct, it's just the leaf entries that get altered. */ void longform_dir_entry_check(xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip, int *num_illegal, int *need_dot, dir_stack_t *stack, ino_tree_node_t *irec, int ino_offset) { xfs_dir_leafblock_t *leaf; xfs_buf_t *bp; xfs_dfsbno_t fsbno; xfs_fsblock_t fblock; xfs_dablk_t da_bno; int dirty; int nmap; int error; int skipit; xfs_bmbt_irec_t map; char *ftype; da_bno = 0; fblock = NULLFSBLOCK; *need_dot = 1; ftype = _("dir"); fsbno = map_first_dblock_fsbno(mp, ino, ip, &da_bno); if (fsbno == NULLDFSBNO && no_modify) { do_warn(_("cannot map block 0 of directory inode %llu\n"), ino); return; } do { ASSERT(fsbno != NULLDFSBNO); skipit = 0; bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), XFS_FSB_TO_BB(mp, 1), 0); if (!bp) { do_error( _("can't read block %u (fsbno %llu) for directory inode %llu\n"), da_bno, fsbno, ino); /* NOTREACHED */ } leaf = (xfs_dir_leafblock_t *)XFS_BUF_PTR(bp); da_bno = INT_GET(leaf->hdr.info.forw, ARCH_CONVERT); if (INT_GET(leaf->hdr.info.magic, ARCH_CONVERT) != XFS_DIR_LEAF_MAGIC) { if (!no_modify) { do_error( _("bad magic # (0x%x) for dir ino %llu leaf block (bno %u fsbno %llu)\n"), INT_GET(leaf->hdr.info.magic, ARCH_CONVERT), ino, da_bno, fsbno); /* NOTREACHED */ } else { /* * this block's bad but maybe the * forward pointer is good... */ skipit = 1; dirty = 0; } } if (!skipit) lf_block_dir_entry_check(mp, ino, leaf, &dirty, num_illegal, need_dot, stack, irec, ino_offset); ASSERT(dirty == 0 || (dirty && !no_modify)); if (dirty && !no_modify) libxfs_writebuf(bp, 0); else libxfs_putbuf(bp); bp = NULL; if (da_bno != 0) { nmap = 1; error = libxfs_bmapi(NULL, ip, (xfs_fileoff_t)da_bno, 1, XFS_BMAPI_METADATA, &fblock, 0, &map, &nmap, NULL); if (error || nmap != 1) { if (!no_modify) do_error( _("can't map leaf block %d in dir %llu, xfs_bmapi returns %d, nmap = %d\n"), da_bno, ino, error, nmap); else { do_warn( _("can't map leaf block %d in dir %llu, xfs_bmapi returns %d, nmap = %d\n"), da_bno, ino, error, nmap); return; } } if ((fsbno = map.br_startblock) == HOLESTARTBLOCK) { if (!no_modify) do_error( _("block %d in %s ino %llu doesn't exist\n"), da_bno, ftype, ino); else { do_warn( _("block %d in %s ino %llu doesn't exist\n"), da_bno, ftype, ino); return; } } } } while (da_bno != 0); } /* * Kill a block in a version 2 inode. * Makes its own transaction. */ static void dir2_kill_block( xfs_mount_t *mp, xfs_inode_t *ip, xfs_dablk_t da_bno, xfs_dabuf_t *bp) { xfs_da_args_t args; int committed; int error; xfs_fsblock_t firstblock; xfs_bmap_free_t flist; int nres; xfs_trans_t *tp; tp = libxfs_trans_alloc(mp, 0); nres = XFS_REMOVE_SPACE_RES(mp); error = libxfs_trans_reserve(tp, nres, XFS_REMOVE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_REMOVE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); libxfs_da_bjoin(tp, bp); bzero(&args, sizeof(args)); XFS_BMAP_INIT(&flist, &firstblock); args.dp = ip; args.trans = tp; args.firstblock = &firstblock; args.flist = &flist; args.whichfork = XFS_DATA_FORK; if (da_bno >= mp->m_dirleafblk && da_bno < mp->m_dirfreeblk) error = libxfs_da_shrink_inode(&args, da_bno, bp); else error = libxfs_dir2_shrink_inode(&args, XFS_DIR2_DA_TO_DB(mp, da_bno), bp); if (error) do_error(_("shrink_inode failed inode %llu block %u\n"), ip->i_ino, da_bno); libxfs_bmap_finish(&tp, &flist, firstblock, &committed); libxfs_trans_commit(tp, 0, 0); } /* * process a data block, also checks for .. entry * and corrects it to match what we think .. should be */ static void longform_dir2_entry_check_data( xfs_mount_t *mp, xfs_inode_t *ip, int *num_illegal, int *need_dot, dir_stack_t *stack, ino_tree_node_t *current_irec, int current_ino_offset, xfs_dabuf_t **bpp, dir_hash_tab_t *hashtab, freetab_t **freetabp, xfs_dablk_t da_bno, int isblock) { xfs_dir2_dataptr_t addr; xfs_dir2_leaf_entry_t *blp; xfs_dabuf_t *bp; xfs_dir2_block_tail_t *btp; int committed; xfs_dir2_data_t *d; xfs_dir2_db_t db; xfs_dir2_data_entry_t *dep; xfs_dir2_data_unused_t *dup; char *endptr; int error; xfs_fsblock_t firstblock; xfs_bmap_free_t flist; char fname[MAXNAMELEN + 1]; freetab_t *freetab; int i; int ino_offset; ino_tree_node_t *irec; int junkit; int lastfree; int len; int nbad; int needlog; int needscan; xfs_ino_t parent; char *ptr; xfs_trans_t *tp; int wantmagic; bp = *bpp; d = bp->data; ptr = (char *)d->u; nbad = 0; needscan = needlog = 0; junkit = 0; freetab = *freetabp; if (isblock) { btp = XFS_DIR2_BLOCK_TAIL_P(mp, d); blp = XFS_DIR2_BLOCK_LEAF_P_ARCH(btp, ARCH_CONVERT); endptr = (char *)blp; if (endptr > (char *)btp) endptr = (char *)btp; wantmagic = XFS_DIR2_BLOCK_MAGIC; } else { endptr = (char *)d + mp->m_dirblksize; wantmagic = XFS_DIR2_DATA_MAGIC; } db = XFS_DIR2_DA_TO_DB(mp, da_bno); /* check for data block beyond expected end */ if (freetab->naents <= db) { struct freetab_ent e; *freetabp = freetab = realloc(freetab, FREETAB_SIZE(db + 1)); if (!freetab) { do_error( _("realloc failed in longform_dir2_entry_check_data (%u bytes)\n"), FREETAB_SIZE(db + 1)); } e.v = NULLDATAOFF; e.s = 0; for (i = freetab->naents; i < db; i++) freetab->ents[i] = e; freetab->naents = db + 1; } /* check the data block */ while (ptr < endptr) { /* check for freespace */ dup = (xfs_dir2_data_unused_t *)ptr; if (XFS_DIR2_DATA_FREE_TAG == INT_GET(dup->freetag, ARCH_CONVERT)) { /* check for invalid freespace length */ if (ptr + INT_GET(dup->length, ARCH_CONVERT) > endptr || INT_GET(dup->length, ARCH_CONVERT) == 0 || (INT_GET(dup->length, ARCH_CONVERT) & (XFS_DIR2_DATA_ALIGN - 1))) break; /* check for invalid tag */ if (INT_GET(*XFS_DIR2_DATA_UNUSED_TAG_P_ARCH( dup, ARCH_CONVERT), ARCH_CONVERT) != (char *)dup - (char *)d) break; /* check for block with no data entries */ if ((ptr == (char *)d->u) && (ptr + INT_GET(dup->length, ARCH_CONVERT) >= endptr)) { junkit = 1; *num_illegal += 1; break; } /* continue at the end of the freespace */ ptr += INT_GET(dup->length, ARCH_CONVERT); if (ptr >= endptr) break; } /* validate data entry size */ dep = (xfs_dir2_data_entry_t *)ptr; if (ptr + XFS_DIR2_DATA_ENTSIZE(dep->namelen) > endptr) break; if (INT_GET(*XFS_DIR2_DATA_ENTRY_TAG_P(dep), ARCH_CONVERT) != (char *)dep - (char *)d) break; ptr += XFS_DIR2_DATA_ENTSIZE(dep->namelen); } /* did we find an empty or corrupt block? */ if (ptr != endptr) { if (junkit) { do_warn( _("empty data block %u in directory inode %llu: "), da_bno, ip->i_ino); } else { do_warn(_("corrupt block %u in directory inode %llu: "), da_bno, ip->i_ino); } if (!no_modify) { do_warn(_("junking block\n")); dir2_kill_block(mp, ip, da_bno, bp); } else { do_warn(_("would junk block\n")); libxfs_da_brelse(NULL, bp); } freetab->ents[db].v = NULLDATAOFF; *bpp = NULL; return; } /* update number of data blocks processed */ if (freetab->nents < db + 1) freetab->nents = db + 1; tp = libxfs_trans_alloc(mp, 0); error = libxfs_trans_reserve(tp, 0, XFS_REMOVE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_REMOVE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); libxfs_da_bjoin(tp, bp); if (isblock) libxfs_da_bhold(tp, bp); XFS_BMAP_INIT(&flist, &firstblock); if (INT_GET(d->hdr.magic, ARCH_CONVERT) != wantmagic) { do_warn(_("bad directory block magic # %#x for directory inode " "%llu block %d: "), INT_GET(d->hdr.magic, ARCH_CONVERT), ip->i_ino, da_bno); if (!no_modify) { do_warn(_("fixing magic # to %#x\n"), wantmagic); INT_SET(d->hdr.magic, ARCH_CONVERT, wantmagic); needlog = 1; } else do_warn(_("would fix magic # to %#x\n"), wantmagic); } lastfree = 0; ptr = (char *)d->u; /* * look at each entry. reference inode pointed to by each * entry in the incore inode tree. * if not a directory, set reached flag, increment link count * if a directory and reached, mark entry as to be deleted. * if a directory, check to see if recorded parent * matches current inode #, * if so, then set reached flag, increment link count * of current and child dir inodes, push the child * directory inode onto the directory stack. * if current inode != parent, then mark entry to be deleted. */ while (ptr < endptr) { dup = (xfs_dir2_data_unused_t *)ptr; if (INT_GET(dup->freetag, ARCH_CONVERT) == XFS_DIR2_DATA_FREE_TAG) { if (lastfree) { do_warn(_("directory inode %llu block %u has " "consecutive free entries: "), ip->i_ino, da_bno); if (!no_modify) { do_warn(_("joining together\n")); len = INT_GET(dup->length, ARCH_CONVERT); libxfs_dir2_data_use_free(tp, bp, dup, ptr - (char *)d, len, &needlog, &needscan); libxfs_dir2_data_make_free(tp, bp, ptr - (char *)d, len, &needlog, &needscan); } else do_warn(_("would join together\n")); } ptr += INT_GET(dup->length, ARCH_CONVERT); lastfree = 1; continue; } addr = XFS_DIR2_DB_OFF_TO_DATAPTR(mp, db, ptr - (char *)d); dep = (xfs_dir2_data_entry_t *)ptr; ptr += XFS_DIR2_DATA_ENTSIZE(dep->namelen); lastfree = 0; dir_hash_add(hashtab, libxfs_da_hashname((char *)dep->name, dep->namelen), addr, dep->name[0] == '/'); /* * skip bogus entries (leading '/'). they'll be deleted * later */ if (dep->name[0] == '/') { nbad++; continue; } junkit = 0; bcopy(dep->name, fname, dep->namelen); fname[dep->namelen] = '\0'; ASSERT(INT_GET(dep->inumber, ARCH_CONVERT) != NULLFSINO); /* * skip the '..' entry since it's checked when the * directory is reached by something else. if it never * gets reached, it'll be moved to the orphanage and we'll * take care of it then. */ if (dep->namelen == 2 && dep->name[0] == '.' && dep->name[1] == '.') continue; ASSERT(no_modify || !verify_inum(mp, INT_GET(dep->inumber, ARCH_CONVERT))); /* * special case the . entry. we know there's only one * '.' and only '.' points to itself because bogus entries * got trashed in phase 3 if there were > 1. * bump up link count for '.' but don't set reached * until we're actually reached by another directory * '..' is already accounted for or will be taken care * of when directory is moved to orphanage. */ if (ip->i_ino == INT_GET(dep->inumber, ARCH_CONVERT)) { ASSERT(dep->name[0] == '.' && dep->namelen == 1); add_inode_ref(current_irec, current_ino_offset); *need_dot = 0; continue; } /* * special case the "lost+found" entry if pointing * to where we think lost+found should be. if that's * the case, that's the one we created in phase 6. * just skip it. no need to process it and it's .. * link is already accounted for. */ if (INT_GET(dep->inumber, ARCH_CONVERT) == orphanage_ino && strcmp(fname, ORPHANAGE) == 0) continue; /* * skip entries with bogus inumbers if we're in no modify mode */ if (no_modify && verify_inum(mp, INT_GET(dep->inumber, ARCH_CONVERT))) continue; /* * ok, now handle the rest of the cases besides '.' and '..' */ irec = find_inode_rec( XFS_INO_TO_AGNO(mp, INT_GET(dep->inumber, ARCH_CONVERT)), XFS_INO_TO_AGINO(mp, INT_GET(dep->inumber, ARCH_CONVERT))); if (irec == NULL) { nbad++; do_warn(_("entry \"%s\" in directory inode %llu points " "to non-existent inode, "), fname, ip->i_ino); if (!no_modify) { dep->name[0] = '/'; libxfs_dir2_data_log_entry(tp, bp, dep); do_warn(_("marking entry to be junked\n")); } else { do_warn(_("would junk entry\n")); } continue; } ino_offset = XFS_INO_TO_AGINO(mp, INT_GET(dep->inumber, ARCH_CONVERT)) - irec->ino_startnum; /* * if it's a free inode, blow out the entry. * by now, any inode that we think is free * really is free. */ if (is_inode_free(irec, ino_offset)) { /* * don't complain if this entry points to the old * and now-free lost+found inode */ if (verbose || no_modify || INT_GET(dep->inumber, ARCH_CONVERT) != old_orphanage_ino) do_warn( _("entry \"%s\" in directory inode %llu points to free inode %llu"), fname, ip->i_ino, INT_GET(dep->inumber, ARCH_CONVERT)); nbad++; if (!no_modify) { if (verbose || INT_GET(dep->inumber, ARCH_CONVERT) != old_orphanage_ino) do_warn( _(", marking entry to be junked\n")); else do_warn("\n"); dep->name[0] = '/'; libxfs_dir2_data_log_entry(tp, bp, dep); } else { do_warn(_(", would junk entry\n")); } continue; } /* * check easy case first, regular inode, just bump * the link count and continue */ if (!inode_isadir(irec, ino_offset)) { add_inode_reached(irec, ino_offset); continue; } parent = get_inode_parent(irec, ino_offset); ASSERT(parent != 0); /* * bump up the link counts in parent and child * directory but if the link doesn't agree with * the .. in the child, blow out the entry. * if the directory has already been reached, * blow away the entry also. */ if (is_inode_reached(irec, ino_offset)) { junkit = 1; do_warn( _("entry \"%s\" in dir %llu points to an already connected directory inode %llu,\n"), fname, ip->i_ino, INT_GET(dep->inumber, ARCH_CONVERT)); } else if (parent == ip->i_ino) { add_inode_reached(irec, ino_offset); add_inode_ref(current_irec, current_ino_offset); if (!is_inode_refchecked( INT_GET(dep->inumber, ARCH_CONVERT), irec, ino_offset)) push_dir(stack, INT_GET(dep->inumber, ARCH_CONVERT)); } else { junkit = 1; do_warn( _("entry \"%s\" in dir inode %llu inconsistent with .. value (%llu) in ino %llu,\n"), fname, ip->i_ino, parent, INT_GET(dep->inumber, ARCH_CONVERT)); } if (junkit) { junkit = 0; nbad++; if (!no_modify) { dep->name[0] = '/'; libxfs_dir2_data_log_entry(tp, bp, dep); if (verbose || INT_GET(dep->inumber, ARCH_CONVERT) != old_orphanage_ino) do_warn( _("\twill clear entry \"%s\"\n"), fname); } else { do_warn(_("\twould clear entry \"%s\"\n"), fname); } } } *num_illegal += nbad; if (needscan) libxfs_dir2_data_freescan(mp, d, &needlog, NULL); if (needlog) libxfs_dir2_data_log_header(tp, bp); libxfs_bmap_finish(&tp, &flist, firstblock, &committed); libxfs_trans_commit(tp, 0, 0); freetab->ents[db].v = INT_GET(d->hdr.bestfree[0].length, ARCH_CONVERT); freetab->ents[db].s = 0; } /* * Check contents of leaf-form block. */ int longform_dir2_check_leaf( xfs_mount_t *mp, xfs_inode_t *ip, dir_hash_tab_t *hashtab, freetab_t *freetab) { int badtail; xfs_dir2_data_off_t *bestsp; xfs_dabuf_t *bp; xfs_dablk_t da_bno; int i; xfs_dir2_leaf_t *leaf; xfs_dir2_leaf_tail_t *ltp; int seeval; da_bno = mp->m_dirleafblk; if (libxfs_da_read_bufr(NULL, ip, da_bno, -1, &bp, XFS_DATA_FORK)) { do_error(_("can't read block %u for directory inode %llu\n"), da_bno, ip->i_ino); /* NOTREACHED */ } leaf = bp->data; ltp = XFS_DIR2_LEAF_TAIL_P(mp, leaf); bestsp = XFS_DIR2_LEAF_BESTS_P_ARCH(ltp, ARCH_CONVERT); if (INT_GET(leaf->hdr.info.magic, ARCH_CONVERT) != XFS_DIR2_LEAF1_MAGIC || INT_GET(leaf->hdr.info.forw, ARCH_CONVERT) || INT_GET(leaf->hdr.info.back, ARCH_CONVERT) || INT_GET(leaf->hdr.count, ARCH_CONVERT) < INT_GET(leaf->hdr.stale, ARCH_CONVERT) || INT_GET(leaf->hdr.count, ARCH_CONVERT) > XFS_DIR2_MAX_LEAF_ENTS(mp) || (char *)&leaf->ents[INT_GET(leaf->hdr.count, ARCH_CONVERT)] > (char *)bestsp) { do_warn( _("leaf block %u for directory inode %llu bad header\n"), da_bno, ip->i_ino); libxfs_da_brelse(NULL, bp); return 1; } seeval = dir_hash_see_all(hashtab, leaf->ents, INT_GET(leaf->hdr.count, ARCH_CONVERT), INT_GET(leaf->hdr.stale, ARCH_CONVERT)); if (dir_hash_check(hashtab, ip, seeval)) { libxfs_da_brelse(NULL, bp); return 1; } badtail = freetab->nents != INT_GET(ltp->bestcount, ARCH_CONVERT); for (i = 0; !badtail && i < INT_GET(ltp->bestcount, ARCH_CONVERT); i++) { freetab->ents[i].s = 1; badtail = freetab->ents[i].v != INT_GET(bestsp[i], ARCH_CONVERT); } if (badtail) { do_warn(_("leaf block %u for directory inode %llu bad tail\n"), da_bno, ip->i_ino); libxfs_da_brelse(NULL, bp); return 1; } libxfs_da_brelse(NULL, bp); return 0; } /* * Check contents of the node blocks (leaves) * Looks for matching hash values for the data entries. */ int longform_dir2_check_node( xfs_mount_t *mp, xfs_inode_t *ip, dir_hash_tab_t *hashtab, freetab_t *freetab) { xfs_dabuf_t *bp; xfs_dablk_t da_bno; xfs_dir2_db_t fdb; xfs_dir2_free_t *free; int i; xfs_dir2_leaf_t *leaf; xfs_fileoff_t next_da_bno; int seeval = 0; int used; for (da_bno = mp->m_dirleafblk, next_da_bno = 0; next_da_bno != NULLFILEOFF && da_bno < mp->m_dirfreeblk; da_bno = (xfs_dablk_t)next_da_bno) { next_da_bno = da_bno + mp->m_dirblkfsbs - 1; if (libxfs_bmap_next_offset(NULL, ip, &next_da_bno, XFS_DATA_FORK)) break; if (libxfs_da_read_bufr(NULL, ip, da_bno, -1, &bp, XFS_DATA_FORK)) { do_error( _("can't read block %u for directory inode %llu\n"), da_bno, ip->i_ino); /* NOTREACHED */ } leaf = bp->data; if (INT_GET(leaf->hdr.info.magic, ARCH_CONVERT) != XFS_DIR2_LEAFN_MAGIC) { if (INT_GET(leaf->hdr.info.magic, ARCH_CONVERT) == XFS_DA_NODE_MAGIC) { libxfs_da_brelse(NULL, bp); continue; } do_warn(_("unknown magic number %#x for block %u in " "directory inode %llu\n"), INT_GET(leaf->hdr.info.magic, ARCH_CONVERT), da_bno, ip->i_ino); libxfs_da_brelse(NULL, bp); return 1; } if (INT_GET(leaf->hdr.count, ARCH_CONVERT) < INT_GET(leaf->hdr.stale, ARCH_CONVERT) || INT_GET(leaf->hdr.count, ARCH_CONVERT) > XFS_DIR2_MAX_LEAF_ENTS(mp)) { do_warn(_("leaf block %u for directory inode %llu bad " "header\n"), da_bno, ip->i_ino); libxfs_da_brelse(NULL, bp); return 1; } seeval = dir_hash_see_all(hashtab, leaf->ents, INT_GET(leaf->hdr.count, ARCH_CONVERT), INT_GET(leaf->hdr.stale, ARCH_CONVERT)); libxfs_da_brelse(NULL, bp); if (seeval != DIR_HASH_CK_OK) return 1; } if (dir_hash_check(hashtab, ip, seeval)) return 1; for (da_bno = mp->m_dirfreeblk, next_da_bno = 0; next_da_bno != NULLFILEOFF; da_bno = (xfs_dablk_t)next_da_bno) { next_da_bno = da_bno + mp->m_dirblkfsbs - 1; if (libxfs_bmap_next_offset(NULL, ip, &next_da_bno, XFS_DATA_FORK)) break; if (libxfs_da_read_bufr(NULL, ip, da_bno, -1, &bp, XFS_DATA_FORK)) { do_error(_("can't read block %u for directory inode " "%llu\n"), da_bno, ip->i_ino); /* NOTREACHED */ } free = bp->data; fdb = XFS_DIR2_DA_TO_DB(mp, da_bno); if (INT_GET(free->hdr.magic, ARCH_CONVERT) != XFS_DIR2_FREE_MAGIC || INT_GET(free->hdr.firstdb, ARCH_CONVERT) != (fdb - XFS_DIR2_FREE_FIRSTDB(mp)) * XFS_DIR2_MAX_FREE_BESTS(mp) || INT_GET(free->hdr.nvalid, ARCH_CONVERT) < INT_GET(free->hdr.nused, ARCH_CONVERT)) { do_warn(_("free block %u for directory inode %llu bad " "header\n"), da_bno, ip->i_ino); libxfs_da_brelse(NULL, bp); return 1; } for (i = used = 0; i < INT_GET(free->hdr.nvalid, ARCH_CONVERT); i++) { if (i + INT_GET(free->hdr.firstdb, ARCH_CONVERT) >= freetab->nents || freetab->ents[i + INT_GET(free->hdr.firstdb, ARCH_CONVERT)].v != INT_GET(free->bests[i], ARCH_CONVERT)) { do_warn( _("free block %u entry %i for directory ino %llu bad\n"), da_bno, i, ip->i_ino); libxfs_da_brelse(NULL, bp); return 1; } used += INT_GET(free->bests[i], ARCH_CONVERT) != NULLDATAOFF; freetab->ents[i + INT_GET(free->hdr.firstdb, ARCH_CONVERT)].s = 1; } if (used != INT_GET(free->hdr.nused, ARCH_CONVERT)) { do_warn(_("free block %u for directory inode %llu bad " "nused\n"), da_bno, ip->i_ino); libxfs_da_brelse(NULL, bp); return 1; } libxfs_da_brelse(NULL, bp); } for (i = 0; i < freetab->nents; i++) { if (freetab->ents[i].s == 0) { do_warn(_("missing freetab entry %u for " "directory inode %llu\n"), i, ip->i_ino); return 1; } } return 0; } /* * Rebuild a directory: set up. * Turn it into a node-format directory with no contents in the * upper area. Also has correct freespace blocks. */ void longform_dir2_rebuild_setup( xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip, freetab_t *freetab) { xfs_da_args_t args; int committed; xfs_dir2_data_t *data = NULL; xfs_dabuf_t *dbp; int error; xfs_dir2_db_t fbno; xfs_dabuf_t *fbp; xfs_fsblock_t firstblock; xfs_bmap_free_t flist; xfs_dir2_free_t *free; int i; int j; xfs_dablk_t lblkno; xfs_dabuf_t *lbp; xfs_dir2_leaf_t *leaf; int nres; xfs_trans_t *tp; /* read first directory block */ tp = libxfs_trans_alloc(mp, 0); nres = XFS_DAENTER_SPACE_RES(mp, XFS_DATA_FORK); error = libxfs_trans_reserve(tp, nres, XFS_CREATE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_CREATE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); XFS_BMAP_INIT(&flist, &firstblock); if (libxfs_da_read_buf(tp, ip, mp->m_dirdatablk, -2, &dbp, XFS_DATA_FORK)) { do_error(_("can't read block %u for directory inode %llu\n"), mp->m_dirdatablk, ino); /* NOTREACHED */ } if (dbp) data = dbp->data; /* check for block format directory */ if (data && INT_GET((data)->hdr.magic, ARCH_CONVERT) == XFS_DIR2_BLOCK_MAGIC) { xfs_dir2_block_t *block; xfs_dir2_leaf_entry_t *blp; xfs_dir2_block_tail_t *btp; int needlog; int needscan; /* convert directory block from block format to data format */ INT_SET(data->hdr.magic, ARCH_CONVERT, XFS_DIR2_DATA_MAGIC); /* construct freelist */ block = (xfs_dir2_block_t *)data; btp = XFS_DIR2_BLOCK_TAIL_P(mp, block); blp = XFS_DIR2_BLOCK_LEAF_P_ARCH(btp, ARCH_CONVERT); needlog = needscan = 0; libxfs_dir2_data_make_free(tp, dbp, (char *)blp - (char *)block, (char *)block + mp->m_dirblksize - (char *)blp, &needlog, &needscan); if (needscan) libxfs_dir2_data_freescan(mp, data, &needlog, NULL); libxfs_da_log_buf(tp, dbp, 0, mp->m_dirblksize - 1); } /* allocate blocks for btree */ bzero(&args, sizeof(args)); args.trans = tp; args.dp = ip; args.whichfork = XFS_DATA_FORK; args.firstblock = &firstblock; args.flist = &flist; args.total = nres; if ((error = libxfs_da_grow_inode(&args, &lblkno)) || (error = libxfs_da_get_buf(tp, ip, lblkno, -1, &lbp, XFS_DATA_FORK))) { do_error(_("can't add btree block to directory inode %llu\n"), ino); /* NOTREACHED */ } leaf = lbp->data; bzero(leaf, mp->m_dirblksize); INT_SET(leaf->hdr.info.magic, ARCH_CONVERT, XFS_DIR2_LEAFN_MAGIC); libxfs_da_log_buf(tp, lbp, 0, mp->m_dirblksize - 1); libxfs_bmap_finish(&tp, &flist, firstblock, &committed); libxfs_trans_commit(tp, 0, 0); for (i = 0; i < freetab->nents; i += XFS_DIR2_MAX_FREE_BESTS(mp)) { tp = libxfs_trans_alloc(mp, 0); nres = XFS_DAENTER_SPACE_RES(mp, XFS_DATA_FORK); error = libxfs_trans_reserve(tp, nres, XFS_CREATE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_CREATE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); XFS_BMAP_INIT(&flist, &firstblock); bzero(&args, sizeof(args)); args.trans = tp; args.dp = ip; args.whichfork = XFS_DATA_FORK; args.firstblock = &firstblock; args.flist = &flist; args.total = nres; if ((error = libxfs_dir2_grow_inode(&args, XFS_DIR2_FREE_SPACE, &fbno)) || (error = libxfs_da_get_buf(tp, ip, XFS_DIR2_DB_TO_DA(mp, fbno), -1, &fbp, XFS_DATA_FORK))) { do_error(_("can't add free block to directory inode " "%llu\n"), ino); /* NOTREACHED */ } free = fbp->data; bzero(free, mp->m_dirblksize); INT_SET(free->hdr.magic, ARCH_CONVERT, XFS_DIR2_FREE_MAGIC); INT_SET(free->hdr.firstdb, ARCH_CONVERT, i); INT_SET(free->hdr.nvalid, ARCH_CONVERT, XFS_DIR2_MAX_FREE_BESTS(mp)); if (i + INT_GET(free->hdr.nvalid, ARCH_CONVERT) > freetab->nents) INT_SET(free->hdr.nvalid, ARCH_CONVERT, freetab->nents - i); for (j = 0; j < INT_GET(free->hdr.nvalid, ARCH_CONVERT); j++) { INT_SET(free->bests[j], ARCH_CONVERT, freetab->ents[i + j].v); if (INT_GET(free->bests[j], ARCH_CONVERT) != NULLDATAOFF) INT_MOD(free->hdr.nused, ARCH_CONVERT, +1); } libxfs_da_log_buf(tp, fbp, 0, mp->m_dirblksize - 1); libxfs_bmap_finish(&tp, &flist, firstblock, &committed); libxfs_trans_commit(tp, 0, 0); } } /* * Rebuild the entries from a single data block. */ void longform_dir2_rebuild_data( xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip, xfs_dablk_t da_bno) { xfs_dabuf_t *bp; xfs_dir2_block_tail_t *btp; int committed; xfs_dir2_data_t *data; xfs_dir2_db_t dbno; xfs_dir2_data_entry_t *dep; xfs_dir2_data_unused_t *dup; char *endptr; int error; xfs_dir2_free_t *fblock; xfs_dabuf_t *fbp; xfs_dir2_db_t fdb; int fi; xfs_fsblock_t firstblock; xfs_bmap_free_t flist; int needlog; int needscan; int nres; char *ptr; xfs_trans_t *tp; if (libxfs_da_read_buf(NULL, ip, da_bno, da_bno == 0 ? -2 : -1, &bp, XFS_DATA_FORK)) { do_error(_("can't read block %u for directory inode %llu\n"), da_bno, ino); /* NOTREACHED */ } if (da_bno == 0 && bp == NULL) /* * The block was punched out. */ return; ASSERT(bp); dbno = XFS_DIR2_DA_TO_DB(mp, da_bno); fdb = XFS_DIR2_DB_TO_FDB(mp, dbno); if (libxfs_da_read_buf(NULL, ip, XFS_DIR2_DB_TO_DA(mp, fdb), -1, &fbp, XFS_DATA_FORK)) { do_error(_("can't read block %u for directory inode %llu\n"), XFS_DIR2_DB_TO_DA(mp, fdb), ino); /* NOTREACHED */ } data = malloc(mp->m_dirblksize); if (!data) { do_error( _("malloc failed in longform_dir2_rebuild_data (%u bytes)\n"), mp->m_dirblksize); exit(1); } bcopy(bp->data, data, mp->m_dirblksize); ptr = (char *)data->u; if (INT_GET(data->hdr.magic, ARCH_CONVERT) == XFS_DIR2_BLOCK_MAGIC) { btp = XFS_DIR2_BLOCK_TAIL_P(mp, (xfs_dir2_block_t *)data); endptr = (char *)XFS_DIR2_BLOCK_LEAF_P_ARCH(btp, ARCH_CONVERT); } else endptr = (char *)data + mp->m_dirblksize; fblock = fbp->data; fi = XFS_DIR2_DB_TO_FDINDEX(mp, dbno); tp = libxfs_trans_alloc(mp, 0); error = libxfs_trans_reserve(tp, 0, XFS_CREATE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_CREATE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); libxfs_da_bjoin(tp, bp); libxfs_da_bhold(tp, bp); libxfs_da_bjoin(tp, fbp); libxfs_da_bhold(tp, fbp); XFS_BMAP_INIT(&flist, &firstblock); needlog = needscan = 0; bzero(((xfs_dir2_data_t *)(bp->data))->hdr.bestfree, sizeof(data->hdr.bestfree)); libxfs_dir2_data_make_free(tp, bp, (xfs_dir2_data_aoff_t)sizeof(data->hdr), mp->m_dirblksize - sizeof(data->hdr), &needlog, &needscan); ASSERT(needscan == 0); libxfs_dir2_data_log_header(tp, bp); INT_SET(fblock->bests[fi], ARCH_CONVERT, INT_GET(((xfs_dir2_data_t *)(bp->data))->hdr.bestfree[0].length, ARCH_CONVERT)); libxfs_dir2_free_log_bests(tp, fbp, fi, fi); libxfs_bmap_finish(&tp, &flist, firstblock, &committed); libxfs_trans_commit(tp, 0, 0); while (ptr < endptr) { dup = (xfs_dir2_data_unused_t *)ptr; if (INT_GET(dup->freetag, ARCH_CONVERT) == XFS_DIR2_DATA_FREE_TAG) { ptr += INT_GET(dup->length, ARCH_CONVERT); continue; } dep = (xfs_dir2_data_entry_t *)ptr; ptr += XFS_DIR2_DATA_ENTSIZE(dep->namelen); if (dep->name[0] == '/') continue; tp = libxfs_trans_alloc(mp, 0); nres = XFS_CREATE_SPACE_RES(mp, dep->namelen); error = libxfs_trans_reserve(tp, nres, XFS_CREATE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_CREATE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); libxfs_da_bjoin(tp, bp); libxfs_da_bhold(tp, bp); libxfs_da_bjoin(tp, fbp); libxfs_da_bhold(tp, fbp); XFS_BMAP_INIT(&flist, &firstblock); error = dir_createname(mp, tp, ip, (char *)dep->name, dep->namelen, INT_GET(dep->inumber, ARCH_CONVERT), &firstblock, &flist, nres); ASSERT(error == 0); libxfs_bmap_finish(&tp, &flist, firstblock, &committed); libxfs_trans_commit(tp, 0, 0); } libxfs_da_brelse(NULL, bp); libxfs_da_brelse(NULL, fbp); free(data); } /* * Finish the rebuild of a directory. * Stuff / in and then remove it, this forces the directory to end * up in the right format. */ void longform_dir2_rebuild_finish( xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip) { int committed; int error; xfs_fsblock_t firstblock; xfs_bmap_free_t flist; int nres; xfs_trans_t *tp; tp = libxfs_trans_alloc(mp, 0); nres = XFS_CREATE_SPACE_RES(mp, 1); error = libxfs_trans_reserve(tp, nres, XFS_CREATE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_CREATE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); XFS_BMAP_INIT(&flist, &firstblock); error = dir_createname(mp, tp, ip, "/", 1, ino, &firstblock, &flist, nres); ASSERT(error == 0); libxfs_bmap_finish(&tp, &flist, firstblock, &committed); libxfs_trans_commit(tp, 0, 0); /* could kill trailing empty data blocks here */ tp = libxfs_trans_alloc(mp, 0); nres = XFS_REMOVE_SPACE_RES(mp); error = libxfs_trans_reserve(tp, nres, XFS_REMOVE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_REMOVE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); XFS_BMAP_INIT(&flist, &firstblock); error = dir_removename(mp, tp, ip, "/", 1, ino, &firstblock, &flist, nres); ASSERT(error == 0); libxfs_bmap_finish(&tp, &flist, firstblock, &committed); libxfs_trans_commit(tp, 0, 0); } /* * Rebuild a directory. * Remove all the non-data blocks. * Re-initialize to (empty) node form. * Loop over the data blocks reinserting each entry. * Force the directory into the right format. */ void longform_dir2_rebuild( xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip, int *num_illegal, freetab_t *freetab, int isblock) { xfs_dabuf_t *bp; xfs_dablk_t da_bno; xfs_fileoff_t next_da_bno; do_warn(_("rebuilding directory inode %llu\n"), ino); /* kill leaf blocks */ for (da_bno = mp->m_dirleafblk, next_da_bno = isblock ? NULLFILEOFF : 0; next_da_bno != NULLFILEOFF; da_bno = (xfs_dablk_t)next_da_bno) { next_da_bno = da_bno + mp->m_dirblkfsbs - 1; if (libxfs_bmap_next_offset(NULL, ip, &next_da_bno, XFS_DATA_FORK)) break; if (libxfs_da_get_buf(NULL, ip, da_bno, -1, &bp, XFS_DATA_FORK)) { do_error(_("can't get block %u for directory inode " "%llu\n"), da_bno, ino); /* NOTREACHED */ } dir2_kill_block(mp, ip, da_bno, bp); } /* rebuild empty btree and freelist */ longform_dir2_rebuild_setup(mp, ino, ip, freetab); /* rebuild directory */ for (da_bno = mp->m_dirdatablk, next_da_bno = 0; da_bno < mp->m_dirleafblk && next_da_bno != NULLFILEOFF; da_bno = (xfs_dablk_t)next_da_bno) { next_da_bno = da_bno + mp->m_dirblkfsbs - 1; if (libxfs_bmap_next_offset(NULL, ip, &next_da_bno, XFS_DATA_FORK)) break; longform_dir2_rebuild_data(mp, ino, ip, da_bno); } /* put the directory in the appropriate on-disk format */ longform_dir2_rebuild_finish(mp, ino, ip); *num_illegal = 0; } /* * succeeds or dies, inode never gets dirtied since all changes * happen in file blocks. the inode size and other core info * is already correct, it's just the leaf entries that get altered. * XXX above comment is wrong for v2 - need to see why it matters */ void longform_dir2_entry_check(xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip, int *num_illegal, int *need_dot, dir_stack_t *stack, ino_tree_node_t *irec, int ino_offset) { xfs_dir2_block_t *block; xfs_dir2_leaf_entry_t *blp; xfs_dabuf_t *bp; xfs_dir2_block_tail_t *btp; xfs_dablk_t da_bno; freetab_t *freetab; dir_hash_tab_t *hashtab; int i; int isblock; int isleaf; xfs_fileoff_t next_da_bno; int seeval; int fixit; *need_dot = 1; freetab = malloc(FREETAB_SIZE(ip->i_d.di_size / mp->m_dirblksize)); if (!freetab) { do_error( _("malloc failed in longform_dir2_entry_check (%u bytes)\n"), FREETAB_SIZE(ip->i_d.di_size / mp->m_dirblksize)); exit(1); } freetab->naents = ip->i_d.di_size / mp->m_dirblksize; freetab->nents = 0; for (i = 0; i < freetab->naents; i++) { freetab->ents[i].v = NULLDATAOFF; freetab->ents[i].s = 0; } /* is this a block, leaf, or node directory? */ libxfs_dir2_isblock(NULL, ip, &isblock); libxfs_dir2_isleaf(NULL, ip, &isleaf); /* check directory data */ hashtab = dir_hash_init(ip->i_d.di_size); for (da_bno = 0, next_da_bno = 0; next_da_bno != NULLFILEOFF && da_bno < mp->m_dirleafblk; da_bno = (xfs_dablk_t)next_da_bno) { next_da_bno = da_bno + mp->m_dirblkfsbs - 1; if (libxfs_bmap_next_offset(NULL, ip, &next_da_bno, XFS_DATA_FORK)) break; if (libxfs_da_read_bufr(NULL, ip, da_bno, da_bno == 0 ? -2 : -1, &bp, XFS_DATA_FORK)) { do_error(_("can't read block %u for directory inode " "%llu\n"), da_bno, ino); /* NOTREACHED */ } /* is there a hole at the start? */ if (da_bno == 0 && bp == NULL) continue; longform_dir2_entry_check_data(mp, ip, num_illegal, need_dot, stack, irec, ino_offset, &bp, hashtab, &freetab, da_bno, isblock); /* it releases the buffer unless isblock is set */ } fixit = (*num_illegal != 0) || dir2_is_badino(ino); /* check btree and freespace */ if (isblock) { ASSERT(bp); block = bp->data; btp = XFS_DIR2_BLOCK_TAIL_P(mp, block); blp = XFS_DIR2_BLOCK_LEAF_P_ARCH(btp, ARCH_CONVERT); seeval = dir_hash_see_all(hashtab, blp, INT_GET(btp->count, ARCH_CONVERT), INT_GET(btp->stale, ARCH_CONVERT)); if (dir_hash_check(hashtab, ip, seeval)) fixit |= 1; libxfs_da_brelse(NULL, bp); } else if (isleaf) { fixit |= longform_dir2_check_leaf(mp, ip, hashtab, freetab); } else { fixit |= longform_dir2_check_node(mp, ip, hashtab, freetab); } dir_hash_done(hashtab); if (!no_modify && fixit) longform_dir2_rebuild(mp, ino, ip, num_illegal, freetab, isblock); free(freetab); } /* * shortform directory processing routines -- entry verification and * bad entry deletion (pruning). */ void shortform_dir_entry_check(xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip, int *ino_dirty, dir_stack_t *stack, ino_tree_node_t *current_irec, int current_ino_offset) { xfs_ino_t lino; xfs_ino_t parent; xfs_dir_shortform_t *sf; xfs_dir_sf_entry_t *sf_entry, *next_sfe, *tmp_sfe; xfs_ifork_t *ifp; ino_tree_node_t *irec; int max_size; int ino_offset; int i; int junkit; int tmp_len; int tmp_elen; int bad_sfnamelen; int namelen; int bytes_deleted; char fname[MAXNAMELEN + 1]; ifp = &ip->i_df; sf = (xfs_dir_shortform_t *) ifp->if_u1.if_data; *ino_dirty = 0; bytes_deleted = 0; max_size = ifp->if_bytes; ASSERT(ip->i_d.di_size <= ifp->if_bytes); /* * no '.' entry in shortform dirs, just bump up ref count by 1 * '..' was already (or will be) accounted for and checked when * the directory is reached or will be taken care of when the * directory is moved to orphanage. */ add_inode_ref(current_irec, current_ino_offset); /* * now run through entries, stop at first bad entry, don't need * to skip over '..' since that's encoded in its own field and * no need to worry about '.' since it doesn't exist. */ sf_entry = next_sfe = &sf->list[0]; if (sf == NULL) { junkit = 1; do_warn(_("shortform dir inode %llu has null data entries \n"), ino); } else { for (i = 0; i < INT_GET(sf->hdr.count, ARCH_CONVERT) && max_size > (__psint_t)next_sfe - (__psint_t)sf; sf_entry = next_sfe, i++) { junkit = 0; bad_sfnamelen = 0; tmp_sfe = NULL; XFS_DIR_SF_GET_DIRINO_ARCH(&sf_entry->inumber, &lino, ARCH_CONVERT); namelen = sf_entry->namelen; ASSERT(no_modify || namelen > 0); if (no_modify && namelen == 0) { /* * if we're really lucky, this is * the last entry in which case we * can use the dir size to set the * namelen value. otherwise, forget * it because we're not going to be * able to find the next entry. */ bad_sfnamelen = 1; if (i == INT_GET(sf->hdr.count, ARCH_CONVERT) - 1) { namelen = ip->i_d.di_size - ((__psint_t) &sf_entry->name[0] - (__psint_t) sf); } else { /* * don't process the rest of the directory, * break out of processing looop */ break; } } else if (no_modify && (__psint_t) sf_entry - (__psint_t) sf + + XFS_DIR_SF_ENTSIZE_BYENTRY(sf_entry) > ip->i_d.di_size) { bad_sfnamelen = 1; if (i == INT_GET(sf->hdr.count, ARCH_CONVERT) - 1) { namelen = ip->i_d.di_size - ((__psint_t) &sf_entry->name[0] - (__psint_t) sf); } else { /* * don't process the rest of the directory, * break out of processing looop */ break; } } bcopy(sf_entry->name, fname, sf_entry->namelen); fname[sf_entry->namelen] = '\0'; ASSERT(no_modify || lino != NULLFSINO); ASSERT(no_modify || !verify_inum(mp, lino)); /* * special case the "lost+found" entry if it's pointing * to where we think lost+found should be. if that's * the case, that's the one we created in phase 6. * just skip it. no need to process it and its .. * link is already accounted for. Also skip entries * with bogus inode numbers if we're in no modify mode. */ if ((lino == orphanage_ino && strcmp(fname, ORPHANAGE) == 0) || (no_modify && verify_inum(mp, lino))) { next_sfe = (xfs_dir_sf_entry_t *) ((__psint_t) sf_entry + XFS_DIR_SF_ENTSIZE_BYENTRY(sf_entry)); continue; } irec = find_inode_rec(XFS_INO_TO_AGNO(mp, lino), XFS_INO_TO_AGINO(mp, lino)); if (irec == NULL && no_modify) { do_warn( _("entry \"%s\" in shortform dir %llu references non-existent ino %llu\n"), fname, ino, lino); do_warn(_("would junk entry\n")); continue; } ASSERT(irec != NULL); ino_offset = XFS_INO_TO_AGINO(mp, lino) - irec->ino_startnum; /* * if it's a free inode, blow out the entry. * by now, any inode that we think is free * really is free. */ if (is_inode_free(irec, ino_offset)) { /* * don't complain if this entry points to the old * and now-free lost+found inode */ if (verbose || no_modify || lino != old_orphanage_ino) do_warn( _("entry \"%s\" in shortform dir inode %llu points to free inode %llu\n"), fname, ino, lino); if (!no_modify) { junkit = 1; } else { do_warn(_("would junk entry \"%s\"\n"), fname); } } else if (!inode_isadir(irec, ino_offset)) { /* * check easy case first, regular inode, just bump * the link count and continue */ add_inode_reached(irec, ino_offset); next_sfe = (xfs_dir_sf_entry_t *) ((__psint_t) sf_entry + XFS_DIR_SF_ENTSIZE_BYENTRY(sf_entry)); continue; } else { parent = get_inode_parent(irec, ino_offset); /* * bump up the link counts in parent and child. * directory but if the link doesn't agree with * the .. in the child, blow out the entry */ if (is_inode_reached(irec, ino_offset)) { junkit = 1; do_warn( _("entry \"%s\" in dir %llu references already connected dir ino %llu,\n"), fname, ino, lino); } else if (parent == ino) { add_inode_reached(irec, ino_offset); add_inode_ref(current_irec, current_ino_offset); if (!is_inode_refchecked(lino, irec, ino_offset)) push_dir(stack, lino); } else { junkit = 1; do_warn( _("entry \"%s\" in dir %llu not consistent with .. value (%llu) in dir ino %llu,\n"), fname, ino, parent, lino); } } if (junkit) { if (!no_modify) { tmp_elen = XFS_DIR_SF_ENTSIZE_BYENTRY(sf_entry); tmp_sfe = (xfs_dir_sf_entry_t *) ((__psint_t) sf_entry + tmp_elen); tmp_len = max_size - ((__psint_t) tmp_sfe - (__psint_t) sf); max_size -= tmp_elen; bytes_deleted += tmp_elen; memmove(sf_entry, tmp_sfe, tmp_len); INT_MOD(sf->hdr.count, ARCH_CONVERT, -1); bzero((void *) ((__psint_t) sf_entry + tmp_len), tmp_elen); /* * set the tmp value to the current * pointer so we'll process the entry * we just moved up */ tmp_sfe = sf_entry; /* * WARNING: drop the index i by one * so it matches the decremented count for * accurate comparisons in the loop test */ i--; *ino_dirty = 1; if (verbose || lino != old_orphanage_ino) do_warn( _("junking entry \"%s\" in directory inode %llu\n"), fname, lino); } else { do_warn(_("would junk entry \"%s\"\n"), fname); } } /* * go onto next entry unless we've just junked an * entry in which the current entry pointer points * to an unprocessed entry. have to take into entries * with bad namelen into account in no modify mode since we * calculate size based on next_sfe. */ ASSERT(no_modify || bad_sfnamelen == 0); next_sfe = (tmp_sfe == NULL) ? (xfs_dir_sf_entry_t *) ((__psint_t) sf_entry + ((!bad_sfnamelen) ? XFS_DIR_SF_ENTSIZE_BYENTRY(sf_entry) : sizeof(xfs_dir_sf_entry_t) - 1 + namelen)) : tmp_sfe; } } /* * sync up sizes if required */ if (*ino_dirty) { ASSERT(bytes_deleted > 0); ASSERT(!no_modify); libxfs_idata_realloc(ip, -bytes_deleted, XFS_DATA_FORK); ip->i_d.di_size -= bytes_deleted; } if (ip->i_d.di_size != ip->i_df.if_bytes) { ASSERT(ip->i_df.if_bytes == (xfs_fsize_t) ((__psint_t) next_sfe - (__psint_t) sf)); ip->i_d.di_size = (xfs_fsize_t) ((__psint_t) next_sfe - (__psint_t) sf); do_warn( _("setting size to %lld bytes to reflect junked entries\n"), ip->i_d.di_size); *ino_dirty = 1; } } /* ARGSUSED */ void prune_sf_dir_entry(xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip) { /* REFERENCED */ xfs_ino_t lino; xfs_dir_shortform_t *sf; xfs_dir_sf_entry_t *sf_entry, *next_sfe, *tmp_sfe; xfs_ifork_t *ifp; int max_size; int i; int tmp_len; int tmp_elen; int bytes_deleted; char fname[MAXNAMELEN + 1]; ifp = &ip->i_df; sf = (xfs_dir_shortform_t *) ifp->if_u1.if_data; bytes_deleted = 0; max_size = ifp->if_bytes; ASSERT(ip->i_d.di_size <= ifp->if_bytes); /* * now run through entries and delete every bad entry */ sf_entry = next_sfe = &sf->list[0]; for (i = 0; i < INT_GET(sf->hdr.count, ARCH_CONVERT) && max_size > (__psint_t)next_sfe - (__psint_t)sf; sf_entry = next_sfe, i++) { tmp_sfe = NULL; XFS_DIR_SF_GET_DIRINO_ARCH(&sf_entry->inumber, &lino, ARCH_CONVERT); bcopy(sf_entry->name, fname, sf_entry->namelen); fname[sf_entry->namelen] = '\0'; if (sf_entry->name[0] == '/') { if (!no_modify) { tmp_elen = XFS_DIR_SF_ENTSIZE_BYENTRY(sf_entry); tmp_sfe = (xfs_dir_sf_entry_t *) ((__psint_t) sf_entry + tmp_elen); tmp_len = max_size - ((__psint_t) tmp_sfe - (__psint_t) sf); max_size -= tmp_elen; bytes_deleted += tmp_elen; memmove(sf_entry, tmp_sfe, tmp_len); INT_MOD(sf->hdr.count, ARCH_CONVERT, -1); bzero((void *) ((__psint_t) sf_entry + tmp_len), tmp_elen); /* * set the tmp value to the current * pointer so we'll process the entry * we just moved up */ tmp_sfe = sf_entry; /* * WARNING: drop the index i by one * so it matches the decremented count for * accurate comparisons in the loop test */ i--; } } next_sfe = (tmp_sfe == NULL) ? (xfs_dir_sf_entry_t *) ((__psint_t) sf_entry + XFS_DIR_SF_ENTSIZE_BYENTRY(sf_entry)) : tmp_sfe; } /* * sync up sizes if required */ if (bytes_deleted > 0) { libxfs_idata_realloc(ip, -bytes_deleted, XFS_DATA_FORK); ip->i_d.di_size -= bytes_deleted; } if (ip->i_d.di_size != ip->i_df.if_bytes) { ASSERT(ip->i_df.if_bytes == (xfs_fsize_t) ((__psint_t) next_sfe - (__psint_t) sf)); ip->i_d.di_size = (xfs_fsize_t) ((__psint_t) next_sfe - (__psint_t) sf); do_warn( _("setting size to %lld bytes to reflect junked entries\n"), ip->i_d.di_size); } } /* * shortform directory v2 processing routines -- entry verification and * bad entry deletion (pruning). */ void shortform_dir2_entry_check(xfs_mount_t *mp, xfs_ino_t ino, xfs_inode_t *ip, int *ino_dirty, dir_stack_t *stack, ino_tree_node_t *current_irec, int current_ino_offset) { xfs_ino_t lino; xfs_ino_t parent; xfs_dir2_sf_t *sfp; xfs_dir2_sf_entry_t *sfep, *next_sfep, *tmp_sfep; xfs_ifork_t *ifp; ino_tree_node_t *irec; int max_size; int ino_offset; int i; int junkit; int tmp_len; int tmp_elen; int bad_sfnamelen; int namelen; int bytes_deleted; char fname[MAXNAMELEN + 1]; int i8; ifp = &ip->i_df; sfp = (xfs_dir2_sf_t *) ifp->if_u1.if_data; *ino_dirty = 0; bytes_deleted = 0; max_size = ifp->if_bytes; ASSERT(ip->i_d.di_size <= ifp->if_bytes); /* * no '.' entry in shortform dirs, just bump up ref count by 1 * '..' was already (or will be) accounted for and checked when * the directory is reached or will be taken care of when the * directory is moved to orphanage. */ add_inode_ref(current_irec, current_ino_offset); /* * Initialise i8 counter -- the parent inode number counts as well. */ i8 = (XFS_DIR2_SF_GET_INUMBER_ARCH(sfp, &sfp->hdr.parent, ARCH_CONVERT) > XFS_DIR2_MAX_SHORT_INUM); /* * now run through entries, stop at first bad entry, don't need * to skip over '..' since that's encoded in its own field and * no need to worry about '.' since it doesn't exist. */ sfep = next_sfep = XFS_DIR2_SF_FIRSTENTRY(sfp); for (i = 0; i < INT_GET(sfp->hdr.count, ARCH_CONVERT) && max_size > (__psint_t)next_sfep - (__psint_t)sfp; sfep = next_sfep, i++) { junkit = 0; bad_sfnamelen = 0; tmp_sfep = NULL; lino = XFS_DIR2_SF_GET_INUMBER_ARCH(sfp, XFS_DIR2_SF_INUMBERP(sfep), ARCH_CONVERT); namelen = sfep->namelen; ASSERT(no_modify || namelen > 0); if (no_modify && namelen == 0) { /* * if we're really lucky, this is * the last entry in which case we * can use the dir size to set the * namelen value. otherwise, forget * it because we're not going to be * able to find the next entry. */ bad_sfnamelen = 1; if (i == INT_GET(sfp->hdr.count, ARCH_CONVERT) - 1) { namelen = ip->i_d.di_size - ((__psint_t) &sfep->name[0] - (__psint_t) sfp); } else { /* * don't process the rest of the directory, * break out of processing loop */ break; } } else if (no_modify && (__psint_t) sfep - (__psint_t) sfp + + XFS_DIR2_SF_ENTSIZE_BYENTRY(sfp, sfep) > ip->i_d.di_size) { bad_sfnamelen = 1; if (i == INT_GET(sfp->hdr.count, ARCH_CONVERT) - 1) { namelen = ip->i_d.di_size - ((__psint_t) &sfep->name[0] - (__psint_t) sfp); } else { /* * don't process the rest of the directory, * break out of processing loop */ break; } } bcopy(sfep->name, fname, sfep->namelen); fname[sfep->namelen] = '\0'; ASSERT(no_modify || (lino != NULLFSINO && lino != 0)); ASSERT(no_modify || !verify_inum(mp, lino)); /* * special case the "lost+found" entry if it's pointing * to where we think lost+found should be. if that's * the case, that's the one we created in phase 6. * just skip it. no need to process it and its .. * link is already accounted for. */ if (lino == orphanage_ino && strcmp(fname, ORPHANAGE) == 0) { if (lino > XFS_DIR2_MAX_SHORT_INUM) i8++; next_sfep = (xfs_dir2_sf_entry_t *) ((__psint_t) sfep + XFS_DIR2_SF_ENTSIZE_BYENTRY(sfp, sfep)); continue; } /* * Also skip entries with bogus inode numbers if we're * in no modify mode. */ if (no_modify && verify_inum(mp, lino)) { next_sfep = (xfs_dir2_sf_entry_t *) ((__psint_t) sfep + XFS_DIR2_SF_ENTSIZE_BYENTRY(sfp, sfep)); continue; } irec = find_inode_rec(XFS_INO_TO_AGNO(mp, lino), XFS_INO_TO_AGINO(mp, lino)); if (irec == NULL && no_modify) { do_warn(_("entry \"%s\" in shortform directory %llu " "references non-existent inode %llu\n"), fname, ino, lino); do_warn(_("would junk entry\n")); continue; } ASSERT(irec != NULL); ino_offset = XFS_INO_TO_AGINO(mp, lino) - irec->ino_startnum; /* * if it's a free inode, blow out the entry. * by now, any inode that we think is free * really is free. */ if (is_inode_free(irec, ino_offset)) { /* * don't complain if this entry points to the old * and now-free lost+found inode */ if (verbose || no_modify || lino != old_orphanage_ino) do_warn(_("entry \"%s\" in shortform directory " "inode %llu points to free inode " "%llu\n"), fname, ino, lino); if (!no_modify) { junkit = 1; } else { do_warn(_("would junk entry \"%s\"\n"), fname); } } else if (!inode_isadir(irec, ino_offset)) { /* * check easy case first, regular inode, just bump * the link count */ add_inode_reached(irec, ino_offset); } else { parent = get_inode_parent(irec, ino_offset); /* * bump up the link counts in parent and child. * directory but if the link doesn't agree with * the .. in the child, blow out the entry */ if (is_inode_reached(irec, ino_offset)) { junkit = 1; do_warn(_("entry \"%s\" in directory inode %llu" " references already connected inode " "%llu,\n"), fname, ino, lino); } else if (parent == ino) { add_inode_reached(irec, ino_offset); add_inode_ref(current_irec, current_ino_offset); if (!is_inode_refchecked(lino, irec, ino_offset)) push_dir(stack, lino); } else { junkit = 1; do_warn(_("entry \"%s\" in directory inode %llu" " not consistent with .. value (%llu)" " in inode %llu,\n"), fname, ino, parent, lino); } } if (junkit) { if (!no_modify) { tmp_elen = XFS_DIR2_SF_ENTSIZE_BYENTRY(sfp, sfep); tmp_sfep = (xfs_dir2_sf_entry_t *) ((__psint_t) sfep + tmp_elen); tmp_len = max_size - ((__psint_t) tmp_sfep - (__psint_t) sfp); max_size -= tmp_elen; bytes_deleted += tmp_elen; memmove(sfep, tmp_sfep, tmp_len); INT_MOD(sfp->hdr.count, ARCH_CONVERT, -1); bzero((void *) ((__psint_t) sfep + tmp_len), tmp_elen); /* * set the tmp value to the current * pointer so we'll process the entry * we just moved up */ tmp_sfep = sfep; /* * WARNING: drop the index i by one * so it matches the decremented count for * accurate comparisons in the loop test */ i--; *ino_dirty = 1; if (verbose || lino != old_orphanage_ino) do_warn(_("junking entry \"%s\" in " "directory inode %llu\n"), fname, lino); } else { do_warn(_("would junk entry \"%s\"\n"), fname); } } else if (lino > XFS_DIR2_MAX_SHORT_INUM) i8++; /* * go onto next entry unless we've just junked an * entry in which the current entry pointer points * to an unprocessed entry. have to take into entries * with bad namelen into account in no modify mode since we * calculate size based on next_sfep. */ ASSERT(no_modify || bad_sfnamelen == 0); next_sfep = (tmp_sfep == NULL) ? (xfs_dir2_sf_entry_t *) ((__psint_t) sfep + ((!bad_sfnamelen) ? XFS_DIR2_SF_ENTSIZE_BYENTRY(sfp, sfep) : XFS_DIR2_SF_ENTSIZE_BYNAME(sfp, namelen))) : tmp_sfep; } if (sfp->hdr.i8count != i8) { if (no_modify) { do_warn(_("would fix i8count in inode %llu\n"), ino); } else { if (i8 == 0) { tmp_sfep = next_sfep; process_sf_dir2_fixi8(sfp, &tmp_sfep); bytes_deleted += (__psint_t)next_sfep - (__psint_t)tmp_sfep; next_sfep = tmp_sfep; } else sfp->hdr.i8count = i8; *ino_dirty = 1; do_warn(_("fixing i8count in inode %llu\n"), ino); } } /* * sync up sizes if required */ if (*ino_dirty) { ASSERT(bytes_deleted > 0); ASSERT(!no_modify); libxfs_idata_realloc(ip, -bytes_deleted, XFS_DATA_FORK); ip->i_d.di_size -= bytes_deleted; } if (ip->i_d.di_size != ip->i_df.if_bytes) { ASSERT(ip->i_df.if_bytes == (xfs_fsize_t) ((__psint_t) next_sfep - (__psint_t) sfp)); ip->i_d.di_size = (xfs_fsize_t) ((__psint_t) next_sfep - (__psint_t) sfp); do_warn(_("setting size to %lld bytes to reflect junked " "entries\n"), ip->i_d.di_size); *ino_dirty = 1; } } /* * processes all directories reachable via the inodes on the stack * returns 0 if things are good, 1 if there's a problem */ void process_dirstack(xfs_mount_t *mp, dir_stack_t *stack) { xfs_bmap_free_t flist; xfs_fsblock_t first; xfs_ino_t ino; xfs_inode_t *ip; xfs_trans_t *tp; xfs_dahash_t hashval; ino_tree_node_t *irec; int ino_offset, need_dot, committed; int dirty, num_illegal, error, nres; /* * pull directory inode # off directory stack * * open up directory inode, check all entries, * then call prune_dir_entries to remove all * remaining illegal directory entries. */ while ((ino = pop_dir(stack)) != NULLFSINO) { irec = find_inode_rec(XFS_INO_TO_AGNO(mp, ino), XFS_INO_TO_AGINO(mp, ino)); ASSERT(irec != NULL); ino_offset = XFS_INO_TO_AGINO(mp, ino) - irec->ino_startnum; ASSERT(!is_inode_refchecked(ino, irec, ino_offset)); if ((error = libxfs_iget(mp, NULL, ino, 0, &ip, 0))) { if (!no_modify) do_error( _("couldn't map inode %llu, err = %d\n"), ino, error); else { do_warn( _("couldn't map inode %llu, err = %d\n"), ino, error); /* * see below for what we're doing if this * is root. Why do we need to do this here? * to ensure that the root doesn't show up * as being disconnected in the no_modify case. */ if (mp->m_sb.sb_rootino == ino) { add_inode_reached(irec, 0); add_inode_ref(irec, 0); } } add_inode_refchecked(ino, irec, 0); continue; } need_dot = dirty = num_illegal = 0; if (mp->m_sb.sb_rootino == ino) { /* * mark root inode reached and bump up * link count for root inode to account * for '..' entry since the root inode is * never reached by a parent. we know * that root's '..' is always good -- * guaranteed by phase 3 and/or below. */ add_inode_reached(irec, ino_offset); /* * account for link for the orphanage * "lost+found". if we're running in * modify mode and it already existed, * we deleted it so it's '..' reference * never got counted. so add it here if * we're going to create lost+found. * * if we're running in no_modify mode, * we never deleted lost+found and we're * not going to create it so do nothing. * * either way, the counts will match when * we look at the root inode's nlinks * field and compare that to our incore * count in phase 7. */ if (!no_modify) add_inode_ref(irec, ino_offset); } add_inode_refchecked(ino, irec, ino_offset); /* * look for bogus entries */ switch (ip->i_d.di_format) { case XFS_DINODE_FMT_EXTENTS: case XFS_DINODE_FMT_BTREE: /* * also check for missing '.' in longform dirs. * missing .. entries are added if required when * the directory is connected to lost+found. but * we need to create '.' entries here. */ if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) longform_dir2_entry_check(mp, ino, ip, &num_illegal, &need_dot, stack, irec, ino_offset); else longform_dir_entry_check(mp, ino, ip, &num_illegal, &need_dot, stack, irec, ino_offset); break; case XFS_DINODE_FMT_LOCAL: tp = libxfs_trans_alloc(mp, 0); /* * using the remove reservation is overkill * since at most we'll only need to log the * inode but it's easier than wedging a * new define in ourselves. */ nres = no_modify ? 0 : XFS_REMOVE_SPACE_RES(mp); error = libxfs_trans_reserve(tp, nres, XFS_REMOVE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_REMOVE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb)) shortform_dir2_entry_check(mp, ino, ip, &dirty, stack, irec, ino_offset); else shortform_dir_entry_check(mp, ino, ip, &dirty, stack, irec, ino_offset); ASSERT(dirty == 0 || (dirty && !no_modify)); if (dirty) { libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE | XFS_ILOG_DDATA); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES |XFS_TRANS_SYNC, 0); } else { libxfs_trans_cancel(tp, XFS_TRANS_RELEASE_LOG_RES); } break; default: break; } hashval = 0; if (!no_modify && !orphanage_entered && ino == mp->m_sb.sb_rootino) { do_warn(_("re-entering %s into root directory\n"), ORPHANAGE); tp = libxfs_trans_alloc(mp, 0); nres = XFS_MKDIR_SPACE_RES(mp, strlen(ORPHANAGE)); error = libxfs_trans_reserve(tp, nres, XFS_MKDIR_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_MKDIR_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); XFS_BMAP_INIT(&flist, &first); if ((error = dir_createname(mp, tp, ip, ORPHANAGE, strlen(ORPHANAGE), orphanage_ino, &first, &flist, nres))) do_error(_("can't make %s entry in root inode " "%llu, createname error %d\n"), ORPHANAGE, ino, error); libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); error = libxfs_bmap_finish(&tp, &flist, first, &committed); ASSERT(error == 0); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES | XFS_TRANS_SYNC, 0); orphanage_entered = 1; } /* * if we have to create a .. for /, do it now *before* * we delete the bogus entries, otherwise the directory * could transform into a shortform dir which would * probably cause the simulation to choke. Even * if the illegal entries get shifted around, it's ok * because the entries are structurally intact and in * in hash-value order so the simulation won't get confused * if it has to move them around. */ if (!no_modify && need_root_dotdot && ino == mp->m_sb.sb_rootino) { ASSERT(ip->i_d.di_format != XFS_DINODE_FMT_LOCAL); do_warn(_("recreating root directory .. entry\n")); tp = libxfs_trans_alloc(mp, 0); ASSERT(tp != NULL); nres = XFS_MKDIR_SPACE_RES(mp, 2); error = libxfs_trans_reserve(tp, nres, XFS_MKDIR_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_MKDIR_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); XFS_BMAP_INIT(&flist, &first); if ((error = dir_createname(mp, tp, ip, "..", 2, ip->i_ino, &first, &flist, nres))) do_error( _("can't make \"..\" entry in root inode %llu, createname error %d\n"), ino, error); libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); error = libxfs_bmap_finish(&tp, &flist, first, &committed); ASSERT(error == 0); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES |XFS_TRANS_SYNC, 0); need_root_dotdot = 0; } else if (need_root_dotdot && ino == mp->m_sb.sb_rootino) { do_warn(_("would recreate root directory .. entry\n")); } /* * delete any illegal entries -- which should only exist * if the directory is a longform directory. bogus * shortform directory entries were deleted in phase 4. */ if (!no_modify && num_illegal > 0) { ASSERT(ip->i_d.di_format != XFS_DINODE_FMT_LOCAL); ASSERT(!XFS_SB_VERSION_HASDIRV2(&mp->m_sb)); while (num_illegal > 0 && ip->i_d.di_format != XFS_DINODE_FMT_LOCAL) { prune_lf_dir_entry(mp, ino, ip, &hashval); num_illegal--; } /* * handle case where we've deleted so many * entries that the directory has changed from * a longform to a shortform directory. have * to allocate a transaction since we're working * with the incore data fork. */ if (num_illegal > 0) { ASSERT(ip->i_d.di_format == XFS_DINODE_FMT_LOCAL); tp = libxfs_trans_alloc(mp, 0); /* * using the remove reservation is overkill * since at most we'll only need to log the * inode but it's easier than wedging a * new define in ourselves. 10 block fs * space reservation is also overkill but * what the heck... */ nres = XFS_REMOVE_SPACE_RES(mp); error = libxfs_trans_reserve(tp, nres, XFS_REMOVE_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_REMOVE_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); prune_sf_dir_entry(mp, ino, ip); libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE | XFS_ILOG_DDATA); ASSERT(error == 0); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES |XFS_TRANS_SYNC, 0); } } /* * if we need to create the '.' entry, do so only if * the directory is a longform dir. it it's been * turned into a shortform dir, then the inode is ok * since shortform dirs have no '.' entry and the inode * has already been committed by prune_lf_dir_entry(). */ if (need_dot) { /* * bump up our link count but don't * bump up the inode link count. chances * are good that even though we lost '.' * the inode link counts reflect '.' so * leave the inode link count alone and if * it turns out to be wrong, we'll catch * that in phase 7. */ add_inode_ref(irec, ino_offset); if (no_modify) { do_warn( _("would create missing \".\" entry in dir ino %llu\n"), ino); } else if (ip->i_d.di_format != XFS_DINODE_FMT_LOCAL) { /* * need to create . entry in longform dir. */ do_warn( _("creating missing \".\" entry in dir ino %llu\n"), ino); tp = libxfs_trans_alloc(mp, 0); ASSERT(tp != NULL); nres = XFS_MKDIR_SPACE_RES(mp, 1); error = libxfs_trans_reserve(tp, nres, XFS_MKDIR_LOG_RES(mp), 0, XFS_TRANS_PERM_LOG_RES, XFS_MKDIR_LOG_COUNT); if (error) res_failed(error); libxfs_trans_ijoin(tp, ip, 0); libxfs_trans_ihold(tp, ip); XFS_BMAP_INIT(&flist, &first); if ((error = dir_createname(mp, tp, ip, ".", 1, ip->i_ino, &first, &flist, nres))) do_error( _("can't make \".\" entry in dir ino %llu, createname error %d\n"), ino, error); libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); error = libxfs_bmap_finish(&tp, &flist, first, &committed); ASSERT(error == 0); libxfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES |XFS_TRANS_SYNC, 0); } } libxfs_iput(ip, 0); } } /* * mark realtime bitmap and summary inodes as reached. * quota inode will be marked here as well */ void mark_standalone_inodes(xfs_mount_t *mp) { ino_tree_node_t *irec; int offset; irec = find_inode_rec(XFS_INO_TO_AGNO(mp, mp->m_sb.sb_rbmino), XFS_INO_TO_AGINO(mp, mp->m_sb.sb_rbmino)); ASSERT(irec != NULL); offset = XFS_INO_TO_AGINO(mp, mp->m_sb.sb_rbmino) - irec->ino_startnum; add_inode_reached(irec, offset); irec = find_inode_rec(XFS_INO_TO_AGNO(mp, mp->m_sb.sb_rsumino), XFS_INO_TO_AGINO(mp, mp->m_sb.sb_rsumino)); offset = XFS_INO_TO_AGINO(mp, mp->m_sb.sb_rsumino) - irec->ino_startnum; ASSERT(irec != NULL); add_inode_reached(irec, offset); if (fs_quotas) { if (mp->m_sb.sb_uquotino && mp->m_sb.sb_uquotino != NULLFSINO) { irec = find_inode_rec(XFS_INO_TO_AGNO(mp, mp->m_sb.sb_uquotino), XFS_INO_TO_AGINO(mp, mp->m_sb.sb_uquotino)); offset = XFS_INO_TO_AGINO(mp, mp->m_sb.sb_uquotino) - irec->ino_startnum; add_inode_reached(irec, offset); } if (mp->m_sb.sb_gquotino && mp->m_sb.sb_gquotino != NULLFSINO) { irec = find_inode_rec(XFS_INO_TO_AGNO(mp, mp->m_sb.sb_gquotino), XFS_INO_TO_AGINO(mp, mp->m_sb.sb_gquotino)); offset = XFS_INO_TO_AGINO(mp, mp->m_sb.sb_gquotino) - irec->ino_startnum; add_inode_reached(irec, offset); } } } void phase6(xfs_mount_t *mp) { xfs_ino_t ino; ino_tree_node_t *irec; dir_stack_t stack; int i; int j; bzero(&zerocr, sizeof(cred_t)); do_log(_("Phase 6 - check inode connectivity...\n")); if (!no_modify) teardown_bmap_finish(mp); else teardown_bmap(mp); incore_ext_teardown(mp); add_ino_backptrs(mp); /* * verify existence of root directory - if we have to * make one, it's ok for the incore data structs not to * know about it since everything about it (and the other * inodes in its chunk if a new chunk was created) are ok */ if (need_root_inode) { if (!no_modify) { do_warn(_("reinitializing root directory\n")); mk_root_dir(mp); need_root_inode = 0; need_root_dotdot = 0; } else { do_warn(_("would reinitialize root directory\n")); } } if (need_rbmino) { if (!no_modify) { do_warn(_("reinitializing realtime bitmap inode\n")); mk_rbmino(mp); need_rbmino = 0; } else { do_warn(_("would reinitialize realtime bitmap inode\n")); } } if (need_rsumino) { if (!no_modify) { do_warn(_("reinitializing realtime summary inode\n")); mk_rsumino(mp); need_rsumino = 0; } else { do_warn(_("would reinitialize realtime summary inode\n")); } } if (!no_modify) { do_log( _(" - resetting contents of realtime bitmap and summary inodes\n")); if (fill_rbmino(mp)) { do_warn( _("Warning: realtime bitmap may be inconsistent\n")); } if (fill_rsumino(mp)) { do_warn( _("Warning: realtime bitmap may be inconsistent\n")); } } /* * make orphanage (it's guaranteed to not exist now) */ if (!no_modify) { do_log(_(" - ensuring existence of %s directory\n"), ORPHANAGE); orphanage_ino = mk_orphanage(mp); } dir_stack_init(&stack); mark_standalone_inodes(mp); /* * push root dir on stack, then go */ if (!need_root_inode) { do_log(_(" - traversing filesystem starting at / ... \n")); push_dir(&stack, mp->m_sb.sb_rootino); process_dirstack(mp, &stack); do_log(_(" - traversal finished ... \n")); } else { ASSERT(no_modify != 0); do_log( _(" - root inode lost, cannot make new one in no modify mode ... \n")); do_log( _(" - skipping filesystem traversal from / ... \n")); } do_log(_(" - traversing all unattached subtrees ... \n")); irec = find_inode_rec(XFS_INO_TO_AGNO(mp, mp->m_sb.sb_rootino), XFS_INO_TO_AGINO(mp, mp->m_sb.sb_rootino)); /* * we always have a root inode, even if it's free... * if the root is free, forget it, lost+found is already gone */ if (is_inode_free(irec, 0) || !inode_isadir(irec, 0)) { need_root_inode = 1; } /* * then process all unreached inodes * by walking incore inode tree * * get next unreached directory inode # from * incore list * push inode on dir stack * call process_dirstack */ for (i = 0; i < glob_agcount; i++) { irec = findfirst_inode_rec(i); if (irec == NULL) continue; while (irec != NULL) { for (j = 0; j < XFS_INODES_PER_CHUNK; j++) { if (!is_inode_confirmed(irec, j)) continue; /* * skip directories that have already been * processed, even if they haven't been * reached. If they are reachable, we'll * pick them up when we process their parent. */ ino = XFS_AGINO_TO_INO(mp, i, j + irec->ino_startnum); if (inode_isadir(irec, j) && !is_inode_refchecked(ino, irec, j)) { push_dir(&stack, ino); process_dirstack(mp, &stack); } } irec = next_ino_rec(irec); } } do_log(_(" - traversals finished ... \n")); do_log(_(" - moving disconnected inodes to lost+found ... \n")); /* * move all disconnected inodes to the orphanage */ for (i = 0; i < glob_agcount; i++) { irec = findfirst_inode_rec(i); if (irec == NULL) continue; while (irec != NULL) { for (j = 0; j < XFS_INODES_PER_CHUNK; j++) { ASSERT(is_inode_confirmed(irec, j)); if (is_inode_free(irec, j)) continue; if (!is_inode_reached(irec, j)) { ASSERT(inode_isadir(irec, j) || num_inode_references(irec, j) == 0); ino = XFS_AGINO_TO_INO(mp, i, j + irec->ino_startnum); if (inode_isadir(irec, j)) do_warn( _("disconnected dir inode %llu, "), ino); else do_warn( _("disconnected inode %llu, "), ino); if (!no_modify) { do_warn(_("moving to %s\n"), ORPHANAGE); mv_orphanage(mp, orphanage_ino, ino, inode_isadir(irec, j)); } else { do_warn(_("would move to %s\n"), ORPHANAGE); } /* * for read-only case, even though * the inode isn't really reachable, * set the flag (and bump our link * count) anyway to fool phase 7 */ add_inode_reached(irec, j); } } irec = next_ino_rec(irec); } } }