#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_bmap_btree.h"
#include "xfs_dir2_priv.h"
#include "xfs_trans_space.h"
#include "xfs_health.h"
#include "xfs_exchmaps.h"
#include "xfs_parent.h"
#include "xfs_attr.h"
#include "xfs_bmap.h"
#include "xfs_ag.h"
#include "scrub/xfs_scrub.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
#include "scrub/repair.h"
#include "scrub/iscan.h"
#include "scrub/findparent.h"
#include "scrub/readdir.h"
#include "scrub/tempfile.h"
#include "scrub/tempexch.h"
#include "scrub/orphanage.h"
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
#include "scrub/xfblob.h"
#include "scrub/attr_repair.h"
#include "scrub/listxattr.h"
#define XREP_PPTR_ADD (1)
#define XREP_PPTR_REMOVE (2)
struct xrep_pptr {
xfblob_cookie name_cookie;
struct xfs_parent_rec pptr_rec;
uint8_t namelen;
uint8_t action;
};
#define XREP_PARENT_MAX_STASH_BYTES (PAGE_SIZE * 8)
struct xrep_parent {
struct xfs_scrub *sc;
struct xfarray *pptr_recs;
struct xfblob *pptr_names;
struct xfarray *xattr_records;
struct xfblob *xattr_blobs;
unsigned char *xattr_name;
void *xattr_value;
unsigned int xattr_value_sz;
struct xrep_tempexch tx;
struct xrep_parent_scan_info pscan;
struct xrep_adoption adoption;
struct xfs_name xname;
unsigned char namebuf[MAXNAMELEN];
struct xfs_da_args pptr_args;
bool saw_pptr_updates;
unsigned long long parents;
};
struct xrep_parent_xattr {
xfblob_cookie name_cookie;
xfblob_cookie value_cookie;
int flags;
uint32_t valuelen;
uint16_t namelen;
};
#define XREP_PARENT_XATTR_MAX_STASH_BYTES (PAGE_SIZE * 8)
static void
xrep_parent_teardown(
struct xrep_parent *rp)
{
xrep_findparent_scan_teardown(&rp->pscan);
kvfree(rp->xattr_name);
rp->xattr_name = NULL;
kvfree(rp->xattr_value);
rp->xattr_value = NULL;
if (rp->xattr_blobs)
xfblob_destroy(rp->xattr_blobs);
rp->xattr_blobs = NULL;
if (rp->xattr_records)
xfarray_destroy(rp->xattr_records);
rp->xattr_records = NULL;
if (rp->pptr_names)
xfblob_destroy(rp->pptr_names);
rp->pptr_names = NULL;
if (rp->pptr_recs)
xfarray_destroy(rp->pptr_recs);
rp->pptr_recs = NULL;
}
int
xrep_setup_parent(
struct xfs_scrub *sc)
{
struct xrep_parent *rp;
int error;
xchk_fsgates_enable(sc, XCHK_FSGATES_DIRENTS);
rp = kvzalloc_obj(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);
}
STATIC int
xrep_parent_find_dotdot(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
xfs_ino_t ino;
unsigned int sick, checked;
int error;
xfs_inode_measure_sickness(sc->ip, &sick, &checked);
if (sick & XFS_SICK_INO_DIR)
return -EFSCORRUPTED;
ino = xrep_findparent_self_reference(sc);
if (ino != NULLFSINO) {
xrep_findparent_scan_finish_early(&rp->pscan, ino);
return 0;
}
xchk_iunlock(sc, XFS_ILOCK_EXCL);
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;
}
}
error = xrep_findparent_scan(&rp->pscan);
out_relock:
xchk_ilock(sc, XFS_ILOCK_EXCL);
return error;
}
STATIC int
xrep_parent_replay_update(
struct xrep_parent *rp,
const struct xfs_name *xname,
struct xrep_pptr *pptr)
{
struct xfs_scrub *sc = rp->sc;
switch (pptr->action) {
case XREP_PPTR_ADD:
trace_xrep_parent_replay_parentadd(sc->tempip, xname,
&pptr->pptr_rec);
return xfs_parent_set(sc->tempip, sc->ip->i_ino, xname,
&pptr->pptr_rec, &rp->pptr_args);
case XREP_PPTR_REMOVE:
trace_xrep_parent_replay_parentremove(sc->tempip, xname,
&pptr->pptr_rec);
return xfs_parent_unset(sc->tempip, sc->ip->i_ino, xname,
&pptr->pptr_rec, &rp->pptr_args);
}
ASSERT(0);
return -EIO;
}
STATIC int
xrep_parent_replay_updates(
struct xrep_parent *rp)
{
xfarray_idx_t array_cur;
int error;
mutex_lock(&rp->pscan.lock);
foreach_xfarray_idx(rp->pptr_recs, array_cur) {
struct xrep_pptr pptr;
error = xfarray_load(rp->pptr_recs, array_cur, &pptr);
if (error)
goto out_unlock;
error = xfblob_loadname(rp->pptr_names, pptr.name_cookie,
&rp->xname, pptr.namelen);
if (error)
goto out_unlock;
rp->xname.len = pptr.namelen;
mutex_unlock(&rp->pscan.lock);
error = xrep_parent_replay_update(rp, &rp->xname, &pptr);
if (error)
return error;
mutex_lock(&rp->pscan.lock);
}
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;
}
STATIC int
xrep_parent_stash_parentadd(
struct xrep_parent *rp,
const struct xfs_name *name,
const struct xfs_inode *dp)
{
struct xrep_pptr pptr = {
.action = XREP_PPTR_ADD,
.namelen = name->len,
};
int error;
trace_xrep_parent_stash_parentadd(rp->sc->tempip, dp, name);
xfs_inode_to_parent_rec(&pptr.pptr_rec, dp);
error = xfblob_storename(rp->pptr_names, &pptr.name_cookie, name);
if (error)
return error;
return xfarray_append(rp->pptr_recs, &pptr);
}
STATIC int
xrep_parent_stash_parentremove(
struct xrep_parent *rp,
const struct xfs_name *name,
const struct xfs_inode *dp)
{
struct xrep_pptr pptr = {
.action = XREP_PPTR_REMOVE,
.namelen = name->len,
};
int error;
trace_xrep_parent_stash_parentremove(rp->sc->tempip, dp, name);
xfs_inode_to_parent_rec(&pptr.pptr_rec, dp);
error = xfblob_storename(rp->pptr_names, &pptr.name_cookie, name);
if (error)
return error;
return xfarray_append(rp->pptr_recs, &pptr);
}
STATIC int
xrep_parent_scan_dirent(
struct xfs_scrub *sc,
struct xfs_inode *dp,
xfs_dir2_dataptr_t dapos,
const struct xfs_name *name,
xfs_ino_t ino,
void *priv)
{
struct xrep_parent *rp = priv;
int error;
if (ino != rp->sc->ip->i_ino)
return 0;
if (name->len == 0 || !xfs_dir2_namecheck(name->name, name->len))
return -EFSCORRUPTED;
if (name->type != xfs_mode_to_ftype(VFS_I(sc->ip)->i_mode))
return -EFSCORRUPTED;
if (xfs_dir2_samename(name, &xfs_name_dotdot) ||
xfs_dir2_samename(name, &xfs_name_dot))
return 0;
mutex_lock(&rp->pscan.lock);
error = xrep_parent_stash_parentadd(rp, name, dp);
mutex_unlock(&rp->pscan.lock);
return error;
}
static inline bool
xrep_parent_want_scan(
struct xrep_parent *rp,
const struct xfs_inode *ip)
{
return ip != rp->sc->ip && !xrep_is_tempfile(ip);
}
static inline unsigned int
xrep_parent_scan_ilock(
struct xrep_parent *rp,
struct xfs_inode *ip)
{
uint lock_mode = XFS_ILOCK_SHARED;
if (!xrep_parent_want_scan(rp, ip))
goto lock;
if (S_ISDIR(VFS_I(ip)->i_mode) && xfs_need_iread_extents(&ip->i_df)) {
lock_mode = XFS_ILOCK_EXCL;
goto lock;
}
lock:
xfs_ilock(ip, lock_mode);
return lock_mode;
}
STATIC int
xrep_parent_scan_file(
struct xrep_parent *rp,
struct xfs_inode *ip)
{
unsigned int lock_mode;
int error = 0;
lock_mode = xrep_parent_scan_ilock(rp, ip);
if (!xrep_parent_want_scan(rp, ip))
goto scan_done;
if (S_ISDIR(VFS_I(ip)->i_mode)) {
if (xchk_dir_looks_zapped(ip)) {
error = -EBUSY;
goto scan_done;
}
error = xchk_dir_walk(rp->sc, ip, xrep_parent_scan_dirent, rp);
if (error)
goto scan_done;
}
scan_done:
xchk_iscan_mark_visited(&rp->pscan.iscan, ip);
xfs_iunlock(ip, lock_mode);
return error;
}
static inline bool
xrep_parent_want_flush_stashed(
struct xrep_parent *rp)
{
unsigned long long bytes;
bytes = xfarray_bytes(rp->pptr_recs) + xfblob_bytes(rp->pptr_names);
return bytes > XREP_PARENT_MAX_STASH_BYTES;
}
STATIC int
xrep_parent_scan_dirtree(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
struct xfs_inode *ip;
int error;
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;
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 (error == -EBUSY)
return -ECANCELED;
return error;
}
xchk_ilock(rp->sc, XFS_ILOCK_EXCL);
return 0;
}
STATIC int
xrep_parent_live_update(
struct notifier_block *nb,
unsigned long action,
void *data)
{
struct xfs_dir_update_params *p = data;
struct xrep_parent *rp;
struct xfs_scrub *sc;
int error;
rp = container_of(nb, struct xrep_parent, pscan.dhook.dirent_hook.nb);
sc = rp->sc;
if (p->ip->i_ino == sc->ip->i_ino &&
xchk_iscan_want_live_update(&rp->pscan.iscan, p->dp->i_ino)) {
mutex_lock(&rp->pscan.lock);
if (p->delta > 0)
error = xrep_parent_stash_parentadd(rp, p->name, p->dp);
else
error = xrep_parent_stash_parentremove(rp, p->name,
p->dp);
if (!error)
rp->saw_pptr_updates = true;
mutex_unlock(&rp->pscan.lock);
if (error)
goto out_abort;
}
return NOTIFY_DONE;
out_abort:
xchk_iscan_abort(&rp->pscan.iscan);
return NOTIFY_DONE;
}
STATIC int
xrep_parent_reset_dotdot(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
xfs_ino_t ino;
unsigned int spaceres;
int error = 0;
ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL);
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &ino);
if (error || ino == rp->pscan.parent_ino)
return error;
xfs_trans_ijoin(sc->tp, sc->ip, 0);
trace_xrep_parent_reset_dotdot(sc->ip, rp->pscan.parent_ino);
spaceres = xfs_rename_space_res(sc->mp, 0, false, xfs_name_dotdot.len,
false);
error = xfs_trans_reserve_more_inode(sc->tp, sc->ip, spaceres, 0,
true);
if (error)
return error;
error = xfs_dir_replace(sc->tp, sc->ip, &xfs_name_dotdot,
rp->pscan.parent_ino, spaceres);
if (error)
return error;
return xfs_trans_roll(&sc->tp);
}
STATIC int
xrep_parent_lookup_pptr(
struct xfs_scrub *sc,
struct xfs_inode *ip,
unsigned int attr_flags,
const unsigned char *name,
unsigned int namelen,
const void *value,
unsigned int valuelen,
void *priv)
{
xfs_ino_t *inop = priv;
xfs_ino_t parent_ino;
int error;
if (!(attr_flags & XFS_ATTR_PARENT))
return 0;
error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
valuelen, &parent_ino, NULL);
if (error)
return error;
*inop = parent_ino;
return -ECANCELED;
}
STATIC int
xrep_parent_lookup_pptrs(
struct xfs_scrub *sc,
xfs_ino_t *inop)
{
int error;
*inop = NULLFSINO;
error = xchk_xattr_walk(sc, sc->ip, xrep_parent_lookup_pptr, NULL,
inop);
if (error && error != -ECANCELED)
return error;
return 0;
}
STATIC int
xrep_parent_move_to_orphanage(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
xfs_ino_t orig_parent, new_parent;
int error;
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot,
&orig_parent);
if (error)
return error;
} else {
orig_parent = NULLFSINO;
}
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, &rp->adoption);
if (error)
return error;
error = xrep_adoption_compute_name(&rp->adoption, &rp->xname);
if (error)
return error;
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;
if (orig_parent == new_parent && VFS_I(sc->ip)->i_nlink > 0) {
error = xrep_adoption_move(&rp->adoption);
if (error)
return error;
}
error = xrep_adoption_trans_roll(&rp->adoption);
if (error)
return error;
xrep_orphanage_iunlock(sc, XFS_ILOCK_EXCL);
xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
return 0;
}
STATIC int
xrep_parent_alloc_xattr_value(
struct xrep_parent *rp,
size_t bufsize)
{
void *new_val;
if (rp->xattr_value_sz >= bufsize)
return 0;
if (rp->xattr_value) {
kvfree(rp->xattr_value);
rp->xattr_value = NULL;
rp->xattr_value_sz = 0;
}
new_val = kvmalloc(bufsize, XCHK_GFP_FLAGS);
if (!new_val)
return -ENOMEM;
rp->xattr_value = new_val;
rp->xattr_value_sz = bufsize;
return 0;
}
STATIC int
xrep_parent_fetch_xattr_remote(
struct xrep_parent *rp,
struct xfs_inode *ip,
unsigned int attr_flags,
const unsigned char *name,
unsigned int namelen,
unsigned int valuelen)
{
struct xfs_scrub *sc = rp->sc;
struct xfs_da_args args = {
.attr_filter = attr_flags & XFS_ATTR_NSP_ONDISK_MASK,
.geo = sc->mp->m_attr_geo,
.whichfork = XFS_ATTR_FORK,
.dp = ip,
.name = name,
.namelen = namelen,
.trans = sc->tp,
.valuelen = valuelen,
.owner = ip->i_ino,
};
int error;
error = xrep_parent_alloc_xattr_value(rp, valuelen);
if (error == -ENOMEM)
return -EDEADLOCK;
if (error)
return error;
args.value = rp->xattr_value;
xfs_attr_sethash(&args);
return xfs_attr_get_ilocked(&args);
}
STATIC int
xrep_parent_stash_xattr(
struct xfs_scrub *sc,
struct xfs_inode *ip,
unsigned int attr_flags,
const unsigned char *name,
unsigned int namelen,
const void *value,
unsigned int valuelen,
void *priv)
{
struct xrep_parent_xattr key = {
.valuelen = valuelen,
.namelen = namelen,
.flags = attr_flags & XFS_ATTR_NSP_ONDISK_MASK,
};
struct xrep_parent *rp = priv;
int error;
if (attr_flags & (XFS_ATTR_INCOMPLETE | XFS_ATTR_PARENT))
return 0;
if (!value) {
error = xrep_parent_fetch_xattr_remote(rp, ip, attr_flags,
name, namelen, valuelen);
if (error)
return error;
value = rp->xattr_value;
}
trace_xrep_parent_stash_xattr(rp->sc->tempip, key.flags, (void *)name,
key.namelen, key.valuelen);
error = xfblob_store(rp->xattr_blobs, &key.name_cookie, name,
key.namelen);
if (error)
return error;
error = xfblob_store(rp->xattr_blobs, &key.value_cookie, value,
key.valuelen);
if (error)
return error;
return xfarray_append(rp->xattr_records, &key);
}
STATIC int
xrep_parent_insert_xattr(
struct xrep_parent *rp,
const struct xrep_parent_xattr *key)
{
struct xfs_da_args args = {
.dp = rp->sc->tempip,
.attr_filter = key->flags,
.namelen = key->namelen,
.valuelen = key->valuelen,
.owner = rp->sc->ip->i_ino,
.geo = rp->sc->mp->m_attr_geo,
.whichfork = XFS_ATTR_FORK,
.op_flags = XFS_DA_OP_OKNOENT,
};
int error;
ASSERT(!(key->flags & XFS_ATTR_PARENT));
args.name = rp->xattr_name;
args.value = rp->xattr_value;
rp->xattr_name[XATTR_NAME_MAX] = 0;
error = xfblob_load(rp->xattr_blobs, key->name_cookie, rp->xattr_name,
key->namelen);
if (error)
return error;
error = xfblob_free(rp->xattr_blobs, key->name_cookie);
if (error)
return error;
error = xfblob_load(rp->xattr_blobs, key->value_cookie, args.value,
key->valuelen);
if (error)
return error;
error = xfblob_free(rp->xattr_blobs, key->value_cookie);
if (error)
return error;
rp->xattr_name[key->namelen] = 0;
trace_xrep_parent_insert_xattr(rp->sc->tempip, key->flags,
rp->xattr_name, key->namelen, key->valuelen);
xfs_attr_sethash(&args);
return xfs_attr_set(&args, XFS_ATTRUPDATE_UPSERT, false);
}
STATIC int
xrep_parent_flush_xattrs(
struct xrep_parent *rp)
{
xfarray_idx_t array_cur;
int error;
xchk_trans_cancel(rp->sc);
xchk_iunlock(rp->sc, XFS_ILOCK_EXCL);
error = xrep_tempfile_iolock_polled(rp->sc);
if (error)
return error;
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;
}
xfarray_truncate(rp->xattr_records);
xfblob_truncate(rp->xattr_blobs);
xrep_tempfile_iounlock(rp->sc);
xchk_trans_alloc_empty(rp->sc);
xchk_ilock(rp->sc, XFS_ILOCK_EXCL);
return 0;
}
static inline bool
xrep_parent_want_flush_xattrs(
struct xrep_parent *rp)
{
unsigned long long bytes;
bytes = xfarray_bytes(rp->xattr_records) +
xfblob_bytes(rp->xattr_blobs);
return bytes > XREP_PARENT_XATTR_MAX_STASH_BYTES;
}
STATIC int
xrep_parent_try_flush_xattrs(
struct xfs_scrub *sc,
void *priv)
{
struct xrep_parent *rp = priv;
int error;
if (!xrep_parent_want_flush_xattrs(rp))
return 0;
error = xrep_parent_flush_xattrs(rp);
if (error)
return error;
mutex_lock(&rp->pscan.lock);
if (rp->saw_pptr_updates)
error = -ESTALE;
mutex_unlock(&rp->pscan.lock);
return error;
}
STATIC int
xrep_parent_copy_xattrs(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
int error;
mutex_lock(&rp->pscan.lock);
rp->saw_pptr_updates = false;
mutex_unlock(&rp->pscan.lock);
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) {
error = xchk_xattr_walk(sc, sc->ip, xrep_parent_stash_xattr,
NULL, rp);
if (error)
return error;
}
if (xfarray_bytes(rp->xattr_records) == 0)
return 0;
return xrep_parent_flush_xattrs(rp);
}
STATIC int
xrep_parent_ensure_attr_fork(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
int error;
error = xfs_attr_add_fork(sc->tempip,
sizeof(struct xfs_attr_sf_hdr), 1);
if (error)
return error;
return xfs_attr_add_fork(sc->ip, sizeof(struct xfs_attr_sf_hdr), 1);
}
STATIC int
xrep_parent_finalize_tempfile(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
int error;
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;
}
STATIC int
xrep_parent_rebuild_pptrs(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
xfs_ino_t parent_ino = NULLFSINO;
int error;
error = xrep_parent_copy_xattrs(rp);
if (error)
return error;
xchk_trans_cancel(sc);
xchk_iunlock(sc, XFS_ILOCK_EXCL);
error = xrep_tempfile_iolock_polled(sc);
if (error)
return error;
error = xrep_parent_finalize_tempfile(rp);
if (error)
return error;
if (xchk_should_terminate(sc, &error))
return error;
if (xchk_iscan_aborted(&rp->pscan.iscan))
return -ECANCELED;
error = xrep_xattr_swap(sc, &rp->tx);
if (error)
return error;
error = xrep_xattr_reset_tempfile_fork(sc);
if (error)
return error;
error = xfs_trans_roll(&sc->tp);
if (error)
return error;
xrep_tempfile_iunlock(sc);
xrep_tempfile_iounlock(sc);
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;
}
STATIC int
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;
}
try_adoption = rp->pscan.parent_ino == NULLFSINO;
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;
}
STATIC int
xrep_parent_count_pptr(
struct xfs_scrub *sc,
struct xfs_inode *ip,
unsigned int attr_flags,
const unsigned char *name,
unsigned int namelen,
const void *value,
unsigned int valuelen,
void *priv)
{
struct xrep_parent *rp = priv;
int error;
if (!(attr_flags & XFS_ATTR_PARENT))
return 0;
error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
valuelen, NULL, NULL);
if (error)
return error;
rp->parents++;
return 0;
}
STATIC int
xrep_parent_set_nondir_nlink(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
struct xfs_inode *ip = sc->ip;
struct xfs_perag *pag;
bool joined = false;
int error;
rp->parents = 0;
error = xchk_xattr_walk(sc, ip, xrep_parent_count_pptr, NULL, rp);
if (error)
return error;
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;
pag = xfs_perag_get(sc->mp, XFS_INO_TO_AGNO(sc->mp, ip->i_ino));
if (!pag) {
ASSERT(0);
return -EFSCORRUPTED;
}
error = xfs_iunlink_remove(sc->tp, pag, ip);
xfs_perag_put(pag);
if (error)
return error;
} else if (rp->parents == 0 && !xfs_inode_on_unlinked_list(ip)) {
xfs_trans_ijoin(sc->tp, sc->ip, 0);
joined = true;
error = xfs_iunlink(sc->tp, ip);
if (error)
return error;
}
if (VFS_I(ip)->i_nlink != rp->parents) {
if (!joined) {
xfs_trans_ijoin(sc->tp, sc->ip, 0);
joined = true;
}
set_nlink(VFS_I(ip), min_t(unsigned long long, rp->parents,
XFS_NLINK_PINNED));
}
if (joined)
xfs_trans_log_inode(sc->tp, ip, XFS_ILOG_CORE);
return 0;
}
STATIC int
xrep_parent_setup_scan(
struct xrep_parent *rp)
{
struct xfs_scrub *sc = rp->sc;
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);
rp->xattr_name = kvmalloc(XATTR_NAME_MAX + 1, XCHK_GFP_FLAGS);
if (!rp->xattr_name)
return -ENOMEM;
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;
error = xfarray_create("parent pointer entries", 0,
sizeof(struct xrep_pptr), &rp->pptr_recs);
if (error)
goto out_xattr_value;
error = xfblob_create("parent pointer names", &rp->pptr_names);
if (error)
goto out_recs;
error = xfarray_create("parent pointer xattr entries", 0,
sizeof(struct xrep_parent_xattr), &rp->xattr_records);
if (error)
goto out_names;
error = xfblob_create("parent pointer xattr values", &rp->xattr_blobs);
if (error)
goto out_attr_keys;
error = __xrep_findparent_scan_start(sc, &rp->pscan,
xrep_parent_live_update);
if (error)
goto out_attr_values;
return 0;
out_attr_values:
xfblob_destroy(rp->xattr_blobs);
rp->xattr_blobs = NULL;
out_attr_keys:
xfarray_destroy(rp->xattr_records);
rp->xattr_records = NULL;
out_names:
xfblob_destroy(rp->pptr_names);
rp->pptr_names = NULL;
out_recs:
xfarray_destroy(rp->pptr_recs);
rp->pptr_recs = NULL;
out_xattr_value:
kvfree(rp->xattr_value);
rp->xattr_value = NULL;
out_xattr_name:
kvfree(rp->xattr_name);
rp->xattr_name = NULL;
return error;
}
int
xrep_parent(
struct xfs_scrub *sc)
{
struct xrep_parent *rp = sc->buf;
int error;
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;
if (xchk_should_terminate(sc, &error))
goto out_teardown;
error = xrep_parent_rebuild_tree(rp);
if (error)
goto out_teardown;
if (xfs_has_parent(sc->mp) && !S_ISDIR(VFS_I(sc->ip)->i_mode)) {
error = xrep_parent_set_nondir_nlink(rp);
if (error)
goto out_teardown;
}
error = xrep_defer_finish(sc);
out_teardown:
xrep_parent_teardown(rp);
return error;
}