| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) 2020-2024 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <djwong@kernel.org> |
| */ |
| #include "xfs.h" |
| #include "xfs_fs.h" |
| #include "xfs_shared.h" |
| #include "xfs_format.h" |
| #include "xfs_trans_resv.h" |
| #include "xfs_mount.h" |
| #include "xfs_defer.h" |
| #include "xfs_bit.h" |
| #include "xfs_log_format.h" |
| #include "xfs_trans.h" |
| #include "xfs_sb.h" |
| #include "xfs_inode.h" |
| #include "xfs_icache.h" |
| #include "xfs_da_format.h" |
| #include "xfs_da_btree.h" |
| #include "xfs_dir2.h" |
| #include "xfs_bmap_btree.h" |
| #include "xfs_dir2_priv.h" |
| #include "xfs_trans_space.h" |
| #include "xfs_health.h" |
| #include "xfs_exchmaps.h" |
| #include "xfs_parent.h" |
| #include "xfs_attr.h" |
| #include "xfs_bmap.h" |
| #include "xfs_ag.h" |
| #include "scrub/xfs_scrub.h" |
| #include "scrub/scrub.h" |
| #include "scrub/common.h" |
| #include "scrub/trace.h" |
| #include "scrub/repair.h" |
| #include "scrub/iscan.h" |
| #include "scrub/findparent.h" |
| #include "scrub/readdir.h" |
| #include "scrub/tempfile.h" |
| #include "scrub/tempexch.h" |
| #include "scrub/orphanage.h" |
| #include "scrub/xfile.h" |
| #include "scrub/xfarray.h" |
| #include "scrub/xfblob.h" |
| #include "scrub/attr_repair.h" |
| #include "scrub/listxattr.h" |
| |
| /* |
| * Repairing The Directory Parent Pointer |
| * ====================================== |
| * |
| * Currently, only directories support parent pointers (in the form of '..' |
| * entries), so we simply scan the filesystem and update the '..' entry. |
| * |
| * Note that because the only parent pointer is the dotdot entry, we won't |
| * touch an unhealthy directory, since the directory repair code is perfectly |
| * capable of rebuilding a directory with the proper parent inode. |
| * |
| * See the section on locking issues in dir_repair.c for more information about |
| * conflicts with the VFS. The findparent code wll keep our incore parent |
| * inode up to date. |
| * |
| * If parent pointers are enabled, we instead reconstruct the parent pointer |
| * information by visiting every directory entry of every directory in the |
| * system and translating the relevant dirents into parent pointers. In this |
| * case, it is advantageous to stash all parent pointers created from dirents |
| * from a single parent file before replaying them into the temporary file. To |
| * save memory, the live filesystem scan reuses the findparent object. Parent |
| * pointer repair chooses either directory scanning or findparent, but not |
| * both. |
| * |
| * When salvaging completes, the remaining stashed entries are replayed to the |
| * temporary file. All non-parent pointer extended attributes are copied to |
| * the temporary file's extended attributes. An atomic file mapping exchange |
| * is used to commit the new xattr blocks to the file being repaired. This |
| * will disrupt attrmulti cursors. |
| */ |
| |
| /* Create a parent pointer in the tempfile. */ |
| #define XREP_PPTR_ADD (1) |
| |
| /* Remove a parent pointer from the tempfile. */ |
| #define XREP_PPTR_REMOVE (2) |
| |
| /* A stashed parent pointer update. */ |
| struct xrep_pptr { |
| /* Cookie for retrieval of the pptr name. */ |
| xfblob_cookie name_cookie; |
| |
| /* Parent pointer record. */ |
| struct xfs_parent_rec pptr_rec; |
| |
| /* Length of the pptr name. */ |
| uint8_t namelen; |
| |
| /* XREP_PPTR_{ADD,REMOVE} */ |
| uint8_t action; |
| }; |
| |
| /* |
| * Stash up to 8 pages of recovered parent pointers in pptr_recs and |
| * pptr_names before we write them to the temp file. |
| */ |
| #define XREP_PARENT_MAX_STASH_BYTES (PAGE_SIZE * 8) |
| |
| struct xrep_parent { |
| struct xfs_scrub *sc; |
| |
| /* Fixed-size array of xrep_pptr structures. */ |
| struct xfarray *pptr_recs; |
| |
| /* Blobs containing parent pointer names. */ |
| struct xfblob *pptr_names; |
| |
| /* xattr keys */ |
| struct xfarray *xattr_records; |
| |
| /* xattr values */ |
| struct xfblob *xattr_blobs; |
| |
| /* Scratch buffers for saving extended attributes */ |
| unsigned char *xattr_name; |
| void *xattr_value; |
| unsigned int xattr_value_sz; |
| |
| /* |
| * Information used to exchange the attr fork mappings, if the fs |
| * supports parent pointers. |
| */ |
| struct xrep_tempexch tx; |
| |
| /* |
| * Information used to scan the filesystem to find the inumber of the |
| * dotdot entry for this directory. On filesystems without parent |
| * pointers, we use the findparent_* functions on this object and |
| * access only the parent_ino field directly. |
| * |
| * When parent pointers are enabled, the directory entry scanner uses |
| * the iscan, hooks, and lock fields of this object directly. |
| * @pscan.lock coordinates access to pptr_recs, pptr_names, pptr, and |
| * pptr_scratch. This reduces the memory requirements of this |
| * structure. |
| * |
| * The lock also controls access to xattr_records and xattr_blobs(?) |
| */ |
| struct xrep_parent_scan_info pscan; |
| |
| /* Orphanage reparenting request. */ |
| struct xrep_adoption adoption; |
| |
| /* Directory entry name, plus the trailing null. */ |
| struct xfs_name xname; |
| unsigned char namebuf[MAXNAMELEN]; |
| |
| /* Scratch buffer for scanning pptr xattrs */ |
| struct xfs_da_args pptr_args; |
| |
| /* Have we seen any live updates of parent pointers recently? */ |
| bool saw_pptr_updates; |
| |
| /* Number of parents we found after all other repairs */ |
| unsigned long long parents; |
| }; |
| |
| struct xrep_parent_xattr { |
| /* Cookie for retrieval of the xattr name. */ |
| xfblob_cookie name_cookie; |
| |
| /* Cookie for retrieval of the xattr value. */ |
| xfblob_cookie value_cookie; |
| |
| /* XFS_ATTR_* flags */ |
| int flags; |
| |
| /* Length of the value and name. */ |
| uint32_t valuelen; |
| uint16_t namelen; |
| }; |
| |
| /* |
| * Stash up to 8 pages of attrs in xattr_records/xattr_blobs before we write |
| * them to the temp file. |
| */ |
| #define XREP_PARENT_XATTR_MAX_STASH_BYTES (PAGE_SIZE * 8) |
| |
| /* Tear down all the incore stuff we created. */ |
| static void |
| xrep_parent_teardown( |
| struct xrep_parent *rp) |
| { |
| xrep_findparent_scan_teardown(&rp->pscan); |
| kvfree(rp->xattr_name); |
| rp->xattr_name = NULL; |
| kvfree(rp->xattr_value); |
| rp->xattr_value = NULL; |
| if (rp->xattr_blobs) |
| xfblob_destroy(rp->xattr_blobs); |
| rp->xattr_blobs = NULL; |
| if (rp->xattr_records) |
| xfarray_destroy(rp->xattr_records); |
| rp->xattr_records = NULL; |
| if (rp->pptr_names) |
| xfblob_destroy(rp->pptr_names); |
| rp->pptr_names = NULL; |
| if (rp->pptr_recs) |
| xfarray_destroy(rp->pptr_recs); |
| rp->pptr_recs = NULL; |
| } |
| |
| /* Set up for a parent repair. */ |
| int |
| xrep_setup_parent( |
| struct xfs_scrub *sc) |
| { |
| struct xrep_parent *rp; |
| int error; |
| |
| xchk_fsgates_enable(sc, XCHK_FSGATES_DIRENTS); |
| |
| rp = kvzalloc(sizeof(struct xrep_parent), XCHK_GFP_FLAGS); |
| if (!rp) |
| return -ENOMEM; |
| rp->sc = sc; |
| rp->xname.name = rp->namebuf; |
| sc->buf = rp; |
| |
| error = xrep_tempfile_create(sc, S_IFREG); |
| if (error) |
| return error; |
| |
| return xrep_orphanage_try_create(sc); |
| } |
| |
| /* |
| * Scan all files in the filesystem for a child dirent that we can turn into |
| * the dotdot entry for this directory. |
| */ |
| STATIC int |
| xrep_parent_find_dotdot( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| xfs_ino_t ino; |
| unsigned int sick, checked; |
| int error; |
| |
| /* |
| * Avoid sick directories. There shouldn't be anyone else clearing the |
| * directory's sick status. |
| */ |
| xfs_inode_measure_sickness(sc->ip, &sick, &checked); |
| if (sick & XFS_SICK_INO_DIR) |
| return -EFSCORRUPTED; |
| |
| ino = xrep_findparent_self_reference(sc); |
| if (ino != NULLFSINO) { |
| xrep_findparent_scan_finish_early(&rp->pscan, ino); |
| return 0; |
| } |
| |
| /* |
| * Drop the ILOCK on this directory so that we can scan for the dotdot |
| * entry. Figure out who is going to be the parent of this directory, |
| * then retake the ILOCK so that we can salvage directory entries. |
| */ |
| xchk_iunlock(sc, XFS_ILOCK_EXCL); |
| |
| /* Does the VFS dcache have an answer for us? */ |
| ino = xrep_findparent_from_dcache(sc); |
| if (ino != NULLFSINO) { |
| error = xrep_findparent_confirm(sc, &ino); |
| if (!error && ino != NULLFSINO) { |
| xrep_findparent_scan_finish_early(&rp->pscan, ino); |
| goto out_relock; |
| } |
| } |
| |
| /* Scan the entire filesystem for a parent. */ |
| error = xrep_findparent_scan(&rp->pscan); |
| out_relock: |
| xchk_ilock(sc, XFS_ILOCK_EXCL); |
| |
| return error; |
| } |
| |
| /* |
| * Add this stashed incore parent pointer to the temporary file. |
| * The caller must hold the tempdir's IOLOCK, must not hold any ILOCKs, and |
| * must not be in transaction context. |
| */ |
| STATIC int |
| xrep_parent_replay_update( |
| struct xrep_parent *rp, |
| const struct xfs_name *xname, |
| struct xrep_pptr *pptr) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| |
| switch (pptr->action) { |
| case XREP_PPTR_ADD: |
| /* Create parent pointer. */ |
| trace_xrep_parent_replay_parentadd(sc->tempip, xname, |
| &pptr->pptr_rec); |
| |
| return xfs_parent_set(sc->tempip, sc->ip->i_ino, xname, |
| &pptr->pptr_rec, &rp->pptr_args); |
| case XREP_PPTR_REMOVE: |
| /* Remove parent pointer. */ |
| trace_xrep_parent_replay_parentremove(sc->tempip, xname, |
| &pptr->pptr_rec); |
| |
| return xfs_parent_unset(sc->tempip, sc->ip->i_ino, xname, |
| &pptr->pptr_rec, &rp->pptr_args); |
| } |
| |
| ASSERT(0); |
| return -EIO; |
| } |
| |
| /* |
| * Flush stashed parent pointer updates that have been recorded by the scanner. |
| * This is done to reduce the memory requirements of the parent pointer |
| * rebuild, since files can have a lot of hardlinks and the fs can be busy. |
| * |
| * Caller must not hold transactions or ILOCKs. Caller must hold the tempfile |
| * IOLOCK. |
| */ |
| STATIC int |
| xrep_parent_replay_updates( |
| struct xrep_parent *rp) |
| { |
| xfarray_idx_t array_cur; |
| int error; |
| |
| mutex_lock(&rp->pscan.lock); |
| foreach_xfarray_idx(rp->pptr_recs, array_cur) { |
| struct xrep_pptr pptr; |
| |
| error = xfarray_load(rp->pptr_recs, array_cur, &pptr); |
| if (error) |
| goto out_unlock; |
| |
| error = xfblob_loadname(rp->pptr_names, pptr.name_cookie, |
| &rp->xname, pptr.namelen); |
| if (error) |
| goto out_unlock; |
| rp->xname.len = pptr.namelen; |
| mutex_unlock(&rp->pscan.lock); |
| |
| error = xrep_parent_replay_update(rp, &rp->xname, &pptr); |
| if (error) |
| return error; |
| |
| mutex_lock(&rp->pscan.lock); |
| } |
| |
| /* Empty out both arrays now that we've added the entries. */ |
| xfarray_truncate(rp->pptr_recs); |
| xfblob_truncate(rp->pptr_names); |
| mutex_unlock(&rp->pscan.lock); |
| return 0; |
| out_unlock: |
| mutex_unlock(&rp->pscan.lock); |
| return error; |
| } |
| |
| /* |
| * Remember that we want to create a parent pointer in the tempfile. These |
| * stashed actions will be replayed later. |
| */ |
| STATIC int |
| xrep_parent_stash_parentadd( |
| struct xrep_parent *rp, |
| const struct xfs_name *name, |
| const struct xfs_inode *dp) |
| { |
| struct xrep_pptr pptr = { |
| .action = XREP_PPTR_ADD, |
| .namelen = name->len, |
| }; |
| int error; |
| |
| trace_xrep_parent_stash_parentadd(rp->sc->tempip, dp, name); |
| |
| xfs_inode_to_parent_rec(&pptr.pptr_rec, dp); |
| error = xfblob_storename(rp->pptr_names, &pptr.name_cookie, name); |
| if (error) |
| return error; |
| |
| return xfarray_append(rp->pptr_recs, &pptr); |
| } |
| |
| /* |
| * Remember that we want to remove a parent pointer from the tempfile. These |
| * stashed actions will be replayed later. |
| */ |
| STATIC int |
| xrep_parent_stash_parentremove( |
| struct xrep_parent *rp, |
| const struct xfs_name *name, |
| const struct xfs_inode *dp) |
| { |
| struct xrep_pptr pptr = { |
| .action = XREP_PPTR_REMOVE, |
| .namelen = name->len, |
| }; |
| int error; |
| |
| trace_xrep_parent_stash_parentremove(rp->sc->tempip, dp, name); |
| |
| xfs_inode_to_parent_rec(&pptr.pptr_rec, dp); |
| error = xfblob_storename(rp->pptr_names, &pptr.name_cookie, name); |
| if (error) |
| return error; |
| |
| return xfarray_append(rp->pptr_recs, &pptr); |
| } |
| |
| /* |
| * Examine an entry of a directory. If this dirent leads us back to the file |
| * whose parent pointers we're rebuilding, add a pptr to the temporary |
| * directory. |
| */ |
| STATIC int |
| xrep_parent_scan_dirent( |
| struct xfs_scrub *sc, |
| struct xfs_inode *dp, |
| xfs_dir2_dataptr_t dapos, |
| const struct xfs_name *name, |
| xfs_ino_t ino, |
| void *priv) |
| { |
| struct xrep_parent *rp = priv; |
| int error; |
| |
| /* Dirent doesn't point to this directory. */ |
| if (ino != rp->sc->ip->i_ino) |
| return 0; |
| |
| /* No weird looking names. */ |
| if (name->len == 0 || !xfs_dir2_namecheck(name->name, name->len)) |
| return -EFSCORRUPTED; |
| |
| /* No mismatching ftypes. */ |
| if (name->type != xfs_mode_to_ftype(VFS_I(sc->ip)->i_mode)) |
| return -EFSCORRUPTED; |
| |
| /* Don't pick up dot or dotdot entries; we only want child dirents. */ |
| if (xfs_dir2_samename(name, &xfs_name_dotdot) || |
| xfs_dir2_samename(name, &xfs_name_dot)) |
| return 0; |
| |
| /* |
| * Transform this dirent into a parent pointer and queue it for later |
| * addition to the temporary file. |
| */ |
| mutex_lock(&rp->pscan.lock); |
| error = xrep_parent_stash_parentadd(rp, name, dp); |
| mutex_unlock(&rp->pscan.lock); |
| return error; |
| } |
| |
| /* |
| * Decide if we want to look for dirents in this directory. Skip the file |
| * being repaired and any files being used to stage repairs. |
| */ |
| static inline bool |
| xrep_parent_want_scan( |
| struct xrep_parent *rp, |
| const struct xfs_inode *ip) |
| { |
| return ip != rp->sc->ip && !xrep_is_tempfile(ip); |
| } |
| |
| /* |
| * Take ILOCK on a file that we want to scan. |
| * |
| * Select ILOCK_EXCL if the file is a directory with an unloaded data bmbt. |
| * Otherwise, take ILOCK_SHARED. |
| */ |
| static inline unsigned int |
| xrep_parent_scan_ilock( |
| struct xrep_parent *rp, |
| struct xfs_inode *ip) |
| { |
| uint lock_mode = XFS_ILOCK_SHARED; |
| |
| /* Still need to take the shared ILOCK to advance the iscan cursor. */ |
| if (!xrep_parent_want_scan(rp, ip)) |
| goto lock; |
| |
| if (S_ISDIR(VFS_I(ip)->i_mode) && xfs_need_iread_extents(&ip->i_df)) { |
| lock_mode = XFS_ILOCK_EXCL; |
| goto lock; |
| } |
| |
| lock: |
| xfs_ilock(ip, lock_mode); |
| return lock_mode; |
| } |
| |
| /* |
| * Scan this file for relevant child dirents that point to the file whose |
| * parent pointers we're rebuilding. |
| */ |
| STATIC int |
| xrep_parent_scan_file( |
| struct xrep_parent *rp, |
| struct xfs_inode *ip) |
| { |
| unsigned int lock_mode; |
| int error = 0; |
| |
| lock_mode = xrep_parent_scan_ilock(rp, ip); |
| |
| if (!xrep_parent_want_scan(rp, ip)) |
| goto scan_done; |
| |
| if (S_ISDIR(VFS_I(ip)->i_mode)) { |
| /* |
| * If the directory looks as though it has been zapped by the |
| * inode record repair code, we cannot scan for child dirents. |
| */ |
| if (xchk_dir_looks_zapped(ip)) { |
| error = -EBUSY; |
| goto scan_done; |
| } |
| |
| error = xchk_dir_walk(rp->sc, ip, xrep_parent_scan_dirent, rp); |
| if (error) |
| goto scan_done; |
| } |
| |
| scan_done: |
| xchk_iscan_mark_visited(&rp->pscan.iscan, ip); |
| xfs_iunlock(ip, lock_mode); |
| return error; |
| } |
| |
| /* Decide if we've stashed too much pptr data in memory. */ |
| static inline bool |
| xrep_parent_want_flush_stashed( |
| struct xrep_parent *rp) |
| { |
| unsigned long long bytes; |
| |
| bytes = xfarray_bytes(rp->pptr_recs) + xfblob_bytes(rp->pptr_names); |
| return bytes > XREP_PARENT_MAX_STASH_BYTES; |
| } |
| |
| /* |
| * Scan all directories in the filesystem to look for dirents that we can turn |
| * into parent pointers. |
| */ |
| STATIC int |
| xrep_parent_scan_dirtree( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| struct xfs_inode *ip; |
| int error; |
| |
| /* |
| * Filesystem scans are time consuming. Drop the file ILOCK and all |
| * other resources for the duration of the scan and hope for the best. |
| * The live update hooks will keep our scan information up to date. |
| */ |
| xchk_trans_cancel(sc); |
| if (sc->ilock_flags & (XFS_ILOCK_SHARED | XFS_ILOCK_EXCL)) |
| xchk_iunlock(sc, sc->ilock_flags & (XFS_ILOCK_SHARED | |
| XFS_ILOCK_EXCL)); |
| error = xchk_trans_alloc_empty(sc); |
| if (error) |
| return error; |
| |
| while ((error = xchk_iscan_iter(&rp->pscan.iscan, &ip)) == 1) { |
| bool flush; |
| |
| error = xrep_parent_scan_file(rp, ip); |
| xchk_irele(sc, ip); |
| if (error) |
| break; |
| |
| /* Flush stashed pptr updates to constrain memory usage. */ |
| mutex_lock(&rp->pscan.lock); |
| flush = xrep_parent_want_flush_stashed(rp); |
| mutex_unlock(&rp->pscan.lock); |
| if (flush) { |
| xchk_trans_cancel(sc); |
| |
| error = xrep_tempfile_iolock_polled(sc); |
| if (error) |
| break; |
| |
| error = xrep_parent_replay_updates(rp); |
| xrep_tempfile_iounlock(sc); |
| if (error) |
| break; |
| |
| error = xchk_trans_alloc_empty(sc); |
| if (error) |
| break; |
| } |
| |
| if (xchk_should_terminate(sc, &error)) |
| break; |
| } |
| xchk_iscan_iter_finish(&rp->pscan.iscan); |
| if (error) { |
| /* |
| * If we couldn't grab an inode that was busy with a state |
| * change, change the error code so that we exit to userspace |
| * as quickly as possible. |
| */ |
| if (error == -EBUSY) |
| return -ECANCELED; |
| return error; |
| } |
| |
| /* |
| * Retake sc->ip's ILOCK now that we're done flushing stashed parent |
| * pointers. We end this function with an empty transaction and the |
| * ILOCK. |
| */ |
| xchk_ilock(rp->sc, XFS_ILOCK_EXCL); |
| return 0; |
| } |
| |
| /* |
| * Capture dirent updates being made by other threads which are relevant to the |
| * file being repaired. |
| */ |
| STATIC int |
| xrep_parent_live_update( |
| struct notifier_block *nb, |
| unsigned long action, |
| void *data) |
| { |
| struct xfs_dir_update_params *p = data; |
| struct xrep_parent *rp; |
| struct xfs_scrub *sc; |
| int error; |
| |
| rp = container_of(nb, struct xrep_parent, pscan.dhook.dirent_hook.nb); |
| sc = rp->sc; |
| |
| /* |
| * This thread updated a dirent that points to the file that we're |
| * repairing, so stash the update for replay against the temporary |
| * file. |
| */ |
| if (p->ip->i_ino == sc->ip->i_ino && |
| xchk_iscan_want_live_update(&rp->pscan.iscan, p->dp->i_ino)) { |
| mutex_lock(&rp->pscan.lock); |
| if (p->delta > 0) |
| error = xrep_parent_stash_parentadd(rp, p->name, p->dp); |
| else |
| error = xrep_parent_stash_parentremove(rp, p->name, |
| p->dp); |
| if (!error) |
| rp->saw_pptr_updates = true; |
| mutex_unlock(&rp->pscan.lock); |
| if (error) |
| goto out_abort; |
| } |
| |
| return NOTIFY_DONE; |
| out_abort: |
| xchk_iscan_abort(&rp->pscan.iscan); |
| return NOTIFY_DONE; |
| } |
| |
| /* Reset a directory's dotdot entry, if needed. */ |
| STATIC int |
| xrep_parent_reset_dotdot( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| xfs_ino_t ino; |
| unsigned int spaceres; |
| int error = 0; |
| |
| ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL); |
| |
| error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &ino); |
| if (error || ino == rp->pscan.parent_ino) |
| return error; |
| |
| xfs_trans_ijoin(sc->tp, sc->ip, 0); |
| |
| trace_xrep_parent_reset_dotdot(sc->ip, rp->pscan.parent_ino); |
| |
| /* |
| * Reserve more space just in case we have to expand the dir. We're |
| * allowed to exceed quota to repair inconsistent metadata. |
| */ |
| spaceres = xfs_rename_space_res(sc->mp, 0, false, xfs_name_dotdot.len, |
| false); |
| error = xfs_trans_reserve_more_inode(sc->tp, sc->ip, spaceres, 0, |
| true); |
| if (error) |
| return error; |
| |
| error = xfs_dir_replace(sc->tp, sc->ip, &xfs_name_dotdot, |
| rp->pscan.parent_ino, spaceres); |
| if (error) |
| return error; |
| |
| /* |
| * Roll transaction to detach the inode from the transaction but retain |
| * ILOCK_EXCL. |
| */ |
| return xfs_trans_roll(&sc->tp); |
| } |
| |
| /* Pass back the parent inumber if this a parent pointer */ |
| STATIC int |
| xrep_parent_lookup_pptr( |
| struct xfs_scrub *sc, |
| struct xfs_inode *ip, |
| unsigned int attr_flags, |
| const unsigned char *name, |
| unsigned int namelen, |
| const void *value, |
| unsigned int valuelen, |
| void *priv) |
| { |
| xfs_ino_t *inop = priv; |
| xfs_ino_t parent_ino; |
| int error; |
| |
| if (!(attr_flags & XFS_ATTR_PARENT)) |
| return 0; |
| |
| error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value, |
| valuelen, &parent_ino, NULL); |
| if (error) |
| return error; |
| |
| *inop = parent_ino; |
| return -ECANCELED; |
| } |
| |
| /* |
| * Find the first parent of the scrub target by walking parent pointers for |
| * the purpose of deciding if we're going to move it to the orphanage. |
| * We don't care if the attr fork is zapped. |
| */ |
| STATIC int |
| xrep_parent_lookup_pptrs( |
| struct xfs_scrub *sc, |
| xfs_ino_t *inop) |
| { |
| int error; |
| |
| *inop = NULLFSINO; |
| |
| error = xchk_xattr_walk(sc, sc->ip, xrep_parent_lookup_pptr, NULL, |
| inop); |
| if (error && error != -ECANCELED) |
| return error; |
| return 0; |
| } |
| |
| /* |
| * Move the current file to the orphanage. |
| * |
| * Caller must hold IOLOCK_EXCL on @sc->ip, and no other inode locks. Upon |
| * successful return, the scrub transaction will have enough extra reservation |
| * to make the move; it will hold IOLOCK_EXCL and ILOCK_EXCL of @sc->ip and the |
| * orphanage; and both inodes will be ijoined. |
| */ |
| STATIC int |
| xrep_parent_move_to_orphanage( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| xfs_ino_t orig_parent, new_parent; |
| int error; |
| |
| if (S_ISDIR(VFS_I(sc->ip)->i_mode)) { |
| /* |
| * We are about to drop the ILOCK on sc->ip to lock the |
| * orphanage and prepare for the adoption. Therefore, look up |
| * the old dotdot entry for sc->ip so that we can compare it |
| * after we re-lock sc->ip. |
| */ |
| error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, |
| &orig_parent); |
| if (error) |
| return error; |
| } else { |
| /* |
| * We haven't dropped the ILOCK since we committed the new |
| * xattr structure (and hence the new parent pointer records), |
| * which means that the file cannot have been moved in the |
| * directory tree, and there are no parents. |
| */ |
| orig_parent = NULLFSINO; |
| } |
| |
| /* |
| * Drop the ILOCK on the scrub target and commit the transaction. |
| * Adoption computes its own resource requirements and gathers the |
| * necessary components. |
| */ |
| error = xrep_trans_commit(sc); |
| if (error) |
| return error; |
| xchk_iunlock(sc, XFS_ILOCK_EXCL); |
| |
| /* If we can take the orphanage's iolock then we're ready to move. */ |
| if (!xrep_orphanage_ilock_nowait(sc, XFS_IOLOCK_EXCL)) { |
| xchk_iunlock(sc, sc->ilock_flags); |
| error = xrep_orphanage_iolock_two(sc); |
| if (error) |
| return error; |
| } |
| |
| /* Grab transaction and ILOCK the two files. */ |
| error = xrep_adoption_trans_alloc(sc, &rp->adoption); |
| if (error) |
| return error; |
| |
| error = xrep_adoption_compute_name(&rp->adoption, &rp->xname); |
| if (error) |
| return error; |
| |
| /* |
| * Now that we've reacquired the ILOCK on sc->ip, look up the dotdot |
| * entry again. If the parent changed or the child was unlinked while |
| * the child directory was unlocked, we don't need to move the child to |
| * the orphanage after all. For a non-directory, we have to scan for |
| * the first parent pointer to see if one has been added. |
| */ |
| if (S_ISDIR(VFS_I(sc->ip)->i_mode)) |
| error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, |
| &new_parent); |
| else |
| error = xrep_parent_lookup_pptrs(sc, &new_parent); |
| if (error) |
| return error; |
| |
| /* |
| * Attach to the orphanage if we still have a linked directory and it |
| * hasn't been moved. |
| */ |
| if (orig_parent == new_parent && VFS_I(sc->ip)->i_nlink > 0) { |
| error = xrep_adoption_move(&rp->adoption); |
| if (error) |
| return error; |
| } |
| |
| /* |
| * Launder the scrub transaction so we can drop the orphanage ILOCK |
| * and IOLOCK. Return holding the scrub target's ILOCK and IOLOCK. |
| */ |
| error = xrep_adoption_trans_roll(&rp->adoption); |
| if (error) |
| return error; |
| |
| xrep_orphanage_iunlock(sc, XFS_ILOCK_EXCL); |
| xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL); |
| return 0; |
| } |
| |
| /* Ensure that the xattr value buffer is large enough. */ |
| STATIC int |
| xrep_parent_alloc_xattr_value( |
| struct xrep_parent *rp, |
| size_t bufsize) |
| { |
| void *new_val; |
| |
| if (rp->xattr_value_sz >= bufsize) |
| return 0; |
| |
| if (rp->xattr_value) { |
| kvfree(rp->xattr_value); |
| rp->xattr_value = NULL; |
| rp->xattr_value_sz = 0; |
| } |
| |
| new_val = kvmalloc(bufsize, XCHK_GFP_FLAGS); |
| if (!new_val) |
| return -ENOMEM; |
| |
| rp->xattr_value = new_val; |
| rp->xattr_value_sz = bufsize; |
| return 0; |
| } |
| |
| /* Retrieve the (remote) value of a non-pptr xattr. */ |
| STATIC int |
| xrep_parent_fetch_xattr_remote( |
| struct xrep_parent *rp, |
| struct xfs_inode *ip, |
| unsigned int attr_flags, |
| const unsigned char *name, |
| unsigned int namelen, |
| unsigned int valuelen) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| struct xfs_da_args args = { |
| .attr_filter = attr_flags & XFS_ATTR_NSP_ONDISK_MASK, |
| .geo = sc->mp->m_attr_geo, |
| .whichfork = XFS_ATTR_FORK, |
| .dp = ip, |
| .name = name, |
| .namelen = namelen, |
| .trans = sc->tp, |
| .valuelen = valuelen, |
| .owner = ip->i_ino, |
| }; |
| int error; |
| |
| /* |
| * If we need a larger value buffer, try to allocate one. If that |
| * fails, return with -EDEADLOCK to try harder. |
| */ |
| error = xrep_parent_alloc_xattr_value(rp, valuelen); |
| if (error == -ENOMEM) |
| return -EDEADLOCK; |
| if (error) |
| return error; |
| |
| args.value = rp->xattr_value; |
| xfs_attr_sethash(&args); |
| return xfs_attr_get_ilocked(&args); |
| } |
| |
| /* Stash non-pptr attributes for later replay into the temporary file. */ |
| STATIC int |
| xrep_parent_stash_xattr( |
| struct xfs_scrub *sc, |
| struct xfs_inode *ip, |
| unsigned int attr_flags, |
| const unsigned char *name, |
| unsigned int namelen, |
| const void *value, |
| unsigned int valuelen, |
| void *priv) |
| { |
| struct xrep_parent_xattr key = { |
| .valuelen = valuelen, |
| .namelen = namelen, |
| .flags = attr_flags & XFS_ATTR_NSP_ONDISK_MASK, |
| }; |
| struct xrep_parent *rp = priv; |
| int error; |
| |
| if (attr_flags & (XFS_ATTR_INCOMPLETE | XFS_ATTR_PARENT)) |
| return 0; |
| |
| if (!value) { |
| error = xrep_parent_fetch_xattr_remote(rp, ip, attr_flags, |
| name, namelen, valuelen); |
| if (error) |
| return error; |
| |
| value = rp->xattr_value; |
| } |
| |
| trace_xrep_parent_stash_xattr(rp->sc->tempip, key.flags, (void *)name, |
| key.namelen, key.valuelen); |
| |
| error = xfblob_store(rp->xattr_blobs, &key.name_cookie, name, |
| key.namelen); |
| if (error) |
| return error; |
| |
| error = xfblob_store(rp->xattr_blobs, &key.value_cookie, value, |
| key.valuelen); |
| if (error) |
| return error; |
| |
| return xfarray_append(rp->xattr_records, &key); |
| } |
| |
| /* Insert one xattr key/value. */ |
| STATIC int |
| xrep_parent_insert_xattr( |
| struct xrep_parent *rp, |
| const struct xrep_parent_xattr *key) |
| { |
| struct xfs_da_args args = { |
| .dp = rp->sc->tempip, |
| .attr_filter = key->flags, |
| .namelen = key->namelen, |
| .valuelen = key->valuelen, |
| .owner = rp->sc->ip->i_ino, |
| .geo = rp->sc->mp->m_attr_geo, |
| .whichfork = XFS_ATTR_FORK, |
| .op_flags = XFS_DA_OP_OKNOENT, |
| }; |
| int error; |
| |
| ASSERT(!(key->flags & XFS_ATTR_PARENT)); |
| |
| /* |
| * Grab pointers to the scrub buffer so that we can use them to insert |
| * attrs into the temp file. |
| */ |
| args.name = rp->xattr_name; |
| args.value = rp->xattr_value; |
| |
| /* |
| * The attribute name is stored near the end of the in-core buffer, |
| * though we reserve one more byte to ensure null termination. |
| */ |
| rp->xattr_name[XATTR_NAME_MAX] = 0; |
| |
| error = xfblob_load(rp->xattr_blobs, key->name_cookie, rp->xattr_name, |
| key->namelen); |
| if (error) |
| return error; |
| |
| error = xfblob_free(rp->xattr_blobs, key->name_cookie); |
| if (error) |
| return error; |
| |
| error = xfblob_load(rp->xattr_blobs, key->value_cookie, args.value, |
| key->valuelen); |
| if (error) |
| return error; |
| |
| error = xfblob_free(rp->xattr_blobs, key->value_cookie); |
| if (error) |
| return error; |
| |
| rp->xattr_name[key->namelen] = 0; |
| |
| trace_xrep_parent_insert_xattr(rp->sc->tempip, key->flags, |
| rp->xattr_name, key->namelen, key->valuelen); |
| |
| xfs_attr_sethash(&args); |
| return xfs_attr_set(&args, XFS_ATTRUPDATE_UPSERT, false); |
| } |
| |
| /* |
| * Periodically flush salvaged attributes to the temporary file. This is done |
| * to reduce the memory requirements of the xattr rebuild because files can |
| * contain millions of attributes. |
| */ |
| STATIC int |
| xrep_parent_flush_xattrs( |
| struct xrep_parent *rp) |
| { |
| xfarray_idx_t array_cur; |
| int error; |
| |
| /* |
| * Entering this function, the scrub context has a reference to the |
| * inode being repaired, the temporary file, and the empty scrub |
| * transaction that we created for the xattr scan. We hold ILOCK_EXCL |
| * on the inode being repaired. |
| * |
| * To constrain kernel memory use, we occasionally flush salvaged |
| * xattrs from the xfarray and xfblob structures into the temporary |
| * file in preparation for exchanging the xattr structures at the end. |
| * Updating the temporary file requires a transaction, so we commit the |
| * scrub transaction and drop the ILOCK so that xfs_attr_set can |
| * allocate whatever transaction it wants. |
| * |
| * We still hold IOLOCK_EXCL on the inode being repaired, which |
| * prevents anyone from adding xattrs (or parent pointers) while we're |
| * flushing. |
| */ |
| xchk_trans_cancel(rp->sc); |
| xchk_iunlock(rp->sc, XFS_ILOCK_EXCL); |
| |
| /* |
| * Take the IOLOCK of the temporary file while we modify xattrs. This |
| * isn't strictly required because the temporary file is never revealed |
| * to userspace, but we follow the same locking rules. We still hold |
| * sc->ip's IOLOCK. |
| */ |
| error = xrep_tempfile_iolock_polled(rp->sc); |
| if (error) |
| return error; |
| |
| /* Add all the salvaged attrs to the temporary file. */ |
| foreach_xfarray_idx(rp->xattr_records, array_cur) { |
| struct xrep_parent_xattr key; |
| |
| error = xfarray_load(rp->xattr_records, array_cur, &key); |
| if (error) |
| return error; |
| |
| error = xrep_parent_insert_xattr(rp, &key); |
| if (error) |
| return error; |
| } |
| |
| /* Empty out both arrays now that we've added the entries. */ |
| xfarray_truncate(rp->xattr_records); |
| xfblob_truncate(rp->xattr_blobs); |
| |
| xrep_tempfile_iounlock(rp->sc); |
| |
| /* Recreate the empty transaction and relock the inode. */ |
| error = xchk_trans_alloc_empty(rp->sc); |
| if (error) |
| return error; |
| xchk_ilock(rp->sc, XFS_ILOCK_EXCL); |
| return 0; |
| } |
| |
| /* Decide if we've stashed too much xattr data in memory. */ |
| static inline bool |
| xrep_parent_want_flush_xattrs( |
| struct xrep_parent *rp) |
| { |
| unsigned long long bytes; |
| |
| bytes = xfarray_bytes(rp->xattr_records) + |
| xfblob_bytes(rp->xattr_blobs); |
| return bytes > XREP_PARENT_XATTR_MAX_STASH_BYTES; |
| } |
| |
| /* Flush staged attributes to the temporary file if we're over the limit. */ |
| STATIC int |
| xrep_parent_try_flush_xattrs( |
| struct xfs_scrub *sc, |
| void *priv) |
| { |
| struct xrep_parent *rp = priv; |
| int error; |
| |
| if (!xrep_parent_want_flush_xattrs(rp)) |
| return 0; |
| |
| error = xrep_parent_flush_xattrs(rp); |
| if (error) |
| return error; |
| |
| /* |
| * If there were any parent pointer updates to the xattr structure |
| * while we dropped the ILOCK, the xattr structure is now stale. |
| * Signal to the attr copy process that we need to start over, but |
| * this time without opportunistic attr flushing. |
| * |
| * This is unlikely to happen, so we're ok with restarting the copy. |
| */ |
| mutex_lock(&rp->pscan.lock); |
| if (rp->saw_pptr_updates) |
| error = -ESTALE; |
| mutex_unlock(&rp->pscan.lock); |
| return error; |
| } |
| |
| /* Copy all the non-pptr extended attributes into the temporary file. */ |
| STATIC int |
| xrep_parent_copy_xattrs( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| int error; |
| |
| /* |
| * Clear the pptr updates flag. We hold sc->ip ILOCKed, so there |
| * can't be any parent pointer updates in progress. |
| */ |
| mutex_lock(&rp->pscan.lock); |
| rp->saw_pptr_updates = false; |
| mutex_unlock(&rp->pscan.lock); |
| |
| /* Copy xattrs, stopping periodically to flush the incore buffers. */ |
| error = xchk_xattr_walk(sc, sc->ip, xrep_parent_stash_xattr, |
| xrep_parent_try_flush_xattrs, rp); |
| if (error && error != -ESTALE) |
| return error; |
| |
| if (error == -ESTALE) { |
| /* |
| * The xattr copy collided with a parent pointer update. |
| * Restart the copy, but this time hold the ILOCK all the way |
| * to the end to lock out any directory parent pointer updates. |
| */ |
| error = xchk_xattr_walk(sc, sc->ip, xrep_parent_stash_xattr, |
| NULL, rp); |
| if (error) |
| return error; |
| } |
| |
| /* Flush any remaining stashed xattrs to the temporary file. */ |
| if (xfarray_bytes(rp->xattr_records) == 0) |
| return 0; |
| |
| return xrep_parent_flush_xattrs(rp); |
| } |
| |
| /* |
| * Ensure that @sc->ip and @sc->tempip both have attribute forks before we head |
| * into the attr fork exchange transaction. All files on a filesystem with |
| * parent pointers must have an attr fork because the parent pointer code does |
| * not itself add attribute forks. |
| * |
| * Note: Unlinkable unlinked files don't need one, but the overhead of having |
| * an unnecessary attr fork is not justified by the additional code complexity |
| * that would be needed to track that state correctly. |
| */ |
| STATIC int |
| xrep_parent_ensure_attr_fork( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| int error; |
| |
| error = xfs_attr_add_fork(sc->tempip, |
| sizeof(struct xfs_attr_sf_hdr), 1); |
| if (error) |
| return error; |
| return xfs_attr_add_fork(sc->ip, sizeof(struct xfs_attr_sf_hdr), 1); |
| } |
| |
| /* |
| * Finish replaying stashed parent pointer updates, allocate a transaction for |
| * exchanging extent mappings, and take the ILOCKs of both files before we |
| * commit the new attribute structure. |
| */ |
| STATIC int |
| xrep_parent_finalize_tempfile( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| int error; |
| |
| /* |
| * Repair relies on the ILOCK to quiesce all possible xattr updates. |
| * Replay all queued parent pointer updates into the tempfile before |
| * exchanging the contents, even if that means dropping the ILOCKs and |
| * the transaction. |
| */ |
| do { |
| error = xrep_parent_replay_updates(rp); |
| if (error) |
| return error; |
| |
| error = xrep_parent_ensure_attr_fork(rp); |
| if (error) |
| return error; |
| |
| error = xrep_tempexch_trans_alloc(sc, XFS_ATTR_FORK, &rp->tx); |
| if (error) |
| return error; |
| |
| if (xfarray_length(rp->pptr_recs) == 0) |
| break; |
| |
| xchk_trans_cancel(sc); |
| xrep_tempfile_iunlock_both(sc); |
| } while (!xchk_should_terminate(sc, &error)); |
| return error; |
| } |
| |
| /* |
| * Replay all the stashed parent pointers into the temporary file, copy all |
| * the non-pptr xattrs from the file being repaired into the temporary file, |
| * and exchange the attr fork contents atomically. |
| */ |
| STATIC int |
| xrep_parent_rebuild_pptrs( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| xfs_ino_t parent_ino = NULLFSINO; |
| int error; |
| |
| /* |
| * Copy non-ppttr xattrs from the file being repaired into the |
| * temporary file's xattr structure. We hold sc->ip's IOLOCK, which |
| * prevents setxattr/removexattr calls from occurring, but renames |
| * update the parent pointers without holding IOLOCK. If we detect |
| * stale attr structures, we restart the scan but only flush at the |
| * end. |
| */ |
| error = xrep_parent_copy_xattrs(rp); |
| if (error) |
| return error; |
| |
| /* |
| * Cancel the empty transaction that we used to walk and copy attrs, |
| * and drop the ILOCK so that we can take the IOLOCK on the temporary |
| * file. We still hold sc->ip's IOLOCK. |
| */ |
| xchk_trans_cancel(sc); |
| xchk_iunlock(sc, XFS_ILOCK_EXCL); |
| |
| error = xrep_tempfile_iolock_polled(sc); |
| if (error) |
| return error; |
| |
| /* |
| * Allocate transaction, lock inodes, and make sure that we've replayed |
| * all the stashed pptr updates to the tempdir. After this point, |
| * we're ready to exchange the attr fork mappings. |
| */ |
| error = xrep_parent_finalize_tempfile(rp); |
| if (error) |
| return error; |
| |
| /* Last chance to abort before we start committing pptr fixes. */ |
| if (xchk_should_terminate(sc, &error)) |
| return error; |
| |
| if (xchk_iscan_aborted(&rp->pscan.iscan)) |
| return -ECANCELED; |
| |
| /* |
| * Exchange the attr fork contents and junk the old attr fork contents, |
| * which are now in the tempfile. |
| */ |
| error = xrep_xattr_swap(sc, &rp->tx); |
| if (error) |
| return error; |
| error = xrep_xattr_reset_tempfile_fork(sc); |
| if (error) |
| return error; |
| |
| /* |
| * Roll to get a transaction without any inodes joined to it. Then we |
| * can drop the tempfile's ILOCK and IOLOCK before doing more work on |
| * the scrub target file. |
| */ |
| error = xfs_trans_roll(&sc->tp); |
| if (error) |
| return error; |
| xrep_tempfile_iunlock(sc); |
| xrep_tempfile_iounlock(sc); |
| |
| /* |
| * We've committed the new parent pointers. Find at least one parent |
| * so that we can decide if we're moving this file to the orphanage. |
| * For this purpose, root directories are their own parents. |
| */ |
| if (sc->ip == sc->mp->m_rootip) { |
| xrep_findparent_scan_found(&rp->pscan, sc->ip->i_ino); |
| } else { |
| error = xrep_parent_lookup_pptrs(sc, &parent_ino); |
| if (error) |
| return error; |
| if (parent_ino != NULLFSINO) |
| xrep_findparent_scan_found(&rp->pscan, parent_ino); |
| } |
| return 0; |
| } |
| |
| /* |
| * Commit the new parent pointer structure (currently only the dotdot entry) to |
| * the file that we're repairing. |
| */ |
| STATIC int |
| xrep_parent_rebuild_tree( |
| struct xrep_parent *rp) |
| { |
| int error; |
| |
| if (xfs_has_parent(rp->sc->mp)) { |
| error = xrep_parent_rebuild_pptrs(rp); |
| if (error) |
| return error; |
| } |
| |
| if (rp->pscan.parent_ino == NULLFSINO) { |
| if (xrep_orphanage_can_adopt(rp->sc)) |
| return xrep_parent_move_to_orphanage(rp); |
| return -EFSCORRUPTED; |
| } |
| |
| if (S_ISDIR(VFS_I(rp->sc->ip)->i_mode)) |
| return xrep_parent_reset_dotdot(rp); |
| |
| return 0; |
| } |
| |
| /* Count the number of parent pointers. */ |
| STATIC int |
| xrep_parent_count_pptr( |
| struct xfs_scrub *sc, |
| struct xfs_inode *ip, |
| unsigned int attr_flags, |
| const unsigned char *name, |
| unsigned int namelen, |
| const void *value, |
| unsigned int valuelen, |
| void *priv) |
| { |
| struct xrep_parent *rp = priv; |
| int error; |
| |
| if (!(attr_flags & XFS_ATTR_PARENT)) |
| return 0; |
| |
| error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value, |
| valuelen, NULL, NULL); |
| if (error) |
| return error; |
| |
| rp->parents++; |
| return 0; |
| } |
| |
| /* |
| * After all parent pointer rebuilding and adoption activity completes, reset |
| * the link count of this nondirectory, having scanned the fs to rebuild all |
| * parent pointers. |
| */ |
| STATIC int |
| xrep_parent_set_nondir_nlink( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| struct xfs_inode *ip = sc->ip; |
| struct xfs_perag *pag; |
| bool joined = false; |
| int error; |
| |
| /* Count parent pointers so we can reset the file link count. */ |
| rp->parents = 0; |
| error = xchk_xattr_walk(sc, ip, xrep_parent_count_pptr, NULL, rp); |
| if (error) |
| return error; |
| |
| if (rp->parents > 0 && xfs_inode_on_unlinked_list(ip)) { |
| xfs_trans_ijoin(sc->tp, sc->ip, 0); |
| joined = true; |
| |
| /* |
| * The file is on the unlinked list but we found parents. |
| * Remove the file from the unlinked list. |
| */ |
| pag = xfs_perag_get(sc->mp, XFS_INO_TO_AGNO(sc->mp, ip->i_ino)); |
| if (!pag) { |
| ASSERT(0); |
| return -EFSCORRUPTED; |
| } |
| |
| error = xfs_iunlink_remove(sc->tp, pag, ip); |
| xfs_perag_put(pag); |
| if (error) |
| return error; |
| } else if (rp->parents == 0 && !xfs_inode_on_unlinked_list(ip)) { |
| xfs_trans_ijoin(sc->tp, sc->ip, 0); |
| joined = true; |
| |
| /* |
| * The file is not on the unlinked list but we found no |
| * parents. Add the file to the unlinked list. |
| */ |
| error = xfs_iunlink(sc->tp, ip); |
| if (error) |
| return error; |
| } |
| |
| /* Set the correct link count. */ |
| if (VFS_I(ip)->i_nlink != rp->parents) { |
| if (!joined) { |
| xfs_trans_ijoin(sc->tp, sc->ip, 0); |
| joined = true; |
| } |
| |
| set_nlink(VFS_I(ip), min_t(unsigned long long, rp->parents, |
| XFS_NLINK_PINNED)); |
| } |
| |
| /* Log the inode to keep it moving forward if we dirtied anything. */ |
| if (joined) |
| xfs_trans_log_inode(sc->tp, ip, XFS_ILOG_CORE); |
| return 0; |
| } |
| |
| /* Set up the filesystem scan so we can look for parents. */ |
| STATIC int |
| xrep_parent_setup_scan( |
| struct xrep_parent *rp) |
| { |
| struct xfs_scrub *sc = rp->sc; |
| char *descr; |
| struct xfs_da_geometry *geo = sc->mp->m_attr_geo; |
| int max_len; |
| int error; |
| |
| if (!xfs_has_parent(sc->mp)) |
| return xrep_findparent_scan_start(sc, &rp->pscan); |
| |
| /* Buffers for copying non-pptr attrs to the tempfile */ |
| rp->xattr_name = kvmalloc(XATTR_NAME_MAX + 1, XCHK_GFP_FLAGS); |
| if (!rp->xattr_name) |
| return -ENOMEM; |
| |
| /* |
| * Allocate enough memory to handle loading local attr values from the |
| * xfblob data while flushing stashed attrs to the temporary file. |
| * We only realloc the buffer when salvaging remote attr values, so |
| * TRY_HARDER means we allocate the maximal attr value size. |
| */ |
| if (sc->flags & XCHK_TRY_HARDER) |
| max_len = XATTR_SIZE_MAX; |
| else |
| max_len = xfs_attr_leaf_entsize_local_max(geo->blksize); |
| error = xrep_parent_alloc_xattr_value(rp, max_len); |
| if (error) |
| goto out_xattr_name; |
| |
| /* Set up some staging memory for logging parent pointer updates. */ |
| descr = xchk_xfile_ino_descr(sc, "parent pointer entries"); |
| error = xfarray_create(descr, 0, sizeof(struct xrep_pptr), |
| &rp->pptr_recs); |
| kfree(descr); |
| if (error) |
| goto out_xattr_value; |
| |
| descr = xchk_xfile_ino_descr(sc, "parent pointer names"); |
| error = xfblob_create(descr, &rp->pptr_names); |
| kfree(descr); |
| if (error) |
| goto out_recs; |
| |
| /* Set up some storage for copying attrs before the mapping exchange */ |
| descr = xchk_xfile_ino_descr(sc, |
| "parent pointer retained xattr entries"); |
| error = xfarray_create(descr, 0, sizeof(struct xrep_parent_xattr), |
| &rp->xattr_records); |
| kfree(descr); |
| if (error) |
| goto out_names; |
| |
| descr = xchk_xfile_ino_descr(sc, |
| "parent pointer retained xattr values"); |
| error = xfblob_create(descr, &rp->xattr_blobs); |
| kfree(descr); |
| if (error) |
| goto out_attr_keys; |
| |
| error = __xrep_findparent_scan_start(sc, &rp->pscan, |
| xrep_parent_live_update); |
| if (error) |
| goto out_attr_values; |
| |
| return 0; |
| |
| out_attr_values: |
| xfblob_destroy(rp->xattr_blobs); |
| rp->xattr_blobs = NULL; |
| out_attr_keys: |
| xfarray_destroy(rp->xattr_records); |
| rp->xattr_records = NULL; |
| out_names: |
| xfblob_destroy(rp->pptr_names); |
| rp->pptr_names = NULL; |
| out_recs: |
| xfarray_destroy(rp->pptr_recs); |
| rp->pptr_recs = NULL; |
| out_xattr_value: |
| kvfree(rp->xattr_value); |
| rp->xattr_value = NULL; |
| out_xattr_name: |
| kvfree(rp->xattr_name); |
| rp->xattr_name = NULL; |
| return error; |
| } |
| |
| int |
| xrep_parent( |
| struct xfs_scrub *sc) |
| { |
| struct xrep_parent *rp = sc->buf; |
| int error; |
| |
| /* |
| * When the parent pointers feature is enabled, repairs are committed |
| * by atomically committing a new xattr structure and reaping the old |
| * attr fork. Reaping requires rmap and exchange-range to be enabled. |
| */ |
| if (xfs_has_parent(sc->mp)) { |
| if (!xfs_has_rmapbt(sc->mp)) |
| return -EOPNOTSUPP; |
| if (!xfs_has_exchange_range(sc->mp)) |
| return -EOPNOTSUPP; |
| } |
| |
| error = xrep_parent_setup_scan(rp); |
| if (error) |
| return error; |
| |
| if (xfs_has_parent(sc->mp)) |
| error = xrep_parent_scan_dirtree(rp); |
| else |
| error = xrep_parent_find_dotdot(rp); |
| if (error) |
| goto out_teardown; |
| |
| /* Last chance to abort before we start committing dotdot fixes. */ |
| if (xchk_should_terminate(sc, &error)) |
| goto out_teardown; |
| |
| error = xrep_parent_rebuild_tree(rp); |
| if (error) |
| goto out_teardown; |
| if (xfs_has_parent(sc->mp) && !S_ISDIR(VFS_I(sc->ip)->i_mode)) { |
| error = xrep_parent_set_nondir_nlink(rp); |
| if (error) |
| goto out_teardown; |
| } |
| |
| error = xrep_defer_finish(sc); |
| |
| out_teardown: |
| xrep_parent_teardown(rp); |
| return error; |
| } |