dfe5febe2b
Add a helper to switch between the different directory formats for removing a directory entry. Signed-off-by: Christoph Hellwig <hch@lst.de> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Signed-off-by: Chandan Babu R <chandanbabu@kernel.org>
1959 lines
49 KiB
C
1959 lines
49 KiB
C
// 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_dir2_priv.h"
|
|
#include "xfs_bmap.h"
|
|
#include "xfs_quota.h"
|
|
#include "xfs_bmap_btree.h"
|
|
#include "xfs_trans_space.h"
|
|
#include "xfs_bmap_util.h"
|
|
#include "xfs_exchmaps.h"
|
|
#include "xfs_exchrange.h"
|
|
#include "xfs_ag.h"
|
|
#include "xfs_parent.h"
|
|
#include "scrub/xfs_scrub.h"
|
|
#include "scrub/scrub.h"
|
|
#include "scrub/common.h"
|
|
#include "scrub/trace.h"
|
|
#include "scrub/repair.h"
|
|
#include "scrub/tempfile.h"
|
|
#include "scrub/tempexch.h"
|
|
#include "scrub/xfile.h"
|
|
#include "scrub/xfarray.h"
|
|
#include "scrub/xfblob.h"
|
|
#include "scrub/iscan.h"
|
|
#include "scrub/readdir.h"
|
|
#include "scrub/reap.h"
|
|
#include "scrub/findparent.h"
|
|
#include "scrub/orphanage.h"
|
|
#include "scrub/listxattr.h"
|
|
|
|
/*
|
|
* Directory Repair
|
|
* ================
|
|
*
|
|
* We repair directories by reading the directory data blocks looking for
|
|
* directory entries that look salvageable (name passes verifiers, entry points
|
|
* to a valid allocated inode, etc). Each entry worth salvaging is stashed in
|
|
* memory, and the stashed entries are periodically replayed into a temporary
|
|
* directory to constrain memory use. Batching the construction of the
|
|
* temporary directory in this fashion reduces lock cycling of the directory
|
|
* being repaired and the temporary directory, and will later become important
|
|
* for parent pointer scanning.
|
|
*
|
|
* If parent pointers are enabled on this filesystem, we instead reconstruct
|
|
* the directory by visiting each parent pointer of each file in the filesystem
|
|
* and translating the relevant parent pointer records into dirents. In this
|
|
* case, it is advantageous to stash all directory entries created from parent
|
|
* pointers for a single child file before replaying them into the temporary
|
|
* directory. To save memory, the live filesystem scan reuses the findparent
|
|
* fields. Directory repair chooses either parent pointer scanning or
|
|
* directory entry salvaging, but not both.
|
|
*
|
|
* Directory entries added to the temporary directory do not elevate the link
|
|
* counts of the inodes found. When salvaging completes, the remaining stashed
|
|
* entries are replayed to the temporary directory. An atomic mapping exchange
|
|
* is used to commit the new directory blocks to the directory being repaired.
|
|
* This will disrupt readdir cursors.
|
|
*
|
|
* Locking Issues
|
|
* --------------
|
|
*
|
|
* If /a, /a/b, and /c are all directories, the VFS does not take i_rwsem on
|
|
* /a/b for a "mv /a/b /c/" operation. This means that only b's ILOCK protects
|
|
* b's dotdot update. This is in contrast to every other dotdot update (link,
|
|
* remove, mkdir). If the repair code drops the ILOCK, it must either
|
|
* revalidate the dotdot entry or use dirent hooks to capture updates from
|
|
* other threads.
|
|
*/
|
|
|
|
/* Create a dirent in the tempdir. */
|
|
#define XREP_DIRENT_ADD (1)
|
|
|
|
/* Remove a dirent from the tempdir. */
|
|
#define XREP_DIRENT_REMOVE (2)
|
|
|
|
/* Directory entry to be restored in the new directory. */
|
|
struct xrep_dirent {
|
|
/* Cookie for retrieval of the dirent name. */
|
|
xfblob_cookie name_cookie;
|
|
|
|
/* Target inode number. */
|
|
xfs_ino_t ino;
|
|
|
|
/* Length of the dirent name. */
|
|
uint8_t namelen;
|
|
|
|
/* File type of the dirent. */
|
|
uint8_t ftype;
|
|
|
|
/* XREP_DIRENT_{ADD,REMOVE} */
|
|
uint8_t action;
|
|
};
|
|
|
|
/*
|
|
* Stash up to 8 pages of recovered dirent data in dir_entries and dir_names
|
|
* before we write them to the temp dir.
|
|
*/
|
|
#define XREP_DIR_MAX_STASH_BYTES (PAGE_SIZE * 8)
|
|
|
|
struct xrep_dir {
|
|
struct xfs_scrub *sc;
|
|
|
|
/* Fixed-size array of xrep_dirent structures. */
|
|
struct xfarray *dir_entries;
|
|
|
|
/* Blobs containing directory entry names. */
|
|
struct xfblob *dir_names;
|
|
|
|
/* Information for exchanging data forks at the end. */
|
|
struct xrep_tempexch tx;
|
|
|
|
/* Preallocated args struct for performing dir operations */
|
|
struct xfs_da_args args;
|
|
|
|
/*
|
|
* Information used to scan the filesystem to find the inumber of the
|
|
* dotdot entry for this directory. For directory salvaging when
|
|
* parent pointers are not enabled, we use the findparent_* functions
|
|
* on this object and access only the parent_ino field directly.
|
|
*
|
|
* When parent pointers are enabled, however, the pptr scanner uses the
|
|
* iscan, hooks, lock, and parent_ino fields of this object directly.
|
|
* @pscan.lock coordinates access to dir_entries, dir_names,
|
|
* parent_ino, subdirs, dirents, and args. This reduces the memory
|
|
* requirements of this structure.
|
|
*/
|
|
struct xrep_parent_scan_info pscan;
|
|
|
|
/*
|
|
* Context information for attaching this directory to the lost+found
|
|
* if this directory does not have a parent.
|
|
*/
|
|
struct xrep_adoption adoption;
|
|
|
|
/* How many subdirectories did we find? */
|
|
uint64_t subdirs;
|
|
|
|
/* How many dirents did we find? */
|
|
unsigned int dirents;
|
|
|
|
/* Should we move this directory to the orphanage? */
|
|
bool needs_adoption;
|
|
|
|
/* Directory entry name, plus the trailing null. */
|
|
struct xfs_name xname;
|
|
unsigned char namebuf[MAXNAMELEN];
|
|
};
|
|
|
|
/* Tear down all the incore stuff we created. */
|
|
static void
|
|
xrep_dir_teardown(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xrep_dir *rd = sc->buf;
|
|
|
|
xrep_findparent_scan_teardown(&rd->pscan);
|
|
xfblob_destroy(rd->dir_names);
|
|
xfarray_destroy(rd->dir_entries);
|
|
}
|
|
|
|
/* Set up for a directory repair. */
|
|
int
|
|
xrep_setup_directory(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xrep_dir *rd;
|
|
int error;
|
|
|
|
xchk_fsgates_enable(sc, XCHK_FSGATES_DIRENTS);
|
|
|
|
error = xrep_orphanage_try_create(sc);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xrep_tempfile_create(sc, S_IFDIR);
|
|
if (error)
|
|
return error;
|
|
|
|
rd = kvzalloc(sizeof(struct xrep_dir), XCHK_GFP_FLAGS);
|
|
if (!rd)
|
|
return -ENOMEM;
|
|
rd->sc = sc;
|
|
rd->xname.name = rd->namebuf;
|
|
sc->buf = rd;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Look up the dotdot entry and confirm that it's really the parent.
|
|
* Returns NULLFSINO if we don't know what to do.
|
|
*/
|
|
static inline xfs_ino_t
|
|
xrep_dir_lookup_parent(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
xfs_ino_t ino;
|
|
int error;
|
|
|
|
error = xfs_dir_lookup(sc->tp, sc->ip, &xfs_name_dotdot, &ino, NULL);
|
|
if (error)
|
|
return NULLFSINO;
|
|
if (!xfs_verify_dir_ino(sc->mp, ino))
|
|
return NULLFSINO;
|
|
|
|
error = xrep_findparent_confirm(sc, &ino);
|
|
if (error)
|
|
return NULLFSINO;
|
|
|
|
return ino;
|
|
}
|
|
|
|
/*
|
|
* Look up '..' in the dentry cache and confirm that it's really the parent.
|
|
* Returns NULLFSINO if the dcache misses or if the hit is implausible.
|
|
*/
|
|
static inline xfs_ino_t
|
|
xrep_dir_dcache_parent(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
xfs_ino_t parent_ino;
|
|
int error;
|
|
|
|
parent_ino = xrep_findparent_from_dcache(sc);
|
|
if (parent_ino == NULLFSINO)
|
|
return parent_ino;
|
|
|
|
error = xrep_findparent_confirm(sc, &parent_ino);
|
|
if (error)
|
|
return NULLFSINO;
|
|
|
|
return parent_ino;
|
|
}
|
|
|
|
/* Try to find the parent of the directory being repaired. */
|
|
STATIC int
|
|
xrep_dir_find_parent(
|
|
struct xrep_dir *rd)
|
|
{
|
|
xfs_ino_t ino;
|
|
|
|
ino = xrep_findparent_self_reference(rd->sc);
|
|
if (ino != NULLFSINO) {
|
|
xrep_findparent_scan_finish_early(&rd->pscan, ino);
|
|
return 0;
|
|
}
|
|
|
|
ino = xrep_dir_dcache_parent(rd);
|
|
if (ino != NULLFSINO) {
|
|
xrep_findparent_scan_finish_early(&rd->pscan, ino);
|
|
return 0;
|
|
}
|
|
|
|
ino = xrep_dir_lookup_parent(rd);
|
|
if (ino != NULLFSINO) {
|
|
xrep_findparent_scan_finish_early(&rd->pscan, ino);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* A full filesystem scan is the last resort. On a busy filesystem,
|
|
* the scan can fail with -EBUSY if we cannot grab IOLOCKs. That means
|
|
* that we don't know what who the parent is, so we should return to
|
|
* userspace.
|
|
*/
|
|
return xrep_findparent_scan(&rd->pscan);
|
|
}
|
|
|
|
/*
|
|
* Decide if we want to salvage this entry. We don't bother with oversized
|
|
* names or the dot entry.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_want_salvage(
|
|
struct xrep_dir *rd,
|
|
const char *name,
|
|
int namelen,
|
|
xfs_ino_t ino)
|
|
{
|
|
struct xfs_mount *mp = rd->sc->mp;
|
|
|
|
/* No pointers to ourselves or to garbage. */
|
|
if (ino == rd->sc->ip->i_ino)
|
|
return false;
|
|
if (!xfs_verify_dir_ino(mp, ino))
|
|
return false;
|
|
|
|
/* No weird looking names or dot entries. */
|
|
if (namelen >= MAXNAMELEN || namelen <= 0)
|
|
return false;
|
|
if (namelen == 1 && name[0] == '.')
|
|
return false;
|
|
if (!xfs_dir2_namecheck(name, namelen))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Remember that we want to create a dirent in the tempdir. These stashed
|
|
* actions will be replayed later.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_stash_createname(
|
|
struct xrep_dir *rd,
|
|
const struct xfs_name *name,
|
|
xfs_ino_t ino)
|
|
{
|
|
struct xrep_dirent dirent = {
|
|
.action = XREP_DIRENT_ADD,
|
|
.ino = ino,
|
|
.namelen = name->len,
|
|
.ftype = name->type,
|
|
};
|
|
int error;
|
|
|
|
trace_xrep_dir_stash_createname(rd->sc->tempip, name, ino);
|
|
|
|
error = xfblob_storename(rd->dir_names, &dirent.name_cookie, name);
|
|
if (error)
|
|
return error;
|
|
|
|
return xfarray_append(rd->dir_entries, &dirent);
|
|
}
|
|
|
|
/*
|
|
* Remember that we want to remove a dirent from the tempdir. These stashed
|
|
* actions will be replayed later.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_stash_removename(
|
|
struct xrep_dir *rd,
|
|
const struct xfs_name *name,
|
|
xfs_ino_t ino)
|
|
{
|
|
struct xrep_dirent dirent = {
|
|
.action = XREP_DIRENT_REMOVE,
|
|
.ino = ino,
|
|
.namelen = name->len,
|
|
.ftype = name->type,
|
|
};
|
|
int error;
|
|
|
|
trace_xrep_dir_stash_removename(rd->sc->tempip, name, ino);
|
|
|
|
error = xfblob_storename(rd->dir_names, &dirent.name_cookie, name);
|
|
if (error)
|
|
return error;
|
|
|
|
return xfarray_append(rd->dir_entries, &dirent);
|
|
}
|
|
|
|
/* Allocate an in-core record to hold entries while we rebuild the dir data. */
|
|
STATIC int
|
|
xrep_dir_salvage_entry(
|
|
struct xrep_dir *rd,
|
|
unsigned char *name,
|
|
unsigned int namelen,
|
|
xfs_ino_t ino)
|
|
{
|
|
struct xfs_name xname = {
|
|
.name = name,
|
|
};
|
|
struct xfs_scrub *sc = rd->sc;
|
|
struct xfs_inode *ip;
|
|
unsigned int i = 0;
|
|
int error = 0;
|
|
|
|
if (xchk_should_terminate(sc, &error))
|
|
return error;
|
|
|
|
/*
|
|
* Truncate the name to the first character that would trip namecheck.
|
|
* If we no longer have a name after that, ignore this entry.
|
|
*/
|
|
while (i < namelen && name[i] != 0 && name[i] != '/')
|
|
i++;
|
|
if (i == 0)
|
|
return 0;
|
|
xname.len = i;
|
|
|
|
/* Ignore '..' entries; we already picked the new parent. */
|
|
if (xname.len == 2 && name[0] == '.' && name[1] == '.') {
|
|
trace_xrep_dir_salvaged_parent(sc->ip, ino);
|
|
return 0;
|
|
}
|
|
|
|
trace_xrep_dir_salvage_entry(sc->ip, &xname, ino);
|
|
|
|
/*
|
|
* Compute the ftype or dump the entry if we can't. We don't lock the
|
|
* inode because inodes can't change type while we have a reference.
|
|
*/
|
|
error = xchk_iget(sc, ino, &ip);
|
|
if (error)
|
|
return 0;
|
|
|
|
xname.type = xfs_mode_to_ftype(VFS_I(ip)->i_mode);
|
|
xchk_irele(sc, ip);
|
|
|
|
return xrep_dir_stash_createname(rd, &xname, ino);
|
|
}
|
|
|
|
/* Record a shortform directory entry for later reinsertion. */
|
|
STATIC int
|
|
xrep_dir_salvage_sf_entry(
|
|
struct xrep_dir *rd,
|
|
struct xfs_dir2_sf_hdr *sfp,
|
|
struct xfs_dir2_sf_entry *sfep)
|
|
{
|
|
xfs_ino_t ino;
|
|
|
|
ino = xfs_dir2_sf_get_ino(rd->sc->mp, sfp, sfep);
|
|
if (!xrep_dir_want_salvage(rd, sfep->name, sfep->namelen, ino))
|
|
return 0;
|
|
|
|
return xrep_dir_salvage_entry(rd, sfep->name, sfep->namelen, ino);
|
|
}
|
|
|
|
/* Record a regular directory entry for later reinsertion. */
|
|
STATIC int
|
|
xrep_dir_salvage_data_entry(
|
|
struct xrep_dir *rd,
|
|
struct xfs_dir2_data_entry *dep)
|
|
{
|
|
xfs_ino_t ino;
|
|
|
|
ino = be64_to_cpu(dep->inumber);
|
|
if (!xrep_dir_want_salvage(rd, dep->name, dep->namelen, ino))
|
|
return 0;
|
|
|
|
return xrep_dir_salvage_entry(rd, dep->name, dep->namelen, ino);
|
|
}
|
|
|
|
/* Try to recover block/data format directory entries. */
|
|
STATIC int
|
|
xrep_dir_recover_data(
|
|
struct xrep_dir *rd,
|
|
struct xfs_buf *bp)
|
|
{
|
|
struct xfs_da_geometry *geo = rd->sc->mp->m_dir_geo;
|
|
unsigned int offset;
|
|
unsigned int end;
|
|
int error = 0;
|
|
|
|
/*
|
|
* Loop over the data portion of the block.
|
|
* Each object is a real entry (dep) or an unused one (dup).
|
|
*/
|
|
offset = geo->data_entry_offset;
|
|
end = min_t(unsigned int, BBTOB(bp->b_length),
|
|
xfs_dir3_data_end_offset(geo, bp->b_addr));
|
|
|
|
while (offset < end) {
|
|
struct xfs_dir2_data_unused *dup = bp->b_addr + offset;
|
|
struct xfs_dir2_data_entry *dep = bp->b_addr + offset;
|
|
|
|
if (xchk_should_terminate(rd->sc, &error))
|
|
return error;
|
|
|
|
/* Skip unused entries. */
|
|
if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
|
|
offset += be16_to_cpu(dup->length);
|
|
continue;
|
|
}
|
|
|
|
/* Don't walk off the end of the block. */
|
|
offset += xfs_dir2_data_entsize(rd->sc->mp, dep->namelen);
|
|
if (offset > end)
|
|
break;
|
|
|
|
/* Ok, let's save this entry. */
|
|
error = xrep_dir_salvage_data_entry(rd, dep);
|
|
if (error)
|
|
return error;
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Try to recover shortform directory entries. */
|
|
STATIC int
|
|
xrep_dir_recover_sf(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_dir2_sf_hdr *hdr;
|
|
struct xfs_dir2_sf_entry *sfep;
|
|
struct xfs_dir2_sf_entry *next;
|
|
struct xfs_ifork *ifp;
|
|
xfs_ino_t ino;
|
|
unsigned char *end;
|
|
int error = 0;
|
|
|
|
ifp = xfs_ifork_ptr(rd->sc->ip, XFS_DATA_FORK);
|
|
hdr = ifp->if_data;
|
|
end = (unsigned char *)ifp->if_data + ifp->if_bytes;
|
|
|
|
ino = xfs_dir2_sf_get_parent_ino(hdr);
|
|
trace_xrep_dir_salvaged_parent(rd->sc->ip, ino);
|
|
|
|
sfep = xfs_dir2_sf_firstentry(hdr);
|
|
while ((unsigned char *)sfep < end) {
|
|
if (xchk_should_terminate(rd->sc, &error))
|
|
return error;
|
|
|
|
next = xfs_dir2_sf_nextentry(rd->sc->mp, hdr, sfep);
|
|
if ((unsigned char *)next > end)
|
|
break;
|
|
|
|
/* Ok, let's save this entry. */
|
|
error = xrep_dir_salvage_sf_entry(rd, hdr, sfep);
|
|
if (error)
|
|
return error;
|
|
|
|
sfep = next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Try to figure out the format of this directory from the data fork mappings
|
|
* and the directory size. If we can be reasonably sure of format, we can be
|
|
* more aggressive in salvaging directory entries. On return, @magic_guess
|
|
* will be set to DIR3_BLOCK_MAGIC if we think this is a "block format"
|
|
* directory; DIR3_DATA_MAGIC if we think this is a "data format" directory,
|
|
* and 0 if we can't tell.
|
|
*/
|
|
STATIC void
|
|
xrep_dir_guess_format(
|
|
struct xrep_dir *rd,
|
|
__be32 *magic_guess)
|
|
{
|
|
struct xfs_inode *dp = rd->sc->ip;
|
|
struct xfs_mount *mp = rd->sc->mp;
|
|
struct xfs_da_geometry *geo = mp->m_dir_geo;
|
|
xfs_fileoff_t last;
|
|
int error;
|
|
|
|
ASSERT(xfs_has_crc(mp));
|
|
|
|
*magic_guess = 0;
|
|
|
|
/*
|
|
* If there's a single directory block and the directory size is
|
|
* exactly one block, this has to be a single block format directory.
|
|
*/
|
|
error = xfs_bmap_last_offset(dp, &last, XFS_DATA_FORK);
|
|
if (!error && XFS_FSB_TO_B(mp, last) == geo->blksize &&
|
|
dp->i_disk_size == geo->blksize) {
|
|
*magic_guess = cpu_to_be32(XFS_DIR3_BLOCK_MAGIC);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If the last extent before the leaf offset matches the directory
|
|
* size and the directory size is larger than 1 block, this is a
|
|
* data format directory.
|
|
*/
|
|
last = geo->leafblk;
|
|
error = xfs_bmap_last_before(rd->sc->tp, dp, &last, XFS_DATA_FORK);
|
|
if (!error &&
|
|
XFS_FSB_TO_B(mp, last) > geo->blksize &&
|
|
XFS_FSB_TO_B(mp, last) == dp->i_disk_size) {
|
|
*magic_guess = cpu_to_be32(XFS_DIR3_DATA_MAGIC);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Recover directory entries from a specific directory block. */
|
|
STATIC int
|
|
xrep_dir_recover_dirblock(
|
|
struct xrep_dir *rd,
|
|
__be32 magic_guess,
|
|
xfs_dablk_t dabno)
|
|
{
|
|
struct xfs_dir2_data_hdr *hdr;
|
|
struct xfs_buf *bp;
|
|
__be32 oldmagic;
|
|
int error;
|
|
|
|
/*
|
|
* Try to read buffer. We invalidate them in the next step so we don't
|
|
* bother to set a buffer type or ops.
|
|
*/
|
|
error = xfs_da_read_buf(rd->sc->tp, rd->sc->ip, dabno,
|
|
XFS_DABUF_MAP_HOLE_OK, &bp, XFS_DATA_FORK, NULL);
|
|
if (error || !bp)
|
|
return error;
|
|
|
|
hdr = bp->b_addr;
|
|
oldmagic = hdr->magic;
|
|
|
|
trace_xrep_dir_recover_dirblock(rd->sc->ip, dabno,
|
|
be32_to_cpu(hdr->magic), be32_to_cpu(magic_guess));
|
|
|
|
/*
|
|
* If we're sure of the block's format, proceed with the salvage
|
|
* operation using the specified magic number.
|
|
*/
|
|
if (magic_guess) {
|
|
hdr->magic = magic_guess;
|
|
goto recover;
|
|
}
|
|
|
|
/*
|
|
* If we couldn't guess what type of directory this is, then we will
|
|
* only salvage entries from directory blocks that match the magic
|
|
* number and pass verifiers.
|
|
*/
|
|
switch (hdr->magic) {
|
|
case cpu_to_be32(XFS_DIR2_BLOCK_MAGIC):
|
|
case cpu_to_be32(XFS_DIR3_BLOCK_MAGIC):
|
|
if (!xrep_buf_verify_struct(bp, &xfs_dir3_block_buf_ops))
|
|
goto out;
|
|
if (xfs_dir3_block_header_check(bp, rd->sc->ip->i_ino) != NULL)
|
|
goto out;
|
|
break;
|
|
case cpu_to_be32(XFS_DIR2_DATA_MAGIC):
|
|
case cpu_to_be32(XFS_DIR3_DATA_MAGIC):
|
|
if (!xrep_buf_verify_struct(bp, &xfs_dir3_data_buf_ops))
|
|
goto out;
|
|
if (xfs_dir3_data_header_check(bp, rd->sc->ip->i_ino) != NULL)
|
|
goto out;
|
|
break;
|
|
default:
|
|
goto out;
|
|
}
|
|
|
|
recover:
|
|
error = xrep_dir_recover_data(rd, bp);
|
|
|
|
out:
|
|
hdr->magic = oldmagic;
|
|
xfs_trans_brelse(rd->sc->tp, bp);
|
|
return error;
|
|
}
|
|
|
|
static inline void
|
|
xrep_dir_init_args(
|
|
struct xrep_dir *rd,
|
|
struct xfs_inode *dp,
|
|
const struct xfs_name *name)
|
|
{
|
|
memset(&rd->args, 0, sizeof(struct xfs_da_args));
|
|
rd->args.geo = rd->sc->mp->m_dir_geo;
|
|
rd->args.whichfork = XFS_DATA_FORK;
|
|
rd->args.owner = rd->sc->ip->i_ino;
|
|
rd->args.trans = rd->sc->tp;
|
|
rd->args.dp = dp;
|
|
if (!name)
|
|
return;
|
|
rd->args.name = name->name;
|
|
rd->args.namelen = name->len;
|
|
rd->args.filetype = name->type;
|
|
rd->args.hashval = xfs_dir2_hashname(rd->sc->mp, name);
|
|
}
|
|
|
|
/* Replay a stashed createname into the temporary directory. */
|
|
STATIC int
|
|
xrep_dir_replay_createname(
|
|
struct xrep_dir *rd,
|
|
const struct xfs_name *name,
|
|
xfs_ino_t inum,
|
|
xfs_extlen_t total)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
struct xfs_inode *dp = rd->sc->tempip;
|
|
int error;
|
|
|
|
ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
|
|
|
|
error = xfs_dir_ino_validate(sc->mp, inum);
|
|
if (error)
|
|
return error;
|
|
|
|
trace_xrep_dir_replay_createname(dp, name, inum);
|
|
|
|
xrep_dir_init_args(rd, dp, name);
|
|
rd->args.inumber = inum;
|
|
rd->args.total = total;
|
|
rd->args.op_flags = XFS_DA_OP_ADDNAME | XFS_DA_OP_OKNOENT;
|
|
return xfs_dir_createname_args(&rd->args);
|
|
}
|
|
|
|
/* Replay a stashed removename onto the temporary directory. */
|
|
STATIC int
|
|
xrep_dir_replay_removename(
|
|
struct xrep_dir *rd,
|
|
const struct xfs_name *name,
|
|
xfs_extlen_t total)
|
|
{
|
|
struct xfs_inode *dp = rd->args.dp;
|
|
|
|
ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
|
|
|
|
xrep_dir_init_args(rd, dp, name);
|
|
rd->args.op_flags = 0;
|
|
rd->args.total = total;
|
|
|
|
trace_xrep_dir_replay_removename(dp, name, 0);
|
|
return xfs_dir_removename_args(&rd->args);
|
|
}
|
|
|
|
/*
|
|
* Add this stashed incore directory entry to the temporary directory.
|
|
* The caller must hold the tempdir's IOLOCK, must not hold any ILOCKs, and
|
|
* must not be in transaction context.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_replay_update(
|
|
struct xrep_dir *rd,
|
|
const struct xfs_name *xname,
|
|
const struct xrep_dirent *dirent)
|
|
{
|
|
struct xfs_mount *mp = rd->sc->mp;
|
|
#ifdef DEBUG
|
|
xfs_ino_t ino;
|
|
#endif
|
|
uint resblks;
|
|
int error;
|
|
|
|
resblks = xfs_link_space_res(mp, xname->len);
|
|
error = xchk_trans_alloc(rd->sc, resblks);
|
|
if (error)
|
|
return error;
|
|
|
|
/* Lock the temporary directory and join it to the transaction */
|
|
xrep_tempfile_ilock(rd->sc);
|
|
xfs_trans_ijoin(rd->sc->tp, rd->sc->tempip, 0);
|
|
|
|
switch (dirent->action) {
|
|
case XREP_DIRENT_ADD:
|
|
/*
|
|
* Create a replacement dirent in the temporary directory.
|
|
* Note that _createname doesn't check for existing entries.
|
|
* There shouldn't be any in the temporary dir, but we'll
|
|
* verify this in debug mode.
|
|
*/
|
|
#ifdef DEBUG
|
|
error = xchk_dir_lookup(rd->sc, rd->sc->tempip, xname, &ino);
|
|
if (error != -ENOENT) {
|
|
ASSERT(error != -ENOENT);
|
|
goto out_cancel;
|
|
}
|
|
#endif
|
|
|
|
error = xrep_dir_replay_createname(rd, xname, dirent->ino,
|
|
resblks);
|
|
if (error)
|
|
goto out_cancel;
|
|
|
|
if (xname->type == XFS_DIR3_FT_DIR)
|
|
rd->subdirs++;
|
|
rd->dirents++;
|
|
break;
|
|
case XREP_DIRENT_REMOVE:
|
|
/*
|
|
* Remove a dirent from the temporary directory. Note that
|
|
* _removename doesn't check the inode target of the exist
|
|
* entry. There should be a perfect match in the temporary
|
|
* dir, but we'll verify this in debug mode.
|
|
*/
|
|
#ifdef DEBUG
|
|
error = xchk_dir_lookup(rd->sc, rd->sc->tempip, xname, &ino);
|
|
if (error) {
|
|
ASSERT(error != 0);
|
|
goto out_cancel;
|
|
}
|
|
if (ino != dirent->ino) {
|
|
ASSERT(ino == dirent->ino);
|
|
error = -EIO;
|
|
goto out_cancel;
|
|
}
|
|
#endif
|
|
|
|
error = xrep_dir_replay_removename(rd, xname, resblks);
|
|
if (error)
|
|
goto out_cancel;
|
|
|
|
if (xname->type == XFS_DIR3_FT_DIR)
|
|
rd->subdirs--;
|
|
rd->dirents--;
|
|
break;
|
|
default:
|
|
ASSERT(0);
|
|
error = -EIO;
|
|
goto out_cancel;
|
|
}
|
|
|
|
/* Commit and unlock. */
|
|
error = xrep_trans_commit(rd->sc);
|
|
if (error)
|
|
return error;
|
|
|
|
xrep_tempfile_iunlock(rd->sc);
|
|
return 0;
|
|
out_cancel:
|
|
xchk_trans_cancel(rd->sc);
|
|
xrep_tempfile_iunlock(rd->sc);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Flush stashed incore dirent updates that have been recorded by the scanner.
|
|
* This is done to reduce the memory requirements of the directory rebuild,
|
|
* since directories can contain up to 32GB of directory data.
|
|
*
|
|
* Caller must not hold transactions or ILOCKs. Caller must hold the tempdir
|
|
* IOLOCK.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_replay_updates(
|
|
struct xrep_dir *rd)
|
|
{
|
|
xfarray_idx_t array_cur;
|
|
int error;
|
|
|
|
/* Add all the salvaged dirents to the temporary directory. */
|
|
mutex_lock(&rd->pscan.lock);
|
|
foreach_xfarray_idx(rd->dir_entries, array_cur) {
|
|
struct xrep_dirent dirent;
|
|
|
|
error = xfarray_load(rd->dir_entries, array_cur, &dirent);
|
|
if (error)
|
|
goto out_unlock;
|
|
|
|
error = xfblob_loadname(rd->dir_names, dirent.name_cookie,
|
|
&rd->xname, dirent.namelen);
|
|
if (error)
|
|
goto out_unlock;
|
|
rd->xname.type = dirent.ftype;
|
|
mutex_unlock(&rd->pscan.lock);
|
|
|
|
error = xrep_dir_replay_update(rd, &rd->xname, &dirent);
|
|
if (error)
|
|
return error;
|
|
mutex_lock(&rd->pscan.lock);
|
|
}
|
|
|
|
/* Empty out both arrays now that we've added the entries. */
|
|
xfarray_truncate(rd->dir_entries);
|
|
xfblob_truncate(rd->dir_names);
|
|
mutex_unlock(&rd->pscan.lock);
|
|
return 0;
|
|
out_unlock:
|
|
mutex_unlock(&rd->pscan.lock);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Periodically flush stashed directory entries to the temporary dir. This
|
|
* is done to reduce the memory requirements of the directory rebuild, since
|
|
* directories can contain up to 32GB of directory data.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_flush_stashed(
|
|
struct xrep_dir *rd)
|
|
{
|
|
int error;
|
|
|
|
/*
|
|
* Entering this function, the scrub context has a reference to the
|
|
* inode being repaired, the temporary file, and a scrub transaction
|
|
* that we use during dirent salvaging to avoid livelocking if there
|
|
* are cycles in the directory structures. We hold ILOCK_EXCL on both
|
|
* the inode being repaired and the temporary file, though they are
|
|
* not ijoined to the scrub transaction.
|
|
*
|
|
* To constrain kernel memory use, we occasionally write salvaged
|
|
* dirents from the xfarray and xfblob structures into the temporary
|
|
* directory in preparation for exchanging the directory structures at
|
|
* the end. Updating the temporary file requires a transaction, so we
|
|
* commit the scrub transaction and drop the two ILOCKs so that
|
|
* we can allocate whatever transaction we want.
|
|
*
|
|
* We still hold IOLOCK_EXCL on the inode being repaired, which
|
|
* prevents anyone from accessing the damaged directory data while we
|
|
* repair it.
|
|
*/
|
|
error = xrep_trans_commit(rd->sc);
|
|
if (error)
|
|
return error;
|
|
xchk_iunlock(rd->sc, XFS_ILOCK_EXCL);
|
|
|
|
/*
|
|
* Take the IOLOCK of the temporary file while we modify dirents. 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(rd->sc);
|
|
if (error)
|
|
return error;
|
|
|
|
/* Write to the tempdir all the updates that we've stashed. */
|
|
error = xrep_dir_replay_updates(rd);
|
|
xrep_tempfile_iounlock(rd->sc);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Recreate the salvage transaction and relock the dir we're salvaging.
|
|
*/
|
|
error = xchk_trans_alloc(rd->sc, 0);
|
|
if (error)
|
|
return error;
|
|
xchk_ilock(rd->sc, XFS_ILOCK_EXCL);
|
|
return 0;
|
|
}
|
|
|
|
/* Decide if we've stashed too much dirent data in memory. */
|
|
static inline bool
|
|
xrep_dir_want_flush_stashed(
|
|
struct xrep_dir *rd)
|
|
{
|
|
unsigned long long bytes;
|
|
|
|
bytes = xfarray_bytes(rd->dir_entries) + xfblob_bytes(rd->dir_names);
|
|
return bytes > XREP_DIR_MAX_STASH_BYTES;
|
|
}
|
|
|
|
/* Extract as many directory entries as we can. */
|
|
STATIC int
|
|
xrep_dir_recover(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_bmbt_irec got;
|
|
struct xfs_scrub *sc = rd->sc;
|
|
struct xfs_da_geometry *geo = sc->mp->m_dir_geo;
|
|
xfs_fileoff_t offset;
|
|
xfs_dablk_t dabno;
|
|
__be32 magic_guess;
|
|
int nmap;
|
|
int error;
|
|
|
|
xrep_dir_guess_format(rd, &magic_guess);
|
|
|
|
/* Iterate each directory data block in the data fork. */
|
|
for (offset = 0;
|
|
offset < geo->leafblk;
|
|
offset = got.br_startoff + got.br_blockcount) {
|
|
nmap = 1;
|
|
error = xfs_bmapi_read(sc->ip, offset, geo->leafblk - offset,
|
|
&got, &nmap, 0);
|
|
if (error)
|
|
return error;
|
|
if (nmap != 1)
|
|
return -EFSCORRUPTED;
|
|
if (!xfs_bmap_is_written_extent(&got))
|
|
continue;
|
|
|
|
for (dabno = round_up(got.br_startoff, geo->fsbcount);
|
|
dabno < got.br_startoff + got.br_blockcount;
|
|
dabno += geo->fsbcount) {
|
|
if (xchk_should_terminate(rd->sc, &error))
|
|
return error;
|
|
|
|
error = xrep_dir_recover_dirblock(rd,
|
|
magic_guess, dabno);
|
|
if (error)
|
|
return error;
|
|
|
|
/* Flush dirents to constrain memory usage. */
|
|
if (xrep_dir_want_flush_stashed(rd)) {
|
|
error = xrep_dir_flush_stashed(rd);
|
|
if (error)
|
|
return error;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Find all the directory entries for this inode by scraping them out of the
|
|
* directory leaf blocks by hand, and flushing them into the temp dir.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_find_entries(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_inode *dp = rd->sc->ip;
|
|
int error;
|
|
|
|
/*
|
|
* Salvage directory entries from the old directory, and write them to
|
|
* the temporary directory.
|
|
*/
|
|
if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL) {
|
|
error = xrep_dir_recover_sf(rd);
|
|
} else {
|
|
error = xfs_iread_extents(rd->sc->tp, dp, XFS_DATA_FORK);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xrep_dir_recover(rd);
|
|
}
|
|
if (error)
|
|
return error;
|
|
|
|
return xrep_dir_flush_stashed(rd);
|
|
}
|
|
|
|
/* Scan all files in the filesystem for dirents. */
|
|
STATIC int
|
|
xrep_dir_salvage_entries(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
int error;
|
|
|
|
/*
|
|
* Drop the ILOCK on this directory so that we can scan for this
|
|
* directory's parent. 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);
|
|
error = xrep_dir_find_parent(rd);
|
|
xchk_ilock(sc, XFS_ILOCK_EXCL);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Collect directory entries by parsing raw leaf blocks to salvage
|
|
* whatever we can. When we're done, free the staging memory before
|
|
* exchanging the directories to reduce memory usage.
|
|
*/
|
|
error = xrep_dir_find_entries(rd);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Cancel the repair transaction and drop the ILOCK so that we can
|
|
* (later) use the atomic mapping exchange functions to compute the
|
|
* correct block reservations and re-lock the inodes.
|
|
*
|
|
* We still hold IOLOCK_EXCL (aka i_rwsem) which will prevent directory
|
|
* modifications, but there's nothing to prevent userspace from reading
|
|
* the directory until we're ready for the exchange operation. Reads
|
|
* will return -EIO without shutting down the fs, so we're ok with
|
|
* that.
|
|
*
|
|
* The VFS can change dotdot on us, but the findparent scan will keep
|
|
* our incore parent inode up to date. See the note on locking issues
|
|
* for more details.
|
|
*/
|
|
error = xrep_trans_commit(sc);
|
|
if (error)
|
|
return error;
|
|
|
|
xchk_iunlock(sc, XFS_ILOCK_EXCL);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Examine a parent pointer of a file. If it leads us back to the directory
|
|
* that we're rebuilding, create an incore dirent from the parent pointer and
|
|
* stash it.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_scan_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 xfs_name xname = {
|
|
.name = name,
|
|
.len = namelen,
|
|
.type = xfs_mode_to_ftype(VFS_I(ip)->i_mode),
|
|
};
|
|
xfs_ino_t parent_ino;
|
|
uint32_t parent_gen;
|
|
struct xrep_dir *rd = priv;
|
|
int error;
|
|
|
|
if (!(attr_flags & XFS_ATTR_PARENT))
|
|
return 0;
|
|
|
|
/*
|
|
* Ignore parent pointers that point back to a different dir, list the
|
|
* wrong generation number, or are invalid.
|
|
*/
|
|
error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
|
|
valuelen, &parent_ino, &parent_gen);
|
|
if (error)
|
|
return error;
|
|
|
|
if (parent_ino != sc->ip->i_ino ||
|
|
parent_gen != VFS_I(sc->ip)->i_generation)
|
|
return 0;
|
|
|
|
mutex_lock(&rd->pscan.lock);
|
|
error = xrep_dir_stash_createname(rd, &xname, ip->i_ino);
|
|
mutex_unlock(&rd->pscan.lock);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* If this child dirent points to the directory being repaired, remember that
|
|
* fact so that we can reset the dotdot entry if necessary.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_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_dir *rd = priv;
|
|
|
|
/* Dirent doesn't point to this directory. */
|
|
if (ino != rd->sc->ip->i_ino)
|
|
return 0;
|
|
|
|
/* Ignore garbage inum. */
|
|
if (!xfs_verify_dir_ino(rd->sc->mp, ino))
|
|
return 0;
|
|
|
|
/* No weird looking names. */
|
|
if (name->len >= MAXNAMELEN || name->len <= 0)
|
|
return 0;
|
|
|
|
/* 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;
|
|
|
|
trace_xrep_dir_stash_createname(sc->tempip, &xfs_name_dotdot,
|
|
dp->i_ino);
|
|
|
|
xrep_findparent_scan_found(&rd->pscan, dp->i_ino);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Decide if we want to look for child dirents or parent pointers in this file.
|
|
* Skip the dir being repaired and any files being used to stage repairs.
|
|
*/
|
|
static inline bool
|
|
xrep_dir_want_scan(
|
|
struct xrep_dir *rd,
|
|
const struct xfs_inode *ip)
|
|
{
|
|
return ip != rd->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 or
|
|
* has an unloaded attr bmbt. Otherwise, take ILOCK_SHARED.
|
|
*/
|
|
static inline unsigned int
|
|
xrep_dir_scan_ilock(
|
|
struct xrep_dir *rd,
|
|
struct xfs_inode *ip)
|
|
{
|
|
uint lock_mode = XFS_ILOCK_SHARED;
|
|
|
|
/* Need to take the shared ILOCK to advance the iscan cursor. */
|
|
if (!xrep_dir_want_scan(rd, 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;
|
|
}
|
|
|
|
if (xfs_inode_has_attr_fork(ip) && xfs_need_iread_extents(&ip->i_af))
|
|
lock_mode = XFS_ILOCK_EXCL;
|
|
|
|
lock:
|
|
xfs_ilock(ip, lock_mode);
|
|
return lock_mode;
|
|
}
|
|
|
|
/*
|
|
* Scan this file for relevant child dirents or parent pointers that point to
|
|
* the directory we're rebuilding.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_scan_file(
|
|
struct xrep_dir *rd,
|
|
struct xfs_inode *ip)
|
|
{
|
|
unsigned int lock_mode;
|
|
int error = 0;
|
|
|
|
lock_mode = xrep_dir_scan_ilock(rd, ip);
|
|
|
|
if (!xrep_dir_want_scan(rd, ip))
|
|
goto scan_done;
|
|
|
|
/*
|
|
* If the extended attributes look as though they has been zapped by
|
|
* the inode record repair code, we cannot scan for parent pointers.
|
|
*/
|
|
if (xchk_pptr_looks_zapped(ip)) {
|
|
error = -EBUSY;
|
|
goto scan_done;
|
|
}
|
|
|
|
error = xchk_xattr_walk(rd->sc, ip, xrep_dir_scan_pptr, NULL, rd);
|
|
if (error)
|
|
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(rd->sc, ip, xrep_dir_scan_dirent, rd);
|
|
if (error)
|
|
goto scan_done;
|
|
}
|
|
|
|
scan_done:
|
|
xchk_iscan_mark_visited(&rd->pscan.iscan, ip);
|
|
xfs_iunlock(ip, lock_mode);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Scan all files in the filesystem for parent pointers that we can turn into
|
|
* replacement dirents, and a dirent that we can use to set the dotdot pointer.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_scan_dirtree(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
struct xfs_inode *ip;
|
|
int error;
|
|
|
|
/* Roots of directory trees are their own parents. */
|
|
if (sc->ip == sc->mp->m_rootip)
|
|
xrep_findparent_scan_found(&rd->pscan, sc->ip->i_ino);
|
|
|
|
/*
|
|
* Filesystem scans are time consuming. Drop the directory 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 even though we've dropped the locks.
|
|
*/
|
|
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(&rd->pscan.iscan, &ip)) == 1) {
|
|
bool flush;
|
|
|
|
error = xrep_dir_scan_file(rd, ip);
|
|
xchk_irele(sc, ip);
|
|
if (error)
|
|
break;
|
|
|
|
/* Flush stashed dirent updates to constrain memory usage. */
|
|
mutex_lock(&rd->pscan.lock);
|
|
flush = xrep_dir_want_flush_stashed(rd);
|
|
mutex_unlock(&rd->pscan.lock);
|
|
if (flush) {
|
|
xchk_trans_cancel(sc);
|
|
|
|
error = xrep_tempfile_iolock_polled(sc);
|
|
if (error)
|
|
break;
|
|
|
|
error = xrep_dir_replay_updates(rd);
|
|
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(&rd->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;
|
|
}
|
|
|
|
/*
|
|
* Cancel the empty transaction so that we can (later) use the atomic
|
|
* file mapping exchange functions to lock files and commit the new
|
|
* directory.
|
|
*/
|
|
xchk_trans_cancel(rd->sc);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Capture dirent updates being made by other threads which are relevant to the
|
|
* directory being repaired.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_live_update(
|
|
struct notifier_block *nb,
|
|
unsigned long action,
|
|
void *data)
|
|
{
|
|
struct xfs_dir_update_params *p = data;
|
|
struct xrep_dir *rd;
|
|
struct xfs_scrub *sc;
|
|
int error = 0;
|
|
|
|
rd = container_of(nb, struct xrep_dir, pscan.dhook.dirent_hook.nb);
|
|
sc = rd->sc;
|
|
|
|
/*
|
|
* This thread updated a child dirent in the directory that we're
|
|
* rebuilding. Stash the update for replay against the temporary
|
|
* directory.
|
|
*/
|
|
if (p->dp->i_ino == sc->ip->i_ino &&
|
|
xchk_iscan_want_live_update(&rd->pscan.iscan, p->ip->i_ino)) {
|
|
mutex_lock(&rd->pscan.lock);
|
|
if (p->delta > 0)
|
|
error = xrep_dir_stash_createname(rd, p->name,
|
|
p->ip->i_ino);
|
|
else
|
|
error = xrep_dir_stash_removename(rd, p->name,
|
|
p->ip->i_ino);
|
|
mutex_unlock(&rd->pscan.lock);
|
|
if (error)
|
|
goto out_abort;
|
|
}
|
|
|
|
/*
|
|
* This thread updated another directory's child dirent that points to
|
|
* the directory that we're rebuilding, so remember the new dotdot
|
|
* target.
|
|
*/
|
|
if (p->ip->i_ino == sc->ip->i_ino &&
|
|
xchk_iscan_want_live_update(&rd->pscan.iscan, p->dp->i_ino)) {
|
|
if (p->delta > 0) {
|
|
trace_xrep_dir_stash_createname(sc->tempip,
|
|
&xfs_name_dotdot,
|
|
p->dp->i_ino);
|
|
|
|
xrep_findparent_scan_found(&rd->pscan, p->dp->i_ino);
|
|
} else {
|
|
trace_xrep_dir_stash_removename(sc->tempip,
|
|
&xfs_name_dotdot,
|
|
rd->pscan.parent_ino);
|
|
|
|
xrep_findparent_scan_found(&rd->pscan, NULLFSINO);
|
|
}
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
out_abort:
|
|
xchk_iscan_abort(&rd->pscan.iscan);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
/*
|
|
* Free all the directory blocks and reset the data fork. The caller must
|
|
* join the inode to the transaction. This function returns with the inode
|
|
* joined to a clean scrub transaction.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_reset_fork(
|
|
struct xrep_dir *rd,
|
|
xfs_ino_t parent_ino)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
struct xfs_ifork *ifp = xfs_ifork_ptr(sc->tempip, XFS_DATA_FORK);
|
|
int error;
|
|
|
|
/* Unmap all the directory buffers. */
|
|
if (xfs_ifork_has_extents(ifp)) {
|
|
error = xrep_reap_ifork(sc, sc->tempip, XFS_DATA_FORK);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
trace_xrep_dir_reset_fork(sc->tempip, parent_ino);
|
|
|
|
/* Reset the data fork to an empty data fork. */
|
|
xfs_idestroy_fork(ifp);
|
|
ifp->if_bytes = 0;
|
|
sc->tempip->i_disk_size = 0;
|
|
|
|
/* Reinitialize the short form directory. */
|
|
xrep_dir_init_args(rd, sc->tempip, NULL);
|
|
return xfs_dir2_sf_create(&rd->args, parent_ino);
|
|
}
|
|
|
|
/*
|
|
* Prepare both inodes' directory forks for exchanging mappings. Promote the
|
|
* tempfile from short format to leaf format, and if the file being repaired
|
|
* has a short format data fork, turn it into an empty extent list.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_swap_prep(
|
|
struct xfs_scrub *sc,
|
|
bool temp_local,
|
|
bool ip_local)
|
|
{
|
|
int error;
|
|
|
|
/*
|
|
* If the tempfile's directory is in shortform format, convert that to
|
|
* a single leaf extent so that we can use the atomic mapping exchange.
|
|
*/
|
|
if (temp_local) {
|
|
struct xfs_da_args args = {
|
|
.dp = sc->tempip,
|
|
.geo = sc->mp->m_dir_geo,
|
|
.whichfork = XFS_DATA_FORK,
|
|
.trans = sc->tp,
|
|
.total = 1,
|
|
.owner = sc->ip->i_ino,
|
|
};
|
|
|
|
error = xfs_dir2_sf_to_block(&args);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Roll the deferred log items to get us back to a clean
|
|
* transaction.
|
|
*/
|
|
error = xfs_defer_finish(&sc->tp);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* If the file being repaired had a shortform data fork, convert that
|
|
* to an empty extent list in preparation for the atomic mapping
|
|
* exchange.
|
|
*/
|
|
if (ip_local) {
|
|
struct xfs_ifork *ifp;
|
|
|
|
ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK);
|
|
xfs_idestroy_fork(ifp);
|
|
ifp->if_format = XFS_DINODE_FMT_EXTENTS;
|
|
ifp->if_nextents = 0;
|
|
ifp->if_bytes = 0;
|
|
ifp->if_data = NULL;
|
|
ifp->if_height = 0;
|
|
|
|
xfs_trans_log_inode(sc->tp, sc->ip,
|
|
XFS_ILOG_CORE | XFS_ILOG_DDATA);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Replace the inode number of a directory entry.
|
|
*/
|
|
static int
|
|
xrep_dir_replace(
|
|
struct xrep_dir *rd,
|
|
struct xfs_inode *dp,
|
|
const struct xfs_name *name,
|
|
xfs_ino_t inum,
|
|
xfs_extlen_t total)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
int error;
|
|
|
|
ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
|
|
|
|
error = xfs_dir_ino_validate(sc->mp, inum);
|
|
if (error)
|
|
return error;
|
|
|
|
xrep_dir_init_args(rd, dp, name);
|
|
rd->args.inumber = inum;
|
|
rd->args.total = total;
|
|
return xfs_dir_replace_args(&rd->args);
|
|
}
|
|
|
|
/*
|
|
* Reset the link count of this directory and adjust the unlinked list pointers
|
|
* as needed.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_set_nlink(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
struct xfs_inode *dp = sc->ip;
|
|
struct xfs_perag *pag;
|
|
unsigned int new_nlink = min_t(unsigned long long,
|
|
rd->subdirs + 2,
|
|
XFS_NLINK_PINNED);
|
|
int error;
|
|
|
|
/*
|
|
* The directory is not on the incore unlinked list, which means that
|
|
* it needs to be reachable via the directory tree. Update the nlink
|
|
* with our observed link count. If the directory has no parent, it
|
|
* will be moved to the orphanage.
|
|
*/
|
|
if (!xfs_inode_on_unlinked_list(dp))
|
|
goto reset_nlink;
|
|
|
|
/*
|
|
* The directory is on the unlinked list and we did not find any
|
|
* dirents. Set the link count to zero and let the directory
|
|
* inactivate when the last reference drops.
|
|
*/
|
|
if (rd->dirents == 0) {
|
|
rd->needs_adoption = false;
|
|
new_nlink = 0;
|
|
goto reset_nlink;
|
|
}
|
|
|
|
/*
|
|
* The directory is on the unlinked list and we found dirents. This
|
|
* directory needs to be reachable via the directory tree. Remove the
|
|
* dir from the unlinked list and update nlink with the observed link
|
|
* count. If the directory has no parent, it will be moved to the
|
|
* orphanage.
|
|
*/
|
|
pag = xfs_perag_get(sc->mp, XFS_INO_TO_AGNO(sc->mp, dp->i_ino));
|
|
if (!pag) {
|
|
ASSERT(0);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
error = xfs_iunlink_remove(sc->tp, pag, dp);
|
|
xfs_perag_put(pag);
|
|
if (error)
|
|
return error;
|
|
|
|
reset_nlink:
|
|
if (VFS_I(dp)->i_nlink != new_nlink)
|
|
set_nlink(VFS_I(dp), new_nlink);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Finish replaying stashed dirent updates, allocate a transaction for
|
|
* exchanging data fork mappings, and take the ILOCKs of both directories
|
|
* before we commit the new directory structure.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_finalize_tempdir(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
int error;
|
|
|
|
if (!xfs_has_parent(sc->mp))
|
|
return xrep_tempexch_trans_alloc(sc, XFS_DATA_FORK, &rd->tx);
|
|
|
|
/*
|
|
* Repair relies on the ILOCK to quiesce all possible dirent updates.
|
|
* Replay all queued dirent updates into the tempdir before exchanging
|
|
* the contents, even if that means dropping the ILOCKs and the
|
|
* transaction.
|
|
*/
|
|
do {
|
|
error = xrep_dir_replay_updates(rd);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xrep_tempexch_trans_alloc(sc, XFS_DATA_FORK, &rd->tx);
|
|
if (error)
|
|
return error;
|
|
|
|
if (xfarray_length(rd->dir_entries) == 0)
|
|
break;
|
|
|
|
xchk_trans_cancel(sc);
|
|
xrep_tempfile_iunlock_both(sc);
|
|
} while (!xchk_should_terminate(sc, &error));
|
|
return error;
|
|
}
|
|
|
|
/* Exchange the temporary directory's data fork with the one being repaired. */
|
|
STATIC int
|
|
xrep_dir_swap(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
bool ip_local, temp_local;
|
|
int error = 0;
|
|
|
|
/*
|
|
* If we never found the parent for this directory, temporarily assign
|
|
* the root dir as the parent; we'll move this to the orphanage after
|
|
* exchanging the dir contents. We hold the ILOCK of the dir being
|
|
* repaired, so we're not worried about racy updates of dotdot.
|
|
*/
|
|
ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL);
|
|
if (rd->pscan.parent_ino == NULLFSINO) {
|
|
rd->needs_adoption = true;
|
|
rd->pscan.parent_ino = rd->sc->mp->m_sb.sb_rootino;
|
|
}
|
|
|
|
/*
|
|
* Reset the temporary directory's '..' entry to point to the parent
|
|
* that we found. The temporary directory was created with the root
|
|
* directory as the parent, so we can skip this if repairing a
|
|
* subdirectory of the root.
|
|
*
|
|
* It's also possible that this replacement could also expand a sf
|
|
* tempdir into block format.
|
|
*/
|
|
if (rd->pscan.parent_ino != sc->mp->m_rootip->i_ino) {
|
|
error = xrep_dir_replace(rd, rd->sc->tempip, &xfs_name_dotdot,
|
|
rd->pscan.parent_ino, rd->tx.req.resblks);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Changing the dot and dotdot entries could have changed the shape of
|
|
* the directory, so we recompute these.
|
|
*/
|
|
ip_local = sc->ip->i_df.if_format == XFS_DINODE_FMT_LOCAL;
|
|
temp_local = sc->tempip->i_df.if_format == XFS_DINODE_FMT_LOCAL;
|
|
|
|
/*
|
|
* If the both files have a local format data fork and the rebuilt
|
|
* directory data would fit in the repaired file's data fork, copy
|
|
* the contents from the tempfile and update the directory link count.
|
|
* We're done now.
|
|
*/
|
|
if (ip_local && temp_local &&
|
|
sc->tempip->i_disk_size <= xfs_inode_data_fork_size(sc->ip)) {
|
|
xrep_tempfile_copyout_local(sc, XFS_DATA_FORK);
|
|
return xrep_dir_set_nlink(rd);
|
|
}
|
|
|
|
/*
|
|
* Clean the transaction before we start working on exchanging
|
|
* directory contents.
|
|
*/
|
|
error = xrep_tempfile_roll_trans(rd->sc);
|
|
if (error)
|
|
return error;
|
|
|
|
/* Otherwise, make sure both data forks are in block-mapping mode. */
|
|
error = xrep_dir_swap_prep(sc, temp_local, ip_local);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Set nlink of the directory in the same transaction sequence that
|
|
* (atomically) commits the new directory data.
|
|
*/
|
|
error = xrep_dir_set_nlink(rd);
|
|
if (error)
|
|
return error;
|
|
|
|
return xrep_tempexch_contents(sc, &rd->tx);
|
|
}
|
|
|
|
/*
|
|
* Exchange the new directory contents (which we created in the tempfile) with
|
|
* the directory being repaired.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_rebuild_tree(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
int error;
|
|
|
|
trace_xrep_dir_rebuild_tree(sc->ip, rd->pscan.parent_ino);
|
|
|
|
/*
|
|
* Take the IOLOCK on the temporary file so that we can run dir
|
|
* operations with the same locks held as we would for a normal file.
|
|
* We still hold sc->ip's IOLOCK.
|
|
*/
|
|
error = xrep_tempfile_iolock_polled(rd->sc);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Allocate transaction, lock inodes, and make sure that we've replayed
|
|
* all the stashed dirent updates to the tempdir. After this point,
|
|
* we're ready to exchange data fork mappings.
|
|
*/
|
|
error = xrep_dir_finalize_tempdir(rd);
|
|
if (error)
|
|
return error;
|
|
|
|
if (xchk_iscan_aborted(&rd->pscan.iscan))
|
|
return -ECANCELED;
|
|
|
|
/*
|
|
* Exchange the tempdir's data fork with the file being repaired. This
|
|
* recreates the transaction and re-takes the ILOCK in the scrub
|
|
* context.
|
|
*/
|
|
error = xrep_dir_swap(rd);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Release the old directory blocks and reset the data fork of the temp
|
|
* directory to an empty shortform directory because inactivation does
|
|
* nothing for directories.
|
|
*/
|
|
error = xrep_dir_reset_fork(rd, sc->mp->m_rootip->i_ino);
|
|
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 directory.
|
|
*/
|
|
error = xfs_trans_roll(&sc->tp);
|
|
if (error)
|
|
return error;
|
|
|
|
xrep_tempfile_iunlock(sc);
|
|
xrep_tempfile_iounlock(sc);
|
|
return 0;
|
|
}
|
|
|
|
/* Set up the filesystem scan so we can regenerate directory entries. */
|
|
STATIC int
|
|
xrep_dir_setup_scan(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
char *descr;
|
|
int error;
|
|
|
|
/* Set up some staging memory for salvaging dirents. */
|
|
descr = xchk_xfile_ino_descr(sc, "directory entries");
|
|
error = xfarray_create(descr, 0, sizeof(struct xrep_dirent),
|
|
&rd->dir_entries);
|
|
kfree(descr);
|
|
if (error)
|
|
return error;
|
|
|
|
descr = xchk_xfile_ino_descr(sc, "directory entry names");
|
|
error = xfblob_create(descr, &rd->dir_names);
|
|
kfree(descr);
|
|
if (error)
|
|
goto out_xfarray;
|
|
|
|
if (xfs_has_parent(sc->mp))
|
|
error = __xrep_findparent_scan_start(sc, &rd->pscan,
|
|
xrep_dir_live_update);
|
|
else
|
|
error = xrep_findparent_scan_start(sc, &rd->pscan);
|
|
if (error)
|
|
goto out_xfblob;
|
|
|
|
return 0;
|
|
|
|
out_xfblob:
|
|
xfblob_destroy(rd->dir_names);
|
|
rd->dir_names = NULL;
|
|
out_xfarray:
|
|
xfarray_destroy(rd->dir_entries);
|
|
rd->dir_entries = NULL;
|
|
return 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.
|
|
*/
|
|
STATIC int
|
|
xrep_dir_move_to_orphanage(
|
|
struct xrep_dir *rd)
|
|
{
|
|
struct xfs_scrub *sc = rd->sc;
|
|
xfs_ino_t orig_parent, new_parent;
|
|
int error;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* 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, &rd->adoption);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xrep_adoption_compute_name(&rd->adoption, &rd->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.
|
|
*/
|
|
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &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(&rd->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(&rd->adoption);
|
|
if (error)
|
|
return error;
|
|
|
|
xrep_orphanage_iunlock(sc, XFS_ILOCK_EXCL);
|
|
xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Repair the directory metadata.
|
|
*
|
|
* XXX: Directory entry buffers can be multiple fsblocks in size. The buffer
|
|
* cache in XFS can't handle aliased multiblock buffers, so this might
|
|
* misbehave if the directory blocks are crosslinked with other filesystem
|
|
* metadata.
|
|
*
|
|
* XXX: Is it necessary to check the dcache for this directory to make sure
|
|
* that we always recreate every cached entry?
|
|
*/
|
|
int
|
|
xrep_directory(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xrep_dir *rd = sc->buf;
|
|
int error;
|
|
|
|
/* The rmapbt is required to reap the old data fork. */
|
|
if (!xfs_has_rmapbt(sc->mp))
|
|
return -EOPNOTSUPP;
|
|
/* We require atomic file exchange range to rebuild anything. */
|
|
if (!xfs_has_exchange_range(sc->mp))
|
|
return -EOPNOTSUPP;
|
|
|
|
error = xrep_dir_setup_scan(rd);
|
|
if (error)
|
|
return error;
|
|
|
|
if (xfs_has_parent(sc->mp))
|
|
error = xrep_dir_scan_dirtree(rd);
|
|
else
|
|
error = xrep_dir_salvage_entries(rd);
|
|
if (error)
|
|
goto out_teardown;
|
|
|
|
/* Last chance to abort before we start committing fixes. */
|
|
if (xchk_should_terminate(sc, &error))
|
|
goto out_teardown;
|
|
|
|
error = xrep_dir_rebuild_tree(rd);
|
|
if (error)
|
|
goto out_teardown;
|
|
|
|
if (rd->needs_adoption) {
|
|
if (!xrep_orphanage_can_adopt(rd->sc))
|
|
error = -EFSCORRUPTED;
|
|
else
|
|
error = xrep_dir_move_to_orphanage(rd);
|
|
if (error)
|
|
goto out_teardown;
|
|
}
|
|
|
|
out_teardown:
|
|
xrep_dir_teardown(sc);
|
|
return error;
|
|
}
|