mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 21:21:41 +00:00
xfs: repair extended attributes
If the extended attributes look bad, try to sift through the rubble to find whatever keys/values we can, stage a new attribute structure in a temporary file and use the atomic extent swapping mechanism to commit the results in bulk. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
parent
629fdaf5f5
commit
e47dcf113a
@ -194,6 +194,7 @@ ifeq ($(CONFIG_XFS_ONLINE_REPAIR),y)
|
||||
xfs-y += $(addprefix scrub/, \
|
||||
agheader_repair.o \
|
||||
alloc_repair.o \
|
||||
attr_repair.o \
|
||||
bmap_repair.o \
|
||||
cow_repair.o \
|
||||
fscounters_repair.o \
|
||||
|
@ -1055,7 +1055,7 @@ out_trans_cancel:
|
||||
* External routines when attribute list is inside the inode
|
||||
*========================================================================*/
|
||||
|
||||
static inline int xfs_attr_sf_totsize(struct xfs_inode *dp)
|
||||
int xfs_attr_sf_totsize(struct xfs_inode *dp)
|
||||
{
|
||||
struct xfs_attr_sf_hdr *sf = dp->i_af.if_data;
|
||||
|
||||
|
@ -618,4 +618,6 @@ extern struct kmem_cache *xfs_attr_intent_cache;
|
||||
int __init xfs_attr_intent_init_cache(void);
|
||||
void xfs_attr_intent_destroy_cache(void);
|
||||
|
||||
int xfs_attr_sf_totsize(struct xfs_inode *dp);
|
||||
|
||||
#endif /* __XFS_ATTR_H__ */
|
||||
|
@ -721,6 +721,11 @@ struct xfs_attr3_leafblock {
|
||||
#define XFS_ATTR_INCOMPLETE (1u << XFS_ATTR_INCOMPLETE_BIT)
|
||||
#define XFS_ATTR_NSP_ONDISK_MASK (XFS_ATTR_ROOT | XFS_ATTR_SECURE)
|
||||
|
||||
#define XFS_ATTR_NAMESPACE_STR \
|
||||
{ XFS_ATTR_LOCAL, "local" }, \
|
||||
{ XFS_ATTR_ROOT, "root" }, \
|
||||
{ XFS_ATTR_SECURE, "secure" }
|
||||
|
||||
/*
|
||||
* Alignment for namelist and valuelist entries (since they are mixed
|
||||
* there can be only one alignment value)
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "xfs_trans_resv.h"
|
||||
#include "xfs_mount.h"
|
||||
#include "xfs_log_format.h"
|
||||
#include "xfs_trans.h"
|
||||
#include "xfs_inode.h"
|
||||
#include "xfs_da_format.h"
|
||||
#include "xfs_da_btree.h"
|
||||
@ -20,6 +21,7 @@
|
||||
#include "scrub/common.h"
|
||||
#include "scrub/dabtree.h"
|
||||
#include "scrub/attr.h"
|
||||
#include "scrub/repair.h"
|
||||
|
||||
/* Free the buffers linked from the xattr buffer. */
|
||||
static void
|
||||
@ -35,6 +37,8 @@ xchk_xattr_buf_cleanup(
|
||||
kvfree(ab->value);
|
||||
ab->value = NULL;
|
||||
ab->value_sz = 0;
|
||||
kvfree(ab->name);
|
||||
ab->name = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -65,7 +69,7 @@ xchk_xattr_want_freemap(
|
||||
* reallocating the buffer if necessary. Buffer contents are not preserved
|
||||
* across a reallocation.
|
||||
*/
|
||||
static int
|
||||
int
|
||||
xchk_setup_xattr_buf(
|
||||
struct xfs_scrub *sc,
|
||||
size_t value_size)
|
||||
@ -95,6 +99,12 @@ xchk_setup_xattr_buf(
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (xchk_could_repair(sc)) {
|
||||
ab->name = kvmalloc(XATTR_NAME_MAX + 1, XCHK_GFP_FLAGS);
|
||||
if (!ab->name)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
resize_value:
|
||||
if (ab->value_sz >= value_size)
|
||||
return 0;
|
||||
@ -121,6 +131,12 @@ xchk_setup_xattr(
|
||||
{
|
||||
int error;
|
||||
|
||||
if (xchk_could_repair(sc)) {
|
||||
error = xrep_setup_xattr(sc);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* We failed to get memory while checking attrs, so this time try to
|
||||
* get all the memory we're ever going to need. Allocate the buffer
|
||||
@ -247,7 +263,7 @@ fail_xref:
|
||||
* Within a char, the lowest bit of the char represents the byte with
|
||||
* the smallest address
|
||||
*/
|
||||
STATIC bool
|
||||
bool
|
||||
xchk_xattr_set_map(
|
||||
struct xfs_scrub *sc,
|
||||
unsigned long *map,
|
||||
|
@ -16,9 +16,16 @@ struct xchk_xattr_buf {
|
||||
/* Bitmap of free space in xattr leaf blocks. */
|
||||
unsigned long *freemap;
|
||||
|
||||
/* Memory buffer used to hold salvaged xattr names. */
|
||||
unsigned char *name;
|
||||
|
||||
/* Memory buffer used to extract xattr values. */
|
||||
void *value;
|
||||
size_t value_sz;
|
||||
};
|
||||
|
||||
bool xchk_xattr_set_map(struct xfs_scrub *sc, unsigned long *map,
|
||||
unsigned int start, unsigned int len);
|
||||
int xchk_setup_xattr_buf(struct xfs_scrub *sc, size_t value_size);
|
||||
|
||||
#endif /* __XFS_SCRUB_ATTR_H__ */
|
||||
|
1207
fs/xfs/scrub/attr_repair.c
Normal file
1207
fs/xfs/scrub/attr_repair.c
Normal file
File diff suppressed because it is too large
Load Diff
11
fs/xfs/scrub/attr_repair.h
Normal file
11
fs/xfs/scrub/attr_repair.h
Normal file
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (c) 2018-2024 Oracle. All Rights Reserved.
|
||||
* Author: Darrick J. Wong <djwong@kernel.org>
|
||||
*/
|
||||
#ifndef __XFS_SCRUB_ATTR_REPAIR_H__
|
||||
#define __XFS_SCRUB_ATTR_REPAIR_H__
|
||||
|
||||
int xrep_xattr_reset_fork(struct xfs_scrub *sc);
|
||||
|
||||
#endif /* __XFS_SCRUB_ATTR_REPAIR_H__ */
|
@ -32,6 +32,9 @@
|
||||
#include "xfs_reflink.h"
|
||||
#include "xfs_health.h"
|
||||
#include "xfs_buf_mem.h"
|
||||
#include "xfs_da_format.h"
|
||||
#include "xfs_da_btree.h"
|
||||
#include "xfs_attr.h"
|
||||
#include "scrub/scrub.h"
|
||||
#include "scrub/common.h"
|
||||
#include "scrub/trace.h"
|
||||
@ -39,6 +42,7 @@
|
||||
#include "scrub/bitmap.h"
|
||||
#include "scrub/stats.h"
|
||||
#include "scrub/xfile.h"
|
||||
#include "scrub/attr_repair.h"
|
||||
|
||||
/*
|
||||
* Attempt to repair some metadata, if the metadata is corrupt and userspace
|
||||
@ -1136,6 +1140,17 @@ xrep_metadata_inode_forks(
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Clear the attr forks since metadata shouldn't have that. */
|
||||
if (xfs_inode_hasattr(sc->ip)) {
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
xfs_trans_ijoin(sc->tp, sc->ip, 0);
|
||||
}
|
||||
error = xrep_xattr_reset_fork(sc);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we modified the inode, roll the transaction but don't rejoin the
|
||||
* inode to the new transaction because xrep_bmap_data can do that.
|
||||
@ -1201,3 +1216,34 @@ xrep_trans_cancel_hook_dummy(
|
||||
current->journal_info = *cookiep;
|
||||
*cookiep = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* See if this buffer can pass the given ->verify_struct() function.
|
||||
*
|
||||
* If the buffer already has ops attached and they're not the ones that were
|
||||
* passed in, we reject the buffer. Otherwise, we perform the structure test
|
||||
* (note that we do not check CRCs) and return the outcome of the test. The
|
||||
* buffer ops and error state are left unchanged.
|
||||
*/
|
||||
bool
|
||||
xrep_buf_verify_struct(
|
||||
struct xfs_buf *bp,
|
||||
const struct xfs_buf_ops *ops)
|
||||
{
|
||||
const struct xfs_buf_ops *old_ops = bp->b_ops;
|
||||
xfs_failaddr_t fa;
|
||||
int old_error;
|
||||
|
||||
if (old_ops) {
|
||||
if (old_ops != ops)
|
||||
return false;
|
||||
}
|
||||
|
||||
old_error = bp->b_error;
|
||||
bp->b_ops = ops;
|
||||
fa = bp->b_ops->verify_struct(bp);
|
||||
bp->b_ops = old_ops;
|
||||
bp->b_error = old_error;
|
||||
|
||||
return fa == NULL;
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ int xrep_bmap(struct xfs_scrub *sc, int whichfork, bool allow_unwritten);
|
||||
int xrep_metadata_inode_forks(struct xfs_scrub *sc);
|
||||
int xrep_setup_ag_rmapbt(struct xfs_scrub *sc);
|
||||
int xrep_setup_ag_refcountbt(struct xfs_scrub *sc);
|
||||
int xrep_setup_xattr(struct xfs_scrub *sc);
|
||||
|
||||
/* Repair setup functions */
|
||||
int xrep_setup_ag_allocbt(struct xfs_scrub *sc);
|
||||
@ -123,6 +124,7 @@ int xrep_bmap_attr(struct xfs_scrub *sc);
|
||||
int xrep_bmap_cow(struct xfs_scrub *sc);
|
||||
int xrep_nlinks(struct xfs_scrub *sc);
|
||||
int xrep_fscounters(struct xfs_scrub *sc);
|
||||
int xrep_xattr(struct xfs_scrub *sc);
|
||||
|
||||
#ifdef CONFIG_XFS_RT
|
||||
int xrep_rtbitmap(struct xfs_scrub *sc);
|
||||
@ -147,6 +149,8 @@ int xrep_trans_alloc_hook_dummy(struct xfs_mount *mp, void **cookiep,
|
||||
struct xfs_trans **tpp);
|
||||
void xrep_trans_cancel_hook_dummy(void **cookiep, struct xfs_trans *tp);
|
||||
|
||||
bool xrep_buf_verify_struct(struct xfs_buf *bp, const struct xfs_buf_ops *ops);
|
||||
|
||||
#else
|
||||
|
||||
#define xrep_ino_dqattach(sc) (0)
|
||||
@ -190,6 +194,7 @@ xrep_setup_nothing(
|
||||
#define xrep_setup_ag_allocbt xrep_setup_nothing
|
||||
#define xrep_setup_ag_rmapbt xrep_setup_nothing
|
||||
#define xrep_setup_ag_refcountbt xrep_setup_nothing
|
||||
#define xrep_setup_xattr xrep_setup_nothing
|
||||
|
||||
#define xrep_setup_inode(sc, imap) ((void)0)
|
||||
|
||||
@ -215,6 +220,7 @@ xrep_setup_nothing(
|
||||
#define xrep_nlinks xrep_notsupported
|
||||
#define xrep_fscounters xrep_notsupported
|
||||
#define xrep_rtsummary xrep_notsupported
|
||||
#define xrep_xattr xrep_notsupported
|
||||
|
||||
#endif /* CONFIG_XFS_ONLINE_REPAIR */
|
||||
|
||||
|
@ -331,7 +331,7 @@ static const struct xchk_meta_ops meta_scrub_ops[] = {
|
||||
.type = ST_INODE,
|
||||
.setup = xchk_setup_xattr,
|
||||
.scrub = xchk_xattr,
|
||||
.repair = xrep_notsupported,
|
||||
.repair = xrep_xattr,
|
||||
},
|
||||
[XFS_SCRUB_TYPE_SYMLINK] = { /* symbolic link */
|
||||
.type = ST_INODE,
|
||||
|
@ -2416,6 +2416,89 @@ TRACE_EVENT(xreap_bmapi_binval_scan,
|
||||
__entry->scan_blocks)
|
||||
);
|
||||
|
||||
TRACE_EVENT(xrep_xattr_recover_leafblock,
|
||||
TP_PROTO(struct xfs_inode *ip, xfs_dablk_t dabno, uint16_t magic),
|
||||
TP_ARGS(ip, dabno, magic),
|
||||
TP_STRUCT__entry(
|
||||
__field(dev_t, dev)
|
||||
__field(xfs_ino_t, ino)
|
||||
__field(xfs_dablk_t, dabno)
|
||||
__field(uint16_t, magic)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->dev = ip->i_mount->m_super->s_dev;
|
||||
__entry->ino = ip->i_ino;
|
||||
__entry->dabno = dabno;
|
||||
__entry->magic = magic;
|
||||
),
|
||||
TP_printk("dev %d:%d ino 0x%llx dablk 0x%x magic 0x%x",
|
||||
MAJOR(__entry->dev), MINOR(__entry->dev),
|
||||
__entry->ino,
|
||||
__entry->dabno,
|
||||
__entry->magic)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(xrep_xattr_salvage_class,
|
||||
TP_PROTO(struct xfs_inode *ip, unsigned int flags, char *name,
|
||||
unsigned int namelen, unsigned int valuelen),
|
||||
TP_ARGS(ip, flags, name, namelen, valuelen),
|
||||
TP_STRUCT__entry(
|
||||
__field(dev_t, dev)
|
||||
__field(xfs_ino_t, ino)
|
||||
__field(unsigned int, flags)
|
||||
__field(unsigned int, namelen)
|
||||
__dynamic_array(char, name, namelen)
|
||||
__field(unsigned int, valuelen)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->dev = ip->i_mount->m_super->s_dev;
|
||||
__entry->ino = ip->i_ino;
|
||||
__entry->flags = flags;
|
||||
__entry->namelen = namelen;
|
||||
memcpy(__get_str(name), name, namelen);
|
||||
__entry->valuelen = valuelen;
|
||||
),
|
||||
TP_printk("dev %d:%d ino 0x%llx flags %s name '%.*s' valuelen 0x%x",
|
||||
MAJOR(__entry->dev), MINOR(__entry->dev),
|
||||
__entry->ino,
|
||||
__print_flags(__entry->flags, "|", XFS_ATTR_NAMESPACE_STR),
|
||||
__entry->namelen,
|
||||
__get_str(name),
|
||||
__entry->valuelen)
|
||||
);
|
||||
#define DEFINE_XREP_XATTR_SALVAGE_EVENT(name) \
|
||||
DEFINE_EVENT(xrep_xattr_salvage_class, name, \
|
||||
TP_PROTO(struct xfs_inode *ip, unsigned int flags, char *name, \
|
||||
unsigned int namelen, unsigned int valuelen), \
|
||||
TP_ARGS(ip, flags, name, namelen, valuelen))
|
||||
DEFINE_XREP_XATTR_SALVAGE_EVENT(xrep_xattr_salvage_rec);
|
||||
DEFINE_XREP_XATTR_SALVAGE_EVENT(xrep_xattr_insert_rec);
|
||||
|
||||
TRACE_EVENT(xrep_xattr_class,
|
||||
TP_PROTO(struct xfs_inode *ip, struct xfs_inode *arg_ip),
|
||||
TP_ARGS(ip, arg_ip),
|
||||
TP_STRUCT__entry(
|
||||
__field(dev_t, dev)
|
||||
__field(xfs_ino_t, ino)
|
||||
__field(xfs_ino_t, src_ino)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->dev = ip->i_mount->m_super->s_dev;
|
||||
__entry->ino = ip->i_ino;
|
||||
__entry->src_ino = arg_ip->i_ino;
|
||||
),
|
||||
TP_printk("dev %d:%d ino 0x%llx src 0x%llx",
|
||||
MAJOR(__entry->dev), MINOR(__entry->dev),
|
||||
__entry->ino,
|
||||
__entry->src_ino)
|
||||
)
|
||||
#define DEFINE_XREP_XATTR_EVENT(name) \
|
||||
DEFINE_EVENT(xrep_xattr_class, name, \
|
||||
TP_PROTO(struct xfs_inode *ip, struct xfs_inode *arg_ip), \
|
||||
TP_ARGS(ip, arg_ip))
|
||||
DEFINE_XREP_XATTR_EVENT(xrep_xattr_rebuild_tree);
|
||||
DEFINE_XREP_XATTR_EVENT(xrep_xattr_reset_fork);
|
||||
|
||||
#endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */
|
||||
|
||||
#endif /* _TRACE_XFS_SCRUB_TRACE_H */
|
||||
|
@ -1051,3 +1051,20 @@ out_free:
|
||||
kvfree(si);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* How many bytes is this array consuming? */
|
||||
unsigned long long
|
||||
xfarray_bytes(
|
||||
struct xfarray *array)
|
||||
{
|
||||
return xfile_bytes(array->xfile);
|
||||
}
|
||||
|
||||
/* Empty the entire array. */
|
||||
void
|
||||
xfarray_truncate(
|
||||
struct xfarray *array)
|
||||
{
|
||||
xfile_discard(array->xfile, 0, MAX_LFS_FILESIZE);
|
||||
array->nr = 0;
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ int xfarray_unset(struct xfarray *array, xfarray_idx_t idx);
|
||||
int xfarray_store(struct xfarray *array, xfarray_idx_t idx, const void *ptr);
|
||||
int xfarray_store_anywhere(struct xfarray *array, const void *ptr);
|
||||
bool xfarray_element_is_null(struct xfarray *array, const void *ptr);
|
||||
void xfarray_truncate(struct xfarray *array);
|
||||
unsigned long long xfarray_bytes(struct xfarray *array);
|
||||
|
||||
/*
|
||||
* Load an array element, but zero the buffer if there's no data because we
|
||||
|
@ -149,3 +149,20 @@ xfblob_free(
|
||||
xfile_discard(blob->xfile, cookie, sizeof(key) + key.xb_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* How many bytes is this blob storage object consuming? */
|
||||
unsigned long long
|
||||
xfblob_bytes(
|
||||
struct xfblob *blob)
|
||||
{
|
||||
return xfile_bytes(blob->xfile);
|
||||
}
|
||||
|
||||
/* Drop all the blobs. */
|
||||
void
|
||||
xfblob_truncate(
|
||||
struct xfblob *blob)
|
||||
{
|
||||
xfile_discard(blob->xfile, PAGE_SIZE, MAX_LFS_FILESIZE - PAGE_SIZE);
|
||||
blob->last_offset = PAGE_SIZE;
|
||||
}
|
||||
|
@ -20,5 +20,7 @@ int xfblob_load(struct xfblob *blob, xfblob_cookie cookie, void *ptr,
|
||||
int xfblob_store(struct xfblob *blob, xfblob_cookie *cookie, const void *ptr,
|
||||
uint32_t size);
|
||||
int xfblob_free(struct xfblob *blob, xfblob_cookie cookie);
|
||||
unsigned long long xfblob_bytes(struct xfblob *blob);
|
||||
void xfblob_truncate(struct xfblob *blob);
|
||||
|
||||
#endif /* __XFS_SCRUB_XFBLOB_H__ */
|
||||
|
@ -27,4 +27,9 @@ struct folio *xfile_get_folio(struct xfile *xf, loff_t offset, size_t len,
|
||||
unsigned int flags);
|
||||
void xfile_put_folio(struct xfile *xf, struct folio *folio);
|
||||
|
||||
static inline unsigned long long xfile_bytes(struct xfile *xf)
|
||||
{
|
||||
return file_inode(xf->file)->i_blocks << SECTOR_SHIFT;
|
||||
}
|
||||
|
||||
#endif /* __XFS_SCRUB_XFILE_H__ */
|
||||
|
@ -494,6 +494,9 @@ _xfs_buf_obj_cmp(
|
||||
* it stale has not yet committed. i.e. we are
|
||||
* reallocating a busy extent. Skip this buffer and
|
||||
* continue searching for an exact match.
|
||||
*
|
||||
* Note: If we're scanning for incore buffers to stale, don't
|
||||
* complain if we find non-stale buffers.
|
||||
*/
|
||||
if (!(map->bm_flags & XBM_LIVESCAN))
|
||||
ASSERT(bp->b_flags & XBF_STALE);
|
||||
|
@ -31,6 +31,8 @@
|
||||
* pos: file offset, in bytes
|
||||
* bytecount: number of bytes
|
||||
*
|
||||
* dablk: directory or xattr block offset, in filesystem blocks
|
||||
*
|
||||
* disize: ondisk file size, in bytes
|
||||
* isize: incore file size, in bytes
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user