#include "xfs_platform.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"
#define XREP_DIRENT_ADD (1)
#define XREP_DIRENT_REMOVE (2)
struct xrep_dirent {
xfblob_cookie name_cookie;
xfs_ino_t ino;
uint8_t namelen;
uint8_t ftype;
uint8_t action;
};
#define XREP_DIR_MAX_STASH_BYTES (PAGE_SIZE * 8)
struct xrep_dir {
struct xfs_scrub *sc;
struct xfarray *dir_entries;
struct xfblob *dir_names;
struct xrep_tempexch tx;
struct xfs_da_args args;
struct xrep_parent_scan_info pscan;
struct xrep_adoption adoption;
uint64_t subdirs;
unsigned int dirents;
bool needs_adoption;
struct xfs_name xname;
unsigned char namebuf[MAXNAMELEN];
};
static void
xrep_dir_teardown(
struct xfs_scrub *sc)
{
struct xrep_dir *rd = sc->buf;
xrep_findparent_scan_teardown(&rd->pscan);
if (rd->dir_names)
xfblob_destroy(rd->dir_names);
rd->dir_names = NULL;
if (rd->dir_entries)
xfarray_destroy(rd->dir_entries);
rd->dir_entries = NULL;
}
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_obj(struct xrep_dir, XCHK_GFP_FLAGS);
if (!rd)
return -ENOMEM;
rd->sc = sc;
rd->xname.name = rd->namebuf;
sc->buf = rd;
return 0;
}
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;
}
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;
}
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;
}
return xrep_findparent_scan(&rd->pscan);
}
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;
if (ino == rd->sc->ip->i_ino)
return false;
if (!xfs_verify_dir_ino(mp, ino))
return false;
if (namelen >= MAXNAMELEN || namelen <= 0)
return false;
if (namelen == 1 && name[0] == '.')
return false;
if (!xfs_dir2_namecheck(name, namelen))
return false;
return true;
}
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);
}
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);
}
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;
while (i < namelen && name[i] != 0 && name[i] != '/')
i++;
if (i == 0)
return 0;
xname.len = i;
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);
error = xchk_iget(sc, ino, &ip);
if (error)
return 0;
if (xfs_is_metadir_inode(ip) != xfs_is_metadir_inode(rd->sc->ip)) {
xchk_irele(sc, ip);
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);
}
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);
}
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);
}
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;
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;
if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
offset += be16_to_cpu(dup->length);
continue;
}
offset += xfs_dir2_data_entsize(rd->sc->mp, dep->namelen);
if (offset > end)
break;
error = xrep_dir_salvage_data_entry(rd, dep);
if (error)
return error;
}
return 0;
}
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;
error = xrep_dir_salvage_sf_entry(rd, hdr, sfep);
if (error)
return error;
sfep = next;
}
return 0;
}
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;
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;
}
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;
}
}
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;
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 (magic_guess) {
hdr->magic = magic_guess;
goto recover;
}
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);
}
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);
}
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);
}
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;
xrep_tempfile_ilock(rd->sc);
xfs_trans_ijoin(rd->sc->tp, rd->sc->tempip, 0);
switch (dirent->action) {
case XREP_DIRENT_ADD:
#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:
#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;
}
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;
}
STATIC int
xrep_dir_replay_updates(
struct xrep_dir *rd)
{
xfarray_idx_t array_cur;
int error;
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);
}
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;
}
STATIC int
xrep_dir_flush_stashed(
struct xrep_dir *rd)
{
int error;
error = xrep_trans_commit(rd->sc);
if (error)
return error;
xchk_iunlock(rd->sc, XFS_ILOCK_EXCL);
error = xrep_tempfile_iolock_polled(rd->sc);
if (error)
return error;
error = xrep_dir_replay_updates(rd);
xrep_tempfile_iounlock(rd->sc);
if (error)
return error;
error = xchk_trans_alloc(rd->sc, 0);
if (error)
return error;
xchk_ilock(rd->sc, XFS_ILOCK_EXCL);
return 0;
}
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;
}
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);
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;
if (xrep_dir_want_flush_stashed(rd)) {
error = xrep_dir_flush_stashed(rd);
if (error)
return error;
}
}
}
return 0;
}
STATIC int
xrep_dir_find_entries(
struct xrep_dir *rd)
{
struct xfs_inode *dp = rd->sc->ip;
int error;
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);
}
STATIC int
xrep_dir_salvage_entries(
struct xrep_dir *rd)
{
struct xfs_scrub *sc = rd->sc;
int error;
xchk_iunlock(sc, XFS_ILOCK_EXCL);
error = xrep_dir_find_parent(rd);
xchk_ilock(sc, XFS_ILOCK_EXCL);
if (error)
return error;
error = xrep_dir_find_entries(rd);
if (error)
return error;
error = xrep_trans_commit(sc);
if (error)
return error;
xchk_iunlock(sc, XFS_ILOCK_EXCL);
return 0;
}
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;
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;
}
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;
if (ino != rd->sc->ip->i_ino)
return 0;
if (!xfs_verify_dir_ino(rd->sc->mp, ino))
return 0;
if (name->len >= MAXNAMELEN || name->len <= 0)
return 0;
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;
}
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);
}
static inline unsigned int
xrep_dir_scan_ilock(
struct xrep_dir *rd,
struct xfs_inode *ip)
{
uint lock_mode = XFS_ILOCK_SHARED;
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;
}
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 (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 (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;
}
STATIC int
xrep_dir_scan_dirtree(
struct xrep_dir *rd)
{
struct xfs_scrub *sc = rd->sc;
struct xfs_inode *ip;
int error;
if (xchk_inode_is_dirtree_root(sc->ip))
xrep_findparent_scan_found(&rd->pscan, sc->ip->i_ino);
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(&rd->pscan.iscan, &ip)) == 1) {
bool flush;
error = xrep_dir_scan_file(rd, ip);
xchk_irele(sc, ip);
if (error)
break;
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;
xchk_trans_alloc_empty(sc);
}
if (xchk_should_terminate(sc, &error))
break;
}
xchk_iscan_iter_finish(&rd->pscan.iscan);
if (error) {
if (error == -EBUSY)
return -ECANCELED;
return error;
}
xchk_trans_cancel(rd->sc);
return 0;
}
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;
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;
}
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;
}
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;
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);
xfs_idestroy_fork(ifp);
ifp->if_bytes = 0;
sc->tempip->i_disk_size = 0;
xrep_dir_init_args(rd, sc->tempip, NULL);
return xfs_dir2_sf_create(&rd->args, parent_ino);
}
STATIC int
xrep_dir_swap_prep(
struct xfs_scrub *sc,
bool temp_local,
bool ip_local)
{
int error;
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;
error = xfs_defer_finish(&sc->tp);
if (error)
return error;
}
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;
}
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);
}
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;
if (!xfs_inode_on_unlinked_list(dp))
goto reset_nlink;
if (rd->dirents == 0) {
rd->needs_adoption = false;
new_nlink = 0;
goto reset_nlink;
}
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;
}
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);
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;
}
STATIC int
xrep_dir_swap(
struct xrep_dir *rd)
{
struct xfs_scrub *sc = rd->sc;
xfs_ino_t ino;
bool ip_local, temp_local;
int error = 0;
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;
}
error = xchk_dir_lookup(sc, rd->sc->tempip, &xfs_name_dotdot, &ino);
if (error)
return error;
if (rd->pscan.parent_ino != ino) {
error = xrep_dir_replace(rd, rd->sc->tempip, &xfs_name_dotdot,
rd->pscan.parent_ino, rd->tx.req.resblks);
if (error)
return error;
}
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 (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);
}
error = xrep_tempfile_roll_trans(rd->sc);
if (error)
return error;
error = xrep_dir_swap_prep(sc, temp_local, ip_local);
if (error)
return error;
error = xrep_dir_set_nlink(rd);
if (error)
return error;
return xrep_tempexch_contents(sc, &rd->tx);
}
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);
error = xrep_tempfile_iolock_polled(rd->sc);
if (error)
return error;
error = xrep_dir_finalize_tempdir(rd);
if (error)
return error;
if (xchk_iscan_aborted(&rd->pscan.iscan))
return -ECANCELED;
error = xrep_dir_swap(rd);
if (error)
return error;
error = xrep_dir_reset_fork(rd, sc->mp->m_rootip->i_ino);
if (error)
return error;
error = xfs_trans_roll(&sc->tp);
if (error)
return error;
xrep_tempfile_iunlock(sc);
xrep_tempfile_iounlock(sc);
return 0;
}
STATIC int
xrep_dir_setup_scan(
struct xrep_dir *rd)
{
struct xfs_scrub *sc = rd->sc;
int error;
error = xfarray_create("directory entries", 0,
sizeof(struct xrep_dirent), &rd->dir_entries);
if (error)
return error;
error = xfblob_create("directory entry names", &rd->dir_names);
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;
}
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;
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &orig_parent);
if (error)
return error;
error = xrep_trans_commit(sc);
if (error)
return error;
xchk_iunlock(sc, XFS_ILOCK_EXCL);
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;
}
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;
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &new_parent);
if (error)
return error;
if (orig_parent == new_parent && VFS_I(sc->ip)->i_nlink > 0) {
error = xrep_adoption_move(&rd->adoption);
if (error)
return error;
}
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;
}
int
xrep_directory(
struct xfs_scrub *sc)
{
struct xrep_dir *rd = sc->buf;
int error;
if (!xfs_has_rmapbt(sc->mp))
return -EOPNOTSUPP;
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;
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;
}