#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_btree.h"
#include "xfs_bit.h"
#include "xfs_log_format.h"
#include "xfs_trans.h"
#include "xfs_sb.h"
#include "xfs_inode.h"
#include "xfs_da_format.h"
#include "xfs_da_btree.h"
#include "xfs_dir2.h"
#include "xfs_attr.h"
#include "xfs_attr_leaf.h"
#include "xfs_attr_sf.h"
#include "xfs_attr_remote.h"
#include "xfs_bmap.h"
#include "xfs_bmap_util.h"
#include "xfs_exchmaps.h"
#include "xfs_exchrange.h"
#include "xfs_acl.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/attr.h"
#include "scrub/reap.h"
#include "scrub/attr_repair.h"
struct xrep_xattr_key {
xfblob_cookie name_cookie;
xfblob_cookie value_cookie;
int flags;
uint32_t valuelen;
uint16_t namelen;
};
#define XREP_XATTR_MAX_STASH_BYTES (PAGE_SIZE * 8)
struct xrep_xattr {
struct xfs_scrub *sc;
struct xrep_tempexch tx;
struct xfarray *xattr_records;
struct xfblob *xattr_blobs;
unsigned long long attrs_found;
bool can_flush;
bool live_update_aborted;
struct mutex lock;
struct xfarray *pptr_recs;
struct xfblob *pptr_names;
struct xfs_dir_hook dhook;
struct xfs_da_args pptr_args;
struct xfs_name xname;
char namebuf[MAXNAMELEN];
};
#define XREP_XATTR_PPTR_ADD (1)
#define XREP_XATTR_PPTR_REMOVE (2)
struct xrep_xattr_pptr {
xfblob_cookie name_cookie;
struct xfs_parent_rec pptr_rec;
uint8_t namelen;
uint8_t action;
};
int
xrep_setup_xattr(
struct xfs_scrub *sc)
{
if (xfs_has_parent(sc->mp))
xchk_fsgates_enable(sc, XCHK_FSGATES_DIRENTS);
return xrep_tempfile_create(sc, S_IFREG);
}
STATIC int
xrep_xattr_want_salvage(
struct xrep_xattr *rx,
unsigned int attr_flags,
const void *name,
int namelen,
const void *value,
int valuelen)
{
if (attr_flags & XFS_ATTR_INCOMPLETE)
return false;
if (namelen > XATTR_NAME_MAX || namelen <= 0)
return false;
if (!xfs_attr_namecheck(attr_flags, name, namelen))
return false;
if (valuelen > XATTR_SIZE_MAX || valuelen < 0)
return false;
if (attr_flags & XFS_ATTR_PARENT)
return xfs_parent_valuecheck(rx->sc->mp, value, valuelen);
return true;
}
STATIC int
xrep_xattr_salvage_key(
struct xrep_xattr *rx,
int flags,
unsigned char *name,
int namelen,
unsigned char *value,
int valuelen)
{
struct xrep_xattr_key key = {
.valuelen = valuelen,
.flags = flags & XFS_ATTR_NSP_ONDISK_MASK,
};
unsigned int i = 0;
int error = 0;
if (xchk_should_terminate(rx->sc, &error))
return error;
if (flags & XFS_ATTR_PARENT) {
key.namelen = namelen;
trace_xrep_xattr_salvage_pptr(rx->sc->ip, flags, name,
key.namelen, value, valuelen);
} else {
while (i < namelen && name[i] != 0)
i++;
if (i == 0)
return 0;
key.namelen = i;
trace_xrep_xattr_salvage_rec(rx->sc->ip, flags, name,
key.namelen, valuelen);
}
error = xfblob_store(rx->xattr_blobs, &key.name_cookie, name,
key.namelen);
if (error)
return error;
error = xfblob_store(rx->xattr_blobs, &key.value_cookie, value,
key.valuelen);
if (error)
return error;
error = xfarray_append(rx->xattr_records, &key);
if (error)
return error;
rx->attrs_found++;
return 0;
}
STATIC int
xrep_xattr_salvage_sf_attr(
struct xrep_xattr *rx,
struct xfs_attr_sf_hdr *hdr,
struct xfs_attr_sf_entry *sfe)
{
struct xfs_scrub *sc = rx->sc;
struct xchk_xattr_buf *ab = sc->buf;
unsigned char *name = sfe->nameval;
unsigned char *value = &sfe->nameval[sfe->namelen];
if (!xchk_xattr_set_map(sc, ab->usedmap, (char *)name - (char *)hdr,
sfe->namelen))
return 0;
if (!xchk_xattr_set_map(sc, ab->usedmap, (char *)value - (char *)hdr,
sfe->valuelen))
return 0;
if (!xrep_xattr_want_salvage(rx, sfe->flags, sfe->nameval,
sfe->namelen, value, sfe->valuelen))
return 0;
return xrep_xattr_salvage_key(rx, sfe->flags, sfe->nameval,
sfe->namelen, value, sfe->valuelen);
}
STATIC int
xrep_xattr_salvage_local_attr(
struct xrep_xattr *rx,
struct xfs_attr_leaf_entry *ent,
unsigned int nameidx,
const char *buf_end,
struct xfs_attr_leaf_name_local *lentry)
{
struct xchk_xattr_buf *ab = rx->sc->buf;
unsigned char *value;
unsigned int valuelen;
unsigned int namesize;
value = &lentry->nameval[lentry->namelen];
valuelen = be16_to_cpu(lentry->valuelen);
namesize = xfs_attr_leaf_entsize_local(lentry->namelen, valuelen);
if ((char *)lentry + namesize > buf_end)
return 0;
if (!xrep_xattr_want_salvage(rx, ent->flags, lentry->nameval,
lentry->namelen, value, valuelen))
return 0;
if (!xchk_xattr_set_map(rx->sc, ab->usedmap, nameidx, namesize))
return 0;
return xrep_xattr_salvage_key(rx, ent->flags, lentry->nameval,
lentry->namelen, value, valuelen);
}
STATIC int
xrep_xattr_salvage_remote_attr(
struct xrep_xattr *rx,
struct xfs_attr_leaf_entry *ent,
unsigned int nameidx,
const char *buf_end,
struct xfs_attr_leaf_name_remote *rentry,
unsigned int ent_idx,
struct xfs_buf *leaf_bp)
{
struct xchk_xattr_buf *ab = rx->sc->buf;
struct xfs_da_args args = {
.trans = rx->sc->tp,
.dp = rx->sc->ip,
.index = ent_idx,
.geo = rx->sc->mp->m_attr_geo,
.owner = rx->sc->ip->i_ino,
.attr_filter = ent->flags & XFS_ATTR_NSP_ONDISK_MASK,
.namelen = rentry->namelen,
.name = rentry->name,
.valuelen = be32_to_cpu(rentry->valuelen),
};
unsigned int namesize;
int error;
namesize = xfs_attr_leaf_entsize_remote(rentry->namelen);
if ((char *)rentry + namesize > buf_end)
return 0;
if (args.valuelen == 0 ||
!xrep_xattr_want_salvage(rx, ent->flags, rentry->name,
rentry->namelen, NULL, args.valuelen))
return 0;
if (!xchk_xattr_set_map(rx->sc, ab->usedmap, nameidx, namesize))
return 0;
error = xchk_setup_xattr_buf(rx->sc, args.valuelen);
if (error == -ENOMEM)
error = -EDEADLOCK;
if (error)
return error;
args.value = ab->value;
error = xfs_attr3_leaf_getvalue(leaf_bp, &args);
if (error || args.rmtblkno == 0)
goto err_free;
error = xfs_attr_rmtval_get(&args);
if (error)
goto err_free;
error = xrep_xattr_salvage_key(rx, ent->flags, rentry->name,
rentry->namelen, ab->value, args.valuelen);
err_free:
if (error == -EFSBADCRC || error == -EFSCORRUPTED)
error = 0;
return error;
}
STATIC int
xrep_xattr_recover_leaf(
struct xrep_xattr *rx,
struct xfs_buf *bp)
{
struct xfs_attr3_icleaf_hdr leafhdr;
struct xfs_scrub *sc = rx->sc;
struct xfs_mount *mp = sc->mp;
struct xfs_attr_leafblock *leaf;
struct xfs_attr_leaf_name_local *lentry;
struct xfs_attr_leaf_name_remote *rentry;
struct xfs_attr_leaf_entry *ent;
struct xfs_attr_leaf_entry *entries;
struct xchk_xattr_buf *ab = rx->sc->buf;
char *buf_end;
size_t off;
unsigned int nameidx;
unsigned int hdrsize;
int i;
int error = 0;
bitmap_zero(ab->usedmap, mp->m_attr_geo->blksize);
leaf = bp->b_addr;
xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &leafhdr, leaf);
hdrsize = xfs_attr3_leaf_hdr_size(leaf);
xchk_xattr_set_map(sc, ab->usedmap, 0, hdrsize);
entries = xfs_attr3_leaf_entryp(leaf);
buf_end = (char *)bp->b_addr + mp->m_attr_geo->blksize;
for (i = 0, ent = entries; i < leafhdr.count; ent++, i++) {
if (xchk_should_terminate(sc, &error))
return error;
off = (char *)ent - (char *)leaf;
if (!xchk_xattr_set_map(sc, ab->usedmap, off,
sizeof(xfs_attr_leaf_entry_t)))
continue;
nameidx = be16_to_cpu(ent->nameidx);
if (nameidx < leafhdr.firstused ||
nameidx >= mp->m_attr_geo->blksize)
continue;
if (ent->flags & XFS_ATTR_LOCAL) {
lentry = xfs_attr3_leaf_name_local(leaf, i);
error = xrep_xattr_salvage_local_attr(rx, ent, nameidx,
buf_end, lentry);
} else {
rentry = xfs_attr3_leaf_name_remote(leaf, i);
error = xrep_xattr_salvage_remote_attr(rx, ent, nameidx,
buf_end, rentry, i, bp);
}
if (error)
return error;
}
return 0;
}
STATIC int
xrep_xattr_recover_sf(
struct xrep_xattr *rx)
{
struct xfs_scrub *sc = rx->sc;
struct xchk_xattr_buf *ab = sc->buf;
struct xfs_attr_sf_hdr *hdr;
struct xfs_attr_sf_entry *sfe;
struct xfs_attr_sf_entry *next;
struct xfs_ifork *ifp;
unsigned char *end;
int i;
int error = 0;
ifp = xfs_ifork_ptr(rx->sc->ip, XFS_ATTR_FORK);
hdr = ifp->if_data;
bitmap_zero(ab->usedmap, ifp->if_bytes);
end = (unsigned char *)ifp->if_data + ifp->if_bytes;
xchk_xattr_set_map(sc, ab->usedmap, 0, sizeof(*hdr));
sfe = xfs_attr_sf_firstentry(hdr);
if ((unsigned char *)sfe > end)
return 0;
for (i = 0; i < hdr->count; i++) {
if (xchk_should_terminate(sc, &error))
return error;
next = xfs_attr_sf_nextentry(sfe);
if ((unsigned char *)next > end)
break;
if (xchk_xattr_set_map(sc, ab->usedmap,
(char *)sfe - (char *)hdr,
sizeof(struct xfs_attr_sf_entry))) {
error = xrep_xattr_salvage_sf_attr(rx, hdr, sfe);
if (error)
return error;
}
sfe = next;
}
return 0;
}
STATIC int
xrep_xattr_find_buf(
struct xfs_mount *mp,
xfs_fsblock_t fsbno,
xfs_extlen_t max_len,
bool can_read,
struct xfs_buf **bpp)
{
struct xrep_bufscan scan = {
.daddr = XFS_FSB_TO_DADDR(mp, fsbno),
.max_sectors = xrep_bufscan_max_sectors(mp, max_len),
.daddr_step = XFS_FSB_TO_BB(mp, 1),
};
struct xfs_buf *bp;
while ((bp = xrep_bufscan_advance(mp, &scan)) != NULL) {
*bpp = bp;
return 0;
}
if (!can_read) {
*bpp = NULL;
return 0;
}
return xfs_buf_read(mp->m_ddev_targp, scan.daddr, XFS_FSB_TO_BB(mp, 1),
XBF_TRYLOCK, bpp, NULL);
}
STATIC int
xrep_xattr_recover_block(
struct xrep_xattr *rx,
xfs_dablk_t dabno,
xfs_fsblock_t fsbno,
xfs_extlen_t max_len,
xfs_extlen_t *actual_len)
{
struct xfs_da_blkinfo *info;
struct xfs_buf *bp;
int error;
error = xrep_xattr_find_buf(rx->sc->mp, fsbno, max_len, true, &bp);
if (error)
return error;
info = bp->b_addr;
*actual_len = XFS_BB_TO_FSB(rx->sc->mp, bp->b_length);
trace_xrep_xattr_recover_leafblock(rx->sc->ip, dabno,
be16_to_cpu(info->magic));
if (info->magic == cpu_to_be16(XFS_ATTR3_LEAF_MAGIC) &&
xrep_buf_verify_struct(bp, &xfs_attr3_leaf_buf_ops) &&
xfs_attr3_leaf_header_check(bp, rx->sc->ip->i_ino) == NULL)
error = xrep_xattr_recover_leaf(rx, bp);
if (bp->b_ops == NULL)
xfs_buf_stale(bp);
xfs_buf_relse(bp);
return error;
}
STATIC int
xrep_xattr_insert_rec(
struct xrep_xattr *rx,
const struct xrep_xattr_key *key)
{
struct xfs_da_args args = {
.dp = rx->sc->tempip,
.attr_filter = key->flags,
.namelen = key->namelen,
.valuelen = key->valuelen,
.owner = rx->sc->ip->i_ino,
.geo = rx->sc->mp->m_attr_geo,
.whichfork = XFS_ATTR_FORK,
.op_flags = XFS_DA_OP_OKNOENT,
};
struct xchk_xattr_buf *ab = rx->sc->buf;
int error;
args.name = ab->name;
args.value = ab->value;
ab->name[XATTR_NAME_MAX] = 0;
error = xfblob_load(rx->xattr_blobs, key->name_cookie, ab->name,
key->namelen);
if (error)
return error;
error = xfblob_free(rx->xattr_blobs, key->name_cookie);
if (error)
return error;
error = xfblob_load(rx->xattr_blobs, key->value_cookie, args.value,
key->valuelen);
if (error)
return error;
error = xfblob_free(rx->xattr_blobs, key->value_cookie);
if (error)
return error;
ab->name[key->namelen] = 0;
if (key->flags & XFS_ATTR_PARENT) {
trace_xrep_xattr_insert_pptr(rx->sc->tempip, key->flags,
ab->name, key->namelen, ab->value,
key->valuelen);
args.op_flags |= XFS_DA_OP_LOGGED;
} else {
trace_xrep_xattr_insert_rec(rx->sc->tempip, key->flags,
ab->name, key->namelen, key->valuelen);
}
xfs_attr_sethash(&args);
error = xfs_attr_set(&args, XFS_ATTRUPDATE_CREATE, false);
if (error == -EEXIST)
error = 0;
return error;
}
STATIC int
xrep_xattr_flush_stashed(
struct xrep_xattr *rx)
{
xfarray_idx_t array_cur;
int error;
error = xrep_trans_commit(rx->sc);
if (error)
return error;
xchk_iunlock(rx->sc, XFS_ILOCK_EXCL);
error = xrep_tempfile_iolock_polled(rx->sc);
if (error)
return error;
foreach_xfarray_idx(rx->xattr_records, array_cur) {
struct xrep_xattr_key key;
error = xfarray_load(rx->xattr_records, array_cur, &key);
if (error)
return error;
error = xrep_xattr_insert_rec(rx, &key);
if (error)
return error;
}
xfarray_truncate(rx->xattr_records);
xfblob_truncate(rx->xattr_blobs);
xrep_tempfile_iounlock(rx->sc);
error = xchk_trans_alloc(rx->sc, 0);
if (error)
return error;
xchk_ilock(rx->sc, XFS_ILOCK_EXCL);
return 0;
}
static inline bool
xrep_xattr_want_flush_stashed(
struct xrep_xattr *rx)
{
unsigned long long bytes;
if (!rx->can_flush)
return false;
bytes = xfarray_bytes(rx->xattr_records) +
xfblob_bytes(rx->xattr_blobs);
return bytes > XREP_XATTR_MAX_STASH_BYTES;
}
static inline bool
xrep_xattr_saw_pptr_conflict(
struct xrep_xattr *rx)
{
bool ret;
ASSERT(rx->can_flush);
if (!xfs_has_parent(rx->sc->mp))
return false;
xfs_assert_ilocked(rx->sc->ip, XFS_ILOCK_EXCL);
mutex_lock(&rx->lock);
ret = xfarray_bytes(rx->pptr_recs) > 0;
mutex_unlock(&rx->lock);
return ret;
}
STATIC int
xrep_xattr_full_reset(
struct xrep_xattr *rx)
{
struct xfs_scrub *sc = rx->sc;
struct xfs_attr_sf_hdr *hdr;
struct xfs_ifork *ifp = &sc->tempip->i_af;
int error;
trace_xrep_xattr_full_reset(sc->ip, sc->tempip);
if (sc->tempip->i_df.if_format == XFS_DINODE_FMT_BTREE) {
ASSERT(0);
return -EIO;
}
xchk_iunlock(rx->sc, XFS_ILOCK_EXCL);
xrep_tempfile_ilock_both(sc);
xfs_trans_ijoin(sc->tp, sc->ip, 0);
xfs_trans_ijoin(sc->tp, sc->tempip, 0);
if (xfs_ifork_has_extents(&sc->tempip->i_af)) {
error = xrep_reap_ifork(sc, sc->tempip, XFS_ATTR_FORK);
if (error)
return error;
ASSERT(ifp->if_bytes == 0);
ifp->if_format = XFS_DINODE_FMT_LOCAL;
xfs_idata_realloc(sc->tempip, sizeof(*hdr), XFS_ATTR_FORK);
}
hdr = ifp->if_data;
memset(hdr, 0, sizeof(*hdr));
hdr->totsize = cpu_to_be16(sizeof(*hdr));
xfs_trans_log_inode(sc->tp, sc->tempip, XFS_ILOG_CORE | XFS_ILOG_ADATA);
error = xrep_roll_trans(sc);
if (error)
return error;
xrep_tempfile_iunlock(sc);
mutex_lock(&rx->lock);
xfarray_truncate(rx->pptr_recs);
xfblob_truncate(rx->pptr_names);
mutex_unlock(&rx->lock);
rx->can_flush = false;
rx->attrs_found = 0;
ASSERT(xfarray_bytes(rx->xattr_records) == 0);
ASSERT(xfblob_bytes(rx->xattr_blobs) == 0);
return 0;
}
STATIC int
xrep_xattr_recover(
struct xrep_xattr *rx)
{
struct xfs_bmbt_irec got;
struct xfs_scrub *sc = rx->sc;
struct xfs_da_geometry *geo = sc->mp->m_attr_geo;
xfs_fileoff_t offset;
xfs_extlen_t len;
xfs_dablk_t dabno;
int nmap;
int error;
restart:
for (offset = 0;
offset < XFS_MAX_FILEOFF;
offset = got.br_startoff + got.br_blockcount) {
nmap = 1;
error = xfs_bmapi_read(sc->ip, offset, XFS_MAX_FILEOFF - offset,
&got, &nmap, XFS_BMAPI_ATTRFORK);
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 += len) {
xfs_fileoff_t curr_offset = dabno - got.br_startoff;
xfs_extlen_t maxlen;
if (xchk_should_terminate(rx->sc, &error))
return error;
maxlen = min_t(xfs_filblks_t, INT_MAX,
got.br_blockcount - curr_offset);
error = xrep_xattr_recover_block(rx, dabno,
curr_offset + got.br_startblock,
maxlen, &len);
if (error)
return error;
if (xrep_xattr_want_flush_stashed(rx)) {
error = xrep_xattr_flush_stashed(rx);
if (error)
return error;
if (xrep_xattr_saw_pptr_conflict(rx)) {
error = xrep_xattr_full_reset(rx);
if (error)
return error;
goto restart;
}
}
}
}
return 0;
}
STATIC int
xrep_xattr_fork_remove(
struct xfs_scrub *sc,
struct xfs_inode *ip)
{
struct xfs_attr_sf_hdr *hdr;
struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_ATTR_FORK);
if (ip->i_df.if_format == XFS_DINODE_FMT_BTREE) {
ifp->if_format = XFS_DINODE_FMT_LOCAL;
hdr = xfs_idata_realloc(ip, (int)sizeof(*hdr) - ifp->if_bytes,
XFS_ATTR_FORK);
hdr->count = 0;
hdr->totsize = cpu_to_be16(sizeof(*hdr));
xfs_trans_log_inode(sc->tp, ip,
XFS_ILOG_CORE | XFS_ILOG_ADATA);
return 0;
}
if (ifp->if_nextents != 0) {
struct xfs_iext_cursor icur;
struct xfs_bmbt_irec irec;
unsigned int i = 0;
xfs_emerg(sc->mp,
"inode 0x%llx attr fork still has %llu attr extents, format %d?!",
ip->i_ino, ifp->if_nextents, ifp->if_format);
for_each_xfs_iext(ifp, &icur, &irec) {
xfs_err(sc->mp,
"[%u]: startoff %llu startblock %llu blockcount %llu state %u",
i++, irec.br_startoff,
irec.br_startblock, irec.br_blockcount,
irec.br_state);
}
ASSERT(0);
return -EFSCORRUPTED;
}
xfs_attr_fork_remove(ip, sc->tp);
return 0;
}
int
xrep_xattr_reset_fork(
struct xfs_scrub *sc)
{
int error;
trace_xrep_xattr_reset_fork(sc->ip, sc->ip);
if (xfs_ifork_has_extents(&sc->ip->i_af)) {
error = xrep_reap_ifork(sc, sc->ip, XFS_ATTR_FORK);
if (error)
return error;
}
error = xrep_xattr_fork_remove(sc, sc->ip);
if (error)
return error;
return xfs_trans_roll_inode(&sc->tp, sc->ip);
}
int
xrep_xattr_reset_tempfile_fork(
struct xfs_scrub *sc)
{
int error;
trace_xrep_xattr_reset_fork(sc->ip, sc->tempip);
if (xfs_ifork_has_extents(&sc->tempip->i_af)) {
error = xrep_reap_ifork(sc, sc->tempip, XFS_ATTR_FORK);
if (error)
return error;
}
return xrep_xattr_fork_remove(sc, sc->tempip);
}
STATIC int
xrep_xattr_salvage_attributes(
struct xrep_xattr *rx)
{
struct xfs_inode *ip = rx->sc->ip;
int error;
if (rx->sc->ip->i_af.if_format == XFS_DINODE_FMT_LOCAL) {
error = xrep_xattr_recover_sf(rx);
if (error)
return error;
return xrep_xattr_flush_stashed(rx);
}
error = xfs_trans_roll(&rx->sc->tp);
if (error)
return error;
error = xfs_iread_extents(rx->sc->tp, ip, XFS_ATTR_FORK);
if (error)
return error;
error = xrep_xattr_recover(rx);
if (error)
return error;
return xrep_xattr_flush_stashed(rx);
}
STATIC int
xrep_xattr_replay_pptr_update(
struct xrep_xattr *rx,
const struct xfs_name *xname,
struct xrep_xattr_pptr *pptr)
{
struct xfs_scrub *sc = rx->sc;
int error;
switch (pptr->action) {
case XREP_XATTR_PPTR_ADD:
trace_xrep_xattr_replay_parentadd(sc->tempip, xname,
&pptr->pptr_rec);
error = xfs_parent_set(sc->tempip, sc->ip->i_ino, xname,
&pptr->pptr_rec, &rx->pptr_args);
ASSERT(error != -EEXIST);
return error;
case XREP_XATTR_PPTR_REMOVE:
trace_xrep_xattr_replay_parentremove(sc->tempip, xname,
&pptr->pptr_rec);
error = xfs_parent_unset(sc->tempip, sc->ip->i_ino, xname,
&pptr->pptr_rec, &rx->pptr_args);
ASSERT(error != -ENOATTR);
return error;
}
ASSERT(0);
return -EIO;
}
STATIC int
xrep_xattr_replay_pptr_updates(
struct xrep_xattr *rx)
{
xfarray_idx_t array_cur;
int error;
mutex_lock(&rx->lock);
foreach_xfarray_idx(rx->pptr_recs, array_cur) {
struct xrep_xattr_pptr pptr;
error = xfarray_load(rx->pptr_recs, array_cur, &pptr);
if (error)
goto out_unlock;
error = xfblob_loadname(rx->pptr_names, pptr.name_cookie,
&rx->xname, pptr.namelen);
if (error)
goto out_unlock;
mutex_unlock(&rx->lock);
error = xrep_xattr_replay_pptr_update(rx, &rx->xname, &pptr);
if (error)
return error;
mutex_lock(&rx->lock);
}
xfarray_truncate(rx->pptr_recs);
xfblob_truncate(rx->pptr_names);
mutex_unlock(&rx->lock);
return 0;
out_unlock:
mutex_unlock(&rx->lock);
return error;
}
STATIC int
xrep_xattr_stash_parentadd(
struct xrep_xattr *rx,
const struct xfs_name *name,
const struct xfs_inode *dp)
{
struct xrep_xattr_pptr pptr = {
.action = XREP_XATTR_PPTR_ADD,
.namelen = name->len,
};
int error;
trace_xrep_xattr_stash_parentadd(rx->sc->tempip, dp, name);
xfs_inode_to_parent_rec(&pptr.pptr_rec, dp);
error = xfblob_storename(rx->pptr_names, &pptr.name_cookie, name);
if (error)
return error;
return xfarray_append(rx->pptr_recs, &pptr);
}
STATIC int
xrep_xattr_stash_parentremove(
struct xrep_xattr *rx,
const struct xfs_name *name,
const struct xfs_inode *dp)
{
struct xrep_xattr_pptr pptr = {
.action = XREP_XATTR_PPTR_REMOVE,
.namelen = name->len,
};
int error;
trace_xrep_xattr_stash_parentremove(rx->sc->tempip, dp, name);
xfs_inode_to_parent_rec(&pptr.pptr_rec, dp);
error = xfblob_storename(rx->pptr_names, &pptr.name_cookie, name);
if (error)
return error;
return xfarray_append(rx->pptr_recs, &pptr);
}
STATIC int
xrep_xattr_live_dirent_update(
struct notifier_block *nb,
unsigned long action,
void *data)
{
struct xfs_dir_update_params *p = data;
struct xrep_xattr *rx;
struct xfs_scrub *sc;
int error;
rx = container_of(nb, struct xrep_xattr, dhook.dirent_hook.nb);
sc = rx->sc;
if (p->ip->i_ino != sc->ip->i_ino)
return NOTIFY_DONE;
mutex_lock(&rx->lock);
if (p->delta > 0)
error = xrep_xattr_stash_parentadd(rx, p->name, p->dp);
else
error = xrep_xattr_stash_parentremove(rx, p->name, p->dp);
if (error)
rx->live_update_aborted = true;
mutex_unlock(&rx->lock);
return NOTIFY_DONE;
}
STATIC int
xrep_xattr_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_attr_geo,
.whichfork = XFS_ATTR_FORK,
.trans = sc->tp,
.total = 1,
.owner = sc->ip->i_ino,
};
error = xfs_attr_shortform_to_leaf(&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_ATTR_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_ADATA);
}
return 0;
}
int
xrep_xattr_swap(
struct xfs_scrub *sc,
struct xrep_tempexch *tx)
{
bool ip_local, temp_local;
int error = 0;
ip_local = sc->ip->i_af.if_format == XFS_DINODE_FMT_LOCAL;
temp_local = sc->tempip->i_af.if_format == XFS_DINODE_FMT_LOCAL;
if (ip_local && temp_local) {
int forkoff;
int newsize;
newsize = xfs_attr_sf_totsize(sc->tempip);
forkoff = xfs_attr_shortform_bytesfit(sc->ip, newsize);
if (forkoff > 0) {
sc->ip->i_forkoff = forkoff;
xrep_tempfile_copyout_local(sc, XFS_ATTR_FORK);
return 0;
}
}
error = xrep_xattr_swap_prep(sc, temp_local, ip_local);
if (error)
return error;
return xrep_tempexch_contents(sc, tx);
}
STATIC int
xrep_xattr_finalize_tempfile(
struct xrep_xattr *rx)
{
struct xfs_scrub *sc = rx->sc;
int error;
if (!xfs_has_parent(sc->mp))
return xrep_tempexch_trans_alloc(sc, XFS_ATTR_FORK, &rx->tx);
do {
error = xrep_xattr_replay_pptr_updates(rx);
if (error)
return error;
error = xrep_tempexch_trans_alloc(sc, XFS_ATTR_FORK, &rx->tx);
if (error)
return error;
if (xfarray_length(rx->pptr_recs) == 0)
break;
xchk_trans_cancel(sc);
xrep_tempfile_iunlock_both(sc);
} while (!xchk_should_terminate(sc, &error));
return error;
}
STATIC int
xrep_xattr_rebuild_tree(
struct xrep_xattr *rx)
{
struct xfs_scrub *sc = rx->sc;
int error;
if (rx->attrs_found == 0) {
xfs_trans_ijoin(sc->tp, sc->ip, 0);
error = xrep_xattr_reset_fork(sc);
if (error)
return error;
goto forget_acls;
}
trace_xrep_xattr_rebuild_tree(sc->ip, sc->tempip);
error = xrep_trans_commit(sc);
if (error)
return error;
xchk_iunlock(sc, XFS_ILOCK_EXCL);
error = xrep_tempfile_iolock_polled(rx->sc);
if (error)
return error;
error = xrep_xattr_finalize_tempfile(rx);
if (error)
return error;
error = xrep_xattr_swap(sc, &rx->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);
forget_acls:
xfs_forget_acl(VFS_I(sc->ip), SGI_ACL_FILE);
xfs_forget_acl(VFS_I(sc->ip), SGI_ACL_DEFAULT);
return 0;
}
STATIC void
xrep_xattr_teardown(
struct xrep_xattr *rx)
{
if (xfs_has_parent(rx->sc->mp))
xfs_dir_hook_del(rx->sc->mp, &rx->dhook);
if (rx->pptr_names)
xfblob_destroy(rx->pptr_names);
if (rx->pptr_recs)
xfarray_destroy(rx->pptr_recs);
if (rx->xattr_blobs)
xfblob_destroy(rx->xattr_blobs);
if (rx->xattr_records)
xfarray_destroy(rx->xattr_records);
mutex_destroy(&rx->lock);
kfree(rx);
}
STATIC int
xrep_xattr_setup_scan(
struct xfs_scrub *sc,
struct xrep_xattr **rxp)
{
struct xrep_xattr *rx;
int max_len;
int error;
rx = kzalloc_obj(struct xrep_xattr, XCHK_GFP_FLAGS);
if (!rx)
return -ENOMEM;
rx->sc = sc;
rx->can_flush = true;
rx->xname.name = rx->namebuf;
mutex_init(&rx->lock);
max_len = xfs_attr_leaf_entsize_local_max(sc->mp->m_attr_geo->blksize);
error = xchk_setup_xattr_buf(rx->sc, max_len);
if (error == -ENOMEM)
error = -EDEADLOCK;
if (error)
goto out_rx;
error = xfarray_create("xattr keys", 0, sizeof(struct xrep_xattr_key),
&rx->xattr_records);
if (error)
goto out_rx;
error = xfblob_create("xattr names", &rx->xattr_blobs);
if (error)
goto out_keys;
if (xfs_has_parent(sc->mp)) {
ASSERT(sc->flags & XCHK_FSGATES_DIRENTS);
error = xfarray_create("xattr parent pointer entries", 0,
sizeof(struct xrep_xattr_pptr),
&rx->pptr_recs);
if (error)
goto out_values;
error = xfblob_create("xattr parent pointer names",
&rx->pptr_names);
if (error)
goto out_pprecs;
xfs_dir_hook_setup(&rx->dhook, xrep_xattr_live_dirent_update);
error = xfs_dir_hook_add(sc->mp, &rx->dhook);
if (error)
goto out_ppnames;
}
*rxp = rx;
return 0;
out_ppnames:
xfblob_destroy(rx->pptr_names);
out_pprecs:
xfarray_destroy(rx->pptr_recs);
out_values:
xfblob_destroy(rx->xattr_blobs);
out_keys:
xfarray_destroy(rx->xattr_records);
out_rx:
mutex_destroy(&rx->lock);
kfree(rx);
return error;
}
int
xrep_xattr(
struct xfs_scrub *sc)
{
struct xrep_xattr *rx = NULL;
int error;
if (!xfs_inode_hasattr(sc->ip))
return -ENOENT;
if (!xfs_has_rmapbt(sc->mp))
return -EOPNOTSUPP;
if (!xfs_has_exchange_range(sc->mp))
return -EOPNOTSUPP;
error = xrep_xattr_setup_scan(sc, &rx);
if (error)
return error;
ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL);
error = xrep_xattr_salvage_attributes(rx);
if (error)
goto out_scan;
if (rx->live_update_aborted) {
error = -EIO;
goto out_scan;
}
if (xchk_should_terminate(sc, &error))
goto out_scan;
error = xrep_xattr_rebuild_tree(rx);
if (error)
goto out_scan;
out_scan:
xrep_xattr_teardown(rx);
return error;
}