Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Darrick J. Wong | 5347 | 99.13% | 43 | 81.13% |
Christoph Hellwig | 12 | 0.22% | 3 | 5.66% |
Russell Cattelan | 10 | 0.19% | 1 | 1.89% |
David Chinner | 10 | 0.19% | 4 | 7.55% |
Michal Marek | 8 | 0.15% | 1 | 1.89% |
Allison Henderson | 7 | 0.13% | 1 | 1.89% |
Total | 5394 | 53 |
// 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; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1