/* * 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;
/* * 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;
/* * 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;
/* Have we seen any live updates of parent pointers recently? */ bool saw_pptr_updates;
/* Number of parents we found after all other repairs */ unsignedlonglong 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. */ staticvoid
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.
*/ STATICint
xrep_parent_find_dotdot( struct xrep_parent *rp)
{ struct xfs_scrub *sc = rp->sc;
xfs_ino_t ino; unsignedint 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;
/* * 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.
*/ STATICint
xrep_parent_replay_update( struct xrep_parent *rp, conststruct xfs_name *xname, struct xrep_pptr *pptr)
{ struct xfs_scrub *sc = rp->sc;
/* * 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.
*/ STATICint
xrep_parent_replay_updates( struct xrep_parent *rp)
{
xfarray_idx_t array_cur; int error;
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.
*/ STATICint
xrep_parent_stash_parentadd( struct xrep_parent *rp, conststruct xfs_name *name, conststruct xfs_inode *dp)
{ struct xrep_pptr pptr = {
.action = XREP_PPTR_ADD,
.namelen = name->len,
}; int error;
/* * Remember that we want to remove a parent pointer from the tempfile. These * stashed actions will be replayed later.
*/ STATICint
xrep_parent_stash_parentremove( struct xrep_parent *rp, conststruct xfs_name *name, conststruct xfs_inode *dp)
{ struct xrep_pptr pptr = {
.action = XREP_PPTR_REMOVE,
.namelen = name->len,
}; int error;
/* * 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.
*/ STATICint
xrep_parent_scan_dirent( struct xfs_scrub *sc, struct xfs_inode *dp,
xfs_dir2_dataptr_t dapos, conststruct 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.
*/ staticinlinebool
xrep_parent_want_scan( struct xrep_parent *rp, conststruct 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.
*/ staticinlineunsignedint
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;
/* * Scan this file for relevant child dirents that point to the file whose * parent pointers we're rebuilding.
*/ STATICint
xrep_parent_scan_file( struct xrep_parent *rp, struct xfs_inode *ip)
{ unsignedint 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;
}
/* Decide if we've stashed too much pptr data in memory. */ staticinlinebool
xrep_parent_want_flush_stashed( struct xrep_parent *rp)
{ unsignedlonglong bytes;
/* * Scan all directories in the filesystem to look for dirents that we can turn * into parent pointers.
*/ STATICint
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));
xchk_trans_alloc_empty(sc);
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;
xchk_trans_alloc_empty(sc);
}
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.
*/ STATICint
xrep_parent_live_update( struct notifier_block *nb, unsignedlong 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;
}
/* * 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;
/* * 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.
*/ STATICint
xrep_parent_lookup_pptrs( struct xfs_scrub *sc,
xfs_ino_t *inop)
{ int error;
/* * 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.
*/ STATICint
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;
/* Ensure that the xattr value buffer is large enough. */ STATICint
xrep_parent_alloc_xattr_value( struct xrep_parent *rp,
size_t bufsize)
{ void *new_val;
/* * 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;
/* * 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;
/* * 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.
*/ STATICint
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. */
xchk_trans_alloc_empty(rp->sc);
xchk_ilock(rp->sc, XFS_ILOCK_EXCL); return 0;
}
/* Decide if we've stashed too much xattr data in memory. */ staticinlinebool
xrep_parent_want_flush_xattrs( struct xrep_parent *rp)
{ unsignedlonglong bytes;
/* Flush staged attributes to the temporary file if we're over the limit. */ STATICint
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. */ STATICint
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.
*/ STATICint
xrep_parent_ensure_attr_fork( struct xrep_parent *rp)
{ struct xfs_scrub *sc = rp->sc; int error;
/* * 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.
*/ STATICint
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.
*/ STATICint
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 (xchk_inode_is_dirtree_root(sc->ip)) {
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.
*/ STATICint
xrep_parent_rebuild_tree( struct xrep_parent *rp)
{ struct xfs_scrub *sc = rp->sc; bool try_adoption; int error;
if (xfs_has_parent(sc->mp)) {
error = xrep_parent_rebuild_pptrs(rp); if (error) return error;
}
/* * Any file with no parent could be adopted. This check happens after * rebuilding the parent pointer structure because we might have cycled * the ILOCK during that process.
*/
try_adoption = rp->pscan.parent_ino == NULLFSINO;
/* * Starting with metadir, we allow checking of parent pointers * of non-directory files that are children of the superblock. * Lack of parent is ok here.
*/ if (try_adoption && xfs_has_metadir(sc->mp) &&
xchk_inode_is_sb_rooted(sc->ip))
try_adoption = false;
if (try_adoption) { if (xrep_orphanage_can_adopt(sc)) return xrep_parent_move_to_orphanage(rp); return -EFSCORRUPTED;
}
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) return xrep_parent_reset_dotdot(rp);
return 0;
}
/* Count the number of parent pointers. */ STATICint
xrep_parent_count_pptr( struct xfs_scrub *sc, struct xfs_inode *ip, unsignedint attr_flags, constunsignedchar *name, unsignedint namelen, constvoid *value, unsignedint valuelen, void *priv)
{ struct xrep_parent *rp = priv; int error;
/* * 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.
*/ STATICint
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;
/* * Starting with metadir, we allow checking of parent pointers of * non-directory files that are children of the superblock. Pretend * that we found a parent pointer attr.
*/ if (xfs_has_metadir(sc->mp) && xchk_inode_is_sb_rooted(sc->ip))
rp->parents++;
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;
}
/* * 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;
}
/* 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. */ STATICint
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;
/* 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;
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;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.