blob: 67900ea4208b4b4daed2fa6e4de4305e86d9dc7c [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*/
#include "xfs.h"
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/statvfs.h>
#include "list.h"
#include "libfrog/paths.h"
#include "xfs_scrub.h"
#include "common.h"
#include "scrub.h"
#include "progress.h"
#include "repair.h"
/*
* Prioritize action items in order of how long we can wait.
* 0 = do it now, 10000 = do it later.
*
* To minimize the amount of repair work, we want to prioritize metadata
* objects by perceived corruptness. If CORRUPT is set, the fields are
* just plain bad; try fixing that first. Otherwise if XCORRUPT is set,
* the fields could be bad, but the xref data could also be bad; we'll
* try fixing that next. Finally, if XFAIL is set, some other metadata
* structure failed validation during xref, so we'll recheck this
* metadata last since it was probably fine.
*
* For metadata that lie in the critical path of checking other metadata
* (superblock, AG{F,I,FL}, inobt) we scrub and fix those things before
* we even get to handling their dependencies, so things should progress
* in order.
*/
/* Sort action items in severity order. */
static int
PRIO(
struct action_item *aitem,
int order)
{
if (aitem->flags & XFS_SCRUB_OFLAG_CORRUPT)
return order;
else if (aitem->flags & XFS_SCRUB_OFLAG_XCORRUPT)
return 100 + order;
else if (aitem->flags & XFS_SCRUB_OFLAG_XFAIL)
return 200 + order;
else if (aitem->flags & XFS_SCRUB_OFLAG_PREEN)
return 300 + order;
abort();
}
/* Sort the repair items in dependency order. */
static int
xfs_action_item_priority(
struct action_item *aitem)
{
switch (aitem->type) {
case XFS_SCRUB_TYPE_SB:
case XFS_SCRUB_TYPE_AGF:
case XFS_SCRUB_TYPE_AGFL:
case XFS_SCRUB_TYPE_AGI:
case XFS_SCRUB_TYPE_BNOBT:
case XFS_SCRUB_TYPE_CNTBT:
case XFS_SCRUB_TYPE_INOBT:
case XFS_SCRUB_TYPE_FINOBT:
case XFS_SCRUB_TYPE_REFCNTBT:
case XFS_SCRUB_TYPE_RMAPBT:
case XFS_SCRUB_TYPE_INODE:
case XFS_SCRUB_TYPE_BMBTD:
case XFS_SCRUB_TYPE_BMBTA:
case XFS_SCRUB_TYPE_BMBTC:
return PRIO(aitem, aitem->type - 1);
case XFS_SCRUB_TYPE_DIR:
case XFS_SCRUB_TYPE_XATTR:
case XFS_SCRUB_TYPE_SYMLINK:
case XFS_SCRUB_TYPE_PARENT:
return PRIO(aitem, XFS_SCRUB_TYPE_DIR);
case XFS_SCRUB_TYPE_RTBITMAP:
case XFS_SCRUB_TYPE_RTSUM:
return PRIO(aitem, XFS_SCRUB_TYPE_RTBITMAP);
case XFS_SCRUB_TYPE_UQUOTA:
case XFS_SCRUB_TYPE_GQUOTA:
case XFS_SCRUB_TYPE_PQUOTA:
return PRIO(aitem, XFS_SCRUB_TYPE_UQUOTA);
case XFS_SCRUB_TYPE_FSCOUNTERS:
/* This should always go after AG headers no matter what. */
return PRIO(aitem, INT_MAX);
}
abort();
}
/* Make sure that btrees get repaired before headers. */
static int
xfs_action_item_compare(
void *priv,
struct list_head *a,
struct list_head *b)
{
struct action_item *ra;
struct action_item *rb;
ra = container_of(a, struct action_item, list);
rb = container_of(b, struct action_item, list);
return xfs_action_item_priority(ra) - xfs_action_item_priority(rb);
}
/*
* Figure out which AG metadata must be fixed before we can move on
* to the inode scan.
*/
void
action_list_find_mustfix(
struct action_list *alist,
struct action_list *immediate_alist,
unsigned long long *broken_primaries,
unsigned long long *broken_secondaries)
{
struct action_item *n;
struct action_item *aitem;
list_for_each_entry_safe(aitem, n, &alist->list, list) {
if (!(aitem->flags & XFS_SCRUB_OFLAG_CORRUPT))
continue;
switch (aitem->type) {
case XFS_SCRUB_TYPE_RMAPBT:
(*broken_secondaries)++;
break;
case XFS_SCRUB_TYPE_FINOBT:
case XFS_SCRUB_TYPE_INOBT:
alist->nr--;
list_move_tail(&aitem->list, &immediate_alist->list);
immediate_alist->nr++;
fallthrough;
case XFS_SCRUB_TYPE_BNOBT:
case XFS_SCRUB_TYPE_CNTBT:
case XFS_SCRUB_TYPE_REFCNTBT:
(*broken_primaries)++;
break;
default:
abort();
break;
}
}
}
/*
* Allocate a certain number of repair lists for the scrub context. Returns
* zero or a positive error number.
*/
int
action_lists_alloc(
size_t nr,
struct action_list **listsp)
{
struct action_list *lists;
xfs_agnumber_t agno;
lists = calloc(nr, sizeof(struct action_list));
if (!lists)
return errno;
for (agno = 0; agno < nr; agno++)
action_list_init(&lists[agno]);
*listsp = lists;
return 0;
}
/* Free the repair lists. */
void
action_lists_free(
struct action_list **listsp)
{
free(*listsp);
*listsp = NULL;
}
/* Initialize repair list */
void
action_list_init(
struct action_list *alist)
{
INIT_LIST_HEAD(&alist->list);
alist->nr = 0;
alist->sorted = false;
}
/* Number of repairs in this list. */
unsigned long long
action_list_length(
struct action_list *alist)
{
return alist->nr;
};
/* Add to the list of repairs. */
void
action_list_add(
struct action_list *alist,
struct action_item *aitem)
{
list_add_tail(&aitem->list, &alist->list);
alist->nr++;
alist->sorted = false;
}
/* Splice two repair lists. */
void
action_list_splice(
struct action_list *dest,
struct action_list *src)
{
if (src->nr == 0)
return;
list_splice_tail_init(&src->list, &dest->list);
dest->nr += src->nr;
src->nr = 0;
dest->sorted = false;
}
/* Repair everything on this list. */
int
action_list_process(
struct scrub_ctx *ctx,
int fd,
struct action_list *alist,
unsigned int repair_flags)
{
struct xfs_fd xfd;
struct xfs_fd *xfdp = &ctx->mnt;
struct action_item *aitem;
struct action_item *n;
enum check_outcome fix;
/*
* If the caller passed us a file descriptor for a scrub, use it
* instead of scrub-by-handle because this enables the kernel to skip
* costly inode btree lookups.
*/
if (fd >= 0) {
memcpy(&xfd, xfdp, sizeof(xfd));
xfd.fd = fd;
xfdp = &xfd;
}
if (!alist->sorted) {
list_sort(NULL, &alist->list, xfs_action_item_compare);
alist->sorted = true;
}
list_for_each_entry_safe(aitem, n, &alist->list, list) {
fix = xfs_repair_metadata(ctx, xfdp, aitem, repair_flags);
switch (fix) {
case CHECK_DONE:
if (!(repair_flags & ALP_NOPROGRESS))
progress_add(1);
alist->nr--;
list_del(&aitem->list);
free(aitem);
continue;
case CHECK_ABORT:
return ECANCELED;
case CHECK_RETRY:
continue;
case CHECK_REPAIR:
abort();
}
}
if (scrub_excessive_errors(ctx))
return ECANCELED;
return 0;
}
/* Defer all the repairs until phase 4. */
void
action_list_defer(
struct scrub_ctx *ctx,
xfs_agnumber_t agno,
struct action_list *alist)
{
ASSERT(agno < ctx->mnt.fsgeom.agcount);
action_list_splice(&ctx->action_lists[agno], alist);
}
/* Run actions now and defer unfinished items for later. */
int
action_list_process_or_defer(
struct scrub_ctx *ctx,
xfs_agnumber_t agno,
struct action_list *alist)
{
int ret;
ret = action_list_process(ctx, -1, alist,
ALP_REPAIR_ONLY | ALP_NOPROGRESS);
if (ret)
return ret;
action_list_defer(ctx, agno, alist);
return 0;
}