blob: f48bb1a0d2b7569a8ee75f8077789dcbe9e92477 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2019 Oracle, Inc.
* All Rights Reserved.
*/
#include "xfs.h"
#include "fsgeom.h"
#include "scrub.h"
/* These must correspond to XFS_SCRUB_TYPE_ */
const struct xfrog_scrub_descr xfrog_scrubbers[XFS_SCRUB_TYPE_NR] = {
[XFS_SCRUB_TYPE_PROBE] = {
.name = "probe",
.descr = "metadata",
.group = XFROG_SCRUB_GROUP_NONE,
},
[XFS_SCRUB_TYPE_SB] = {
.name = "sb",
.descr = "superblock",
.group = XFROG_SCRUB_GROUP_AGHEADER,
},
[XFS_SCRUB_TYPE_AGF] = {
.name = "agf",
.descr = "free space header",
.group = XFROG_SCRUB_GROUP_AGHEADER,
},
[XFS_SCRUB_TYPE_AGFL] = {
.name = "agfl",
.descr = "free list",
.group = XFROG_SCRUB_GROUP_AGHEADER,
},
[XFS_SCRUB_TYPE_AGI] = {
.name = "agi",
.descr = "inode header",
.group = XFROG_SCRUB_GROUP_AGHEADER,
},
[XFS_SCRUB_TYPE_BNOBT] = {
.name = "bnobt",
.descr = "freesp by block btree",
.group = XFROG_SCRUB_GROUP_PERAG,
},
[XFS_SCRUB_TYPE_CNTBT] = {
.name = "cntbt",
.descr = "freesp by length btree",
.group = XFROG_SCRUB_GROUP_PERAG,
},
[XFS_SCRUB_TYPE_INOBT] = {
.name = "inobt",
.descr = "inode btree",
.group = XFROG_SCRUB_GROUP_PERAG,
},
[XFS_SCRUB_TYPE_FINOBT] = {
.name = "finobt",
.descr = "free inode btree",
.group = XFROG_SCRUB_GROUP_PERAG,
},
[XFS_SCRUB_TYPE_RMAPBT] = {
.name = "rmapbt",
.descr = "reverse mapping btree",
.group = XFROG_SCRUB_GROUP_PERAG,
},
[XFS_SCRUB_TYPE_REFCNTBT] = {
.name = "refcountbt",
.descr = "reference count btree",
.group = XFROG_SCRUB_GROUP_PERAG,
},
[XFS_SCRUB_TYPE_INODE] = {
.name = "inode",
.descr = "inode record",
.group = XFROG_SCRUB_GROUP_INODE,
},
[XFS_SCRUB_TYPE_BMBTD] = {
.name = "bmapbtd",
.descr = "data block map",
.group = XFROG_SCRUB_GROUP_INODE,
},
[XFS_SCRUB_TYPE_BMBTA] = {
.name = "bmapbta",
.descr = "attr block map",
.group = XFROG_SCRUB_GROUP_INODE,
},
[XFS_SCRUB_TYPE_BMBTC] = {
.name = "bmapbtc",
.descr = "CoW block map",
.group = XFROG_SCRUB_GROUP_INODE,
},
[XFS_SCRUB_TYPE_DIR] = {
.name = "directory",
.descr = "directory entries",
.group = XFROG_SCRUB_GROUP_INODE,
},
[XFS_SCRUB_TYPE_XATTR] = {
.name = "xattr",
.descr = "extended attributes",
.group = XFROG_SCRUB_GROUP_INODE,
},
[XFS_SCRUB_TYPE_SYMLINK] = {
.name = "symlink",
.descr = "symbolic link",
.group = XFROG_SCRUB_GROUP_INODE,
},
[XFS_SCRUB_TYPE_PARENT] = {
.name = "parent",
.descr = "parent pointer",
.group = XFROG_SCRUB_GROUP_INODE,
},
[XFS_SCRUB_TYPE_RTBITMAP] = {
.name = "rtbitmap",
.descr = "realtime bitmap",
.group = XFROG_SCRUB_GROUP_FS,
},
[XFS_SCRUB_TYPE_RTSUM] = {
.name = "rtsummary",
.descr = "realtime summary",
.group = XFROG_SCRUB_GROUP_FS,
},
[XFS_SCRUB_TYPE_UQUOTA] = {
.name = "usrquota",
.descr = "user quotas",
.group = XFROG_SCRUB_GROUP_FS,
},
[XFS_SCRUB_TYPE_GQUOTA] = {
.name = "grpquota",
.descr = "group quotas",
.group = XFROG_SCRUB_GROUP_FS,
},
[XFS_SCRUB_TYPE_PQUOTA] = {
.name = "prjquota",
.descr = "project quotas",
.group = XFROG_SCRUB_GROUP_FS,
},
[XFS_SCRUB_TYPE_FSCOUNTERS] = {
.name = "fscounters",
.descr = "filesystem summary counters",
.group = XFROG_SCRUB_GROUP_SUMMARY,
},
[XFS_SCRUB_TYPE_QUOTACHECK] = {
.name = "quotacheck",
.descr = "quota counters",
.group = XFROG_SCRUB_GROUP_ISCAN,
},
[XFS_SCRUB_TYPE_NLINKS] = {
.name = "nlinks",
.descr = "inode link counts",
.group = XFROG_SCRUB_GROUP_ISCAN,
},
[XFS_SCRUB_TYPE_HEALTHY] = {
.name = "healthy",
.descr = "retained health records",
.group = XFROG_SCRUB_GROUP_NONE,
},
[XFS_SCRUB_TYPE_DIRTREE] = {
.name = "dirtree",
.descr = "directory tree structure",
.group = XFROG_SCRUB_GROUP_INODE,
},
[XFS_SCRUB_TYPE_METAPATH] = {
.name = "metapath",
.descr = "metadata directory paths",
.group = XFROG_SCRUB_GROUP_METAPATH,
},
};
const struct xfrog_scrub_descr xfrog_metapaths[XFS_SCRUB_METAPATH_NR] = {
[XFS_SCRUB_METAPATH_RTBITMAP] = {
.name = "rtbitmap",
.descr = "realtime bitmap metadir path",
.group = XFROG_SCRUB_GROUP_FS,
},
[XFS_SCRUB_METAPATH_RTSUMMARY] = {
.name = "rtsummary",
.descr = "realtime summary metadir path",
.group = XFROG_SCRUB_GROUP_FS,
},
[XFS_SCRUB_METAPATH_USRQUOTA] = {
.name = "usrquota",
.descr = "user quota metadir path",
.group = XFROG_SCRUB_GROUP_FS,
},
[XFS_SCRUB_METAPATH_GRPQUOTA] = {
.name = "grpquota",
.descr = "group quota metadir path",
.group = XFROG_SCRUB_GROUP_FS,
},
[XFS_SCRUB_METAPATH_PRJQUOTA] = {
.name = "prjquota",
.descr = "project quota metadir path",
.group = XFROG_SCRUB_GROUP_FS,
},
};
/* Invoke the scrub ioctl. Returns zero or negative error code. */
int
xfrog_scrub_metadata(
struct xfs_fd *xfd,
struct xfs_scrub_metadata *meta)
{
int ret;
ret = ioctl(xfd->fd, XFS_IOC_SCRUB_METADATA, meta);
if (ret)
return -errno;
return 0;
}
/* Decide if there have been any scrub failures up to this point. */
static inline int
xfrog_scrubv_previous_failures(
struct xfs_scrub_vec_head *vhead,
struct xfs_scrub_vec *barrier_vec)
{
struct xfs_scrub_vec *v;
__u32 failmask;
failmask = barrier_vec->sv_flags & XFS_SCRUB_FLAGS_OUT;
for (v = vhead->svh_vecs; v < barrier_vec; v++) {
if (v->sv_type == XFS_SCRUB_TYPE_BARRIER)
continue;
/*
* Runtime errors count as a previous failure, except the ones
* used to ask userspace to retry.
*/
if (v->sv_ret && v->sv_ret != -EBUSY && v->sv_ret != -ENOENT &&
v->sv_ret != -EUSERS)
return -ECANCELED;
/*
* If any of the out-flags on the scrub vector match the mask
* that was set on the barrier vector, that's a previous fail.
*/
if (v->sv_flags & failmask)
return -ECANCELED;
}
return 0;
}
static int
xfrog_scrubv_fallback(
struct xfs_fd *xfd,
struct xfs_scrub_vec_head *vhead)
{
struct xfs_scrub_vec *v;
unsigned int i;
if (vhead->svh_flags & ~XFS_SCRUB_VEC_FLAGS_ALL)
return -EINVAL;
for (i = 0, v = vhead->svh_vecs; i < vhead->svh_nr; i++, v++) {
if (v->sv_reserved)
return -EINVAL;
if (v->sv_type == XFS_SCRUB_TYPE_BARRIER &&
(v->sv_flags & ~XFS_SCRUB_FLAGS_OUT))
return -EINVAL;
}
/* Run all the scrubbers. */
for (i = 0, v = vhead->svh_vecs; i < vhead->svh_nr; i++, v++) {
struct xfs_scrub_metadata sm = {
.sm_type = v->sv_type,
.sm_flags = v->sv_flags,
.sm_ino = vhead->svh_ino,
.sm_gen = vhead->svh_gen,
.sm_agno = vhead->svh_agno,
};
struct timespec tv;
if (v->sv_type == XFS_SCRUB_TYPE_BARRIER) {
v->sv_ret = xfrog_scrubv_previous_failures(vhead, v);
if (v->sv_ret)
break;
continue;
}
v->sv_ret = xfrog_scrub_metadata(xfd, &sm);
v->sv_flags = sm.sm_flags;
if (vhead->svh_rest_us) {
tv.tv_sec = 0;
tv.tv_nsec = vhead->svh_rest_us * 1000;
nanosleep(&tv, NULL);
}
}
return 0;
}
/* Invoke the vectored scrub ioctl. */
static int
xfrog_scrubv_call(
struct xfs_fd *xfd,
struct xfs_scrub_vec_head *vhead)
{
int ret;
ret = ioctl(xfd->fd, XFS_IOC_SCRUBV_METADATA, vhead);
if (ret)
return -errno;
return 0;
}
/* Invoke the vectored scrub ioctl. Returns zero or negative error code. */
int
xfrog_scrubv_metadata(
struct xfs_fd *xfd,
struct xfs_scrub_vec_head *vhead)
{
int error = 0;
if (xfd->flags & XFROG_FLAG_SCRUB_FORCE_SINGLE)
goto try_single;
error = xfrog_scrubv_call(xfd, vhead);
if (error == 0 || (xfd->flags & XFROG_FLAG_SCRUB_FORCE_VECTOR))
return error;
/* If the vectored scrub ioctl wasn't found, force single mode. */
switch (error) {
case -EOPNOTSUPP:
case -ENOTTY:
xfd->flags |= XFROG_FLAG_SCRUB_FORCE_SINGLE;
break;
}
try_single:
return xfrog_scrubv_fallback(xfd, vhead);
}