blob: 91a36dd34f1bf03fde37800f0f5be77ab0882eda [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2000-2003,2005 Silicon Graphics, Inc.
* All Rights Reserved.
*/
#include "libfrog/util.h"
#include "libxfs.h"
#include "libxcmd.h"
#include "libxlog.h"
#include "agheader.h"
#include "globals.h"
#include "protos.h"
#include "err_protos.h"
#include "xfs_multidisk.h"
#include "libfrog/topology.h"
#define BSIZE (1024 * 1024)
/*
* copy the fields of a superblock that are present in primary and
* secondaries -- preserve fields that are different in the primary.
*/
static void
copy_sb(xfs_sb_t *source, xfs_sb_t *dest)
{
xfs_ino_t rootino;
xfs_ino_t rbmino;
xfs_ino_t rsumino;
xfs_ino_t uquotino;
xfs_ino_t gquotino;
xfs_ino_t pquotino;
uint16_t versionnum;
rootino = dest->sb_rootino;
rbmino = dest->sb_rbmino;
rsumino = dest->sb_rsumino;
uquotino = dest->sb_uquotino;
gquotino = dest->sb_gquotino;
pquotino = dest->sb_pquotino;
versionnum = dest->sb_versionnum;
*dest = *source;
dest->sb_rootino = rootino;
dest->sb_rbmino = rbmino;
dest->sb_rsumino = rsumino;
dest->sb_uquotino = uquotino;
dest->sb_gquotino = gquotino;
dest->sb_pquotino = pquotino;
dest->sb_versionnum = versionnum;
/*
* copy over version bits that are stamped into all
* secondaries and cannot be changed at run time in
* the primary superblock
*/
if (xfs_sb_version_hasdalign(source))
dest->sb_versionnum |= XFS_SB_VERSION_DALIGNBIT;
dest->sb_versionnum |= XFS_SB_VERSION_EXTFLGBIT;
/*
* these are all supposed to be zero or will get reset anyway
*/
dest->sb_icount = 0;
dest->sb_ifree = 0;
dest->sb_fdblocks = 0;
dest->sb_frextents = 0;
memset(source->sb_fname, 0, 12);
}
static int
verify_sb_blocksize(xfs_sb_t *sb)
{
/* check to make sure blocksize is legal 2^N, 9 <= N <= 16 */
if (sb->sb_blocksize == 0)
return XR_BAD_BLOCKSIZE;
if (sb->sb_blocklog < XFS_MIN_BLOCKSIZE_LOG ||
sb->sb_blocklog > XFS_MAX_BLOCKSIZE_LOG)
return XR_BAD_BLOCKLOG;
if (sb->sb_blocksize != (1 << sb->sb_blocklog))
return XR_BAD_BLOCKLOG;
return 0;
}
/*
* find a secondary superblock, copy it into the sb buffer.
* start is the point to begin reading BSIZE bytes.
* skip contains a byte-count of how far to advance for next read.
*/
static int
__find_secondary_sb(
xfs_sb_t *rsb,
uint64_t start,
uint64_t skip)
{
xfs_off_t off;
xfs_sb_t *sb;
xfs_sb_t bufsb;
char *c_bufsb;
int done;
int i;
int dirty;
int retval;
int bsize;
sb = (xfs_sb_t *)memalign(libxfs_device_alignment(), BSIZE);
if (!sb) {
do_error(
_("error finding secondary superblock -- failed to memalign buffer\n"));
exit(1);
}
memset(&bufsb, 0, sizeof(xfs_sb_t));
retval = 0;
dirty = 0;
bsize = 0;
/*
* skip first sector since we know that's bad
*/
for (done = 0, off = start; !done ; off += skip) {
/*
* read disk 1 MByte at a time.
*/
if (lseek(x.dfd, off, SEEK_SET) != off) {
done = 1;
}
if (!done && (bsize = read(x.dfd, sb, BSIZE)) <= 0) {
done = 1;
}
do_warn(".");
/*
* check the buffer 512 bytes at a time since
* we don't know how big the sectors really are.
*/
for (i = 0; !done && i < bsize; i += BBSIZE) {
c_bufsb = (char *)sb + i;
libxfs_sb_from_disk(&bufsb, (xfs_dsb_t *)c_bufsb);
if (verify_sb(c_bufsb, &bufsb, 0) != XR_OK)
continue;
do_warn(_("found candidate secondary superblock...\n"));
/*
* found one. now verify it by looking
* for other secondaries.
*/
memmove(rsb, &bufsb, sizeof(xfs_sb_t));
rsb->sb_inprogress = 0;
copied_sunit = 1;
if (verify_set_primary_sb(rsb, 0, &dirty) == XR_OK) {
do_warn(
_("verified secondary superblock...\n"));
done = 1;
retval = 1;
} else {
do_warn(
_("unable to verify superblock, continuing...\n"));
}
}
}
free(sb);
return retval;
}
static int
guess_default_geometry(
uint64_t *agsize,
uint64_t *agcount,
libxfs_init_t *x)
{
struct fs_topology ft;
int blocklog;
uint64_t dblocks;
int multidisk;
memset(&ft, 0, sizeof(ft));
get_topology(x, &ft, 1);
/*
* get geometry from get_topology result.
* Use default block size (2^12)
*/
blocklog = 12;
multidisk = ft.dswidth | ft.dsunit;
dblocks = x->dsize >> (blocklog - BBSHIFT);
calc_default_ag_geometry(blocklog, dblocks, multidisk,
agsize, agcount);
return blocklog;
}
int
find_secondary_sb(xfs_sb_t *rsb)
{
int retval = 0;
uint64_t agcount;
uint64_t agsize;
uint64_t skip;
int blocklog;
/*
* Attempt to find secondary sb with a coarse approach,
* first trying agblocks and blocksize read from sb, providing
* they're sane.
*/
do_warn(_("\nattempting to find secondary superblock...\n"));
if (verify_sb_blocksize(rsb) == 0) {
skip = (uint64_t)rsb->sb_agblocks * rsb->sb_blocksize;
if (skip >= XFS_AG_MIN_BYTES && skip <= XFS_AG_MAX_BYTES)
retval = __find_secondary_sb(rsb, skip, skip);
}
/* If that failed, retry coarse approach, using default geometry */
if (!retval) {
blocklog = guess_default_geometry(&agsize, &agcount, &x);
skip = agsize << blocklog;
retval = __find_secondary_sb(rsb, skip, skip);
}
/* If that failed, fall back to the brute force method */
if (!retval)
retval = __find_secondary_sb(rsb, XFS_AG_MIN_BYTES, BSIZE);
return retval;
}
/*
* Calculate what the inode alignment field ought to be based on internal
* superblock info and determine if it is valid.
*
* For standard v5 superblocks, the inode alignment must either match
* XFS_INODE_BIG_CLUSTER_SIZE or a multiple based on the inode size. For v5
* superblocks with sparse inode chunks enabled, inode alignment must match the
* inode chunk size.
*
* Return true if the alignment is valid, false otherwise.
*/
static bool
sb_validate_ino_align(struct xfs_sb *sb)
{
xfs_extlen_t align;
if (!xfs_sb_version_hasalign(sb))
return true;
/* standard cluster size alignment is always valid */
align = XFS_INODE_BIG_CLUSTER_SIZE >> sb->sb_blocklog;
if (align == sb->sb_inoalignmt)
return true;
/* alignment scaled by inode size is v5 only for now */
if (!xfs_sb_version_hascrc(sb))
return false;
align = (XFS_INODE_BIG_CLUSTER_SIZE *
sb->sb_inodesize / XFS_DINODE_MIN_SIZE) >> sb->sb_blocklog;
if (align == sb->sb_inoalignmt)
return true;
/*
* Sparse inodes requires inoalignmt to match full inode chunk size and
* spino_align to match the scaled alignment (as calculated above).
*/
if (xfs_sb_version_hassparseinodes(sb)) {
if (align != sb->sb_spino_align)
return false;
align = (sb->sb_inodesize * XFS_INODES_PER_CHUNK)
>> sb->sb_blocklog;
if (align == sb->sb_inoalignmt)
return true;
}
return false;
}
/*
* Validate the given log space. Derived from xfs_log_mount, though we
* can't validate the minimum log size until later. We only do this
* validation on V5 filesystems because the kernel doesn't reject malformed
* log geometry on older revision filesystems.
*
* Returns false if the log is garbage.
*/
static bool
verify_sb_loginfo(
struct xfs_sb *sb)
{
if (xfs_sb_version_hascrc(sb) &&
(sb->sb_logblocks == 0 ||
sb->sb_logblocks > XFS_MAX_LOG_BLOCKS ||
((unsigned long long)sb->sb_logblocks << sb->sb_blocklog) >
XFS_MAX_LOG_BYTES))
return false;
if (sb->sb_logsunit > 1 && sb->sb_logsunit % sb->sb_blocksize)
return false;
return true;
}
/*
* verify a superblock -- does not verify root inode #
* can only check that geometry info is internally
* consistent. because of growfs, that's no guarantee
* of correctness (e.g. geometry may have changed)
*
* fields verified or consistency checked:
*
* sb_magicnum
*
* sb_versionnum
*
* sb_inprogress
*
* sb_blocksize (as a group)
* sb_blocklog
*
* geometry info - sb_dblocks (as a group)
* sb_agcount
* sb_agblocks
* sb_agblklog
*
* inode info - sb_inodesize (x-checked with geo info)
* sb_inopblock
*
* sector size info -
* sb_sectsize
* sb_sectlog
* sb_logsectsize
* sb_logsectlog
*
* not checked here -
* sb_rootino
* sb_fname
* sb_fpack
* sb_logstart
* sb_uuid
*
* ALL real-time fields
* final 4 summary counters
*/
int
verify_sb(char *sb_buf, xfs_sb_t *sb, int is_primary_sb)
{
uint32_t bsize;
int i;
int ret;
/* check magic number and version number */
if (sb->sb_magicnum != XFS_SB_MAGIC)
return(XR_BAD_MAGIC);
if (!xfs_sb_good_version(sb))
return(XR_BAD_VERSION);
/* does sb think mkfs really finished ? */
if (is_primary_sb && sb->sb_inprogress == 1)
return(XR_BAD_INPROGRESS);
/*
* before going *any further*, validate the sector size and if the
* version says we should have CRCs enabled, validate that.
*/
/* check to make sure sectorsize is legal 2^N, 9 <= N <= 15 */
if (sb->sb_sectsize == 0)
return(XR_BAD_SECT_SIZE_DATA);
bsize = 1;
for (i = 0; bsize < sb->sb_sectsize &&
i < sizeof(sb->sb_sectsize) * NBBY; i++) {
bsize <<= 1;
}
if (i < XFS_MIN_SECTORSIZE_LOG || i > XFS_MAX_SECTORSIZE_LOG)
return(XR_BAD_SECT_SIZE_DATA);
/* check sb sectorsize field against sb sectlog field */
if (i != sb->sb_sectlog)
return(XR_BAD_SECT_SIZE_DATA);
/* sector size in range - CRC check time */
if (xfs_sb_version_hascrc(sb) &&
!libxfs_verify_cksum(sb_buf, sb->sb_sectsize, XFS_SB_CRC_OFF))
return XR_BAD_CRC;
/* check to ensure blocksize and blocklog are legal */
ret = verify_sb_blocksize(sb);
if (ret != 0)
return ret;
/* sanity check ag count, size fields against data size field */
if (sb->sb_dblocks == 0 ||
sb->sb_dblocks > XFS_MAX_DBLOCKS(sb) ||
sb->sb_dblocks < XFS_MIN_DBLOCKS(sb))
return(XR_BAD_FS_SIZE_DATA);
if (sb->sb_agblklog != (uint8_t)log2_roundup(sb->sb_agblocks))
return(XR_BAD_FS_SIZE_DATA);
if (sb->sb_inodesize < XFS_DINODE_MIN_SIZE ||
sb->sb_inodesize > XFS_DINODE_MAX_SIZE ||
sb->sb_inodelog < XFS_DINODE_MIN_LOG ||
sb->sb_inodelog > XFS_DINODE_MAX_LOG ||
sb->sb_inodesize != (1 << sb->sb_inodelog) ||
sb->sb_logsunit > XLOG_MAX_RECORD_BSIZE ||
sb->sb_inopblock != howmany(sb->sb_blocksize, sb->sb_inodesize) ||
(sb->sb_blocklog - sb->sb_inodelog != sb->sb_inopblog))
return XR_BAD_INO_SIZE_DATA;
if (!verify_sb_loginfo(sb))
return XR_BAD_LOG_GEOMETRY;
if (xfs_sb_version_hassector(sb)) {
/* check to make sure log sector is legal 2^N, 9 <= N <= 15 */
if (sb->sb_logsectsize == 0)
return(XR_BAD_SECT_SIZE_DATA);
bsize = 1;
for (i = 0; bsize < sb->sb_logsectsize &&
i < sizeof(sb->sb_logsectsize) * NBBY; i++) {
bsize <<= 1;
}
if (i < XFS_MIN_SECTORSIZE_LOG || i > XFS_MAX_SECTORSIZE_LOG)
return(XR_BAD_SECT_SIZE_DATA);
/* check sb log sectorsize field against sb log sectlog field */
if (i != sb->sb_logsectlog)
return(XR_BAD_SECT_SIZE_DATA);
}
/*
* real-time extent size is always set
*/
if (sb->sb_rextsize * sb->sb_blocksize > XFS_MAX_RTEXTSIZE)
return(XR_BAD_RT_GEO_DATA);
if (sb->sb_rextsize * sb->sb_blocksize < XFS_MIN_RTEXTSIZE)
return(XR_BAD_RT_GEO_DATA);
if (sb->sb_rblocks == 0) {
if (sb->sb_rextents != 0)
return(XR_BAD_RT_GEO_DATA);
if (sb->sb_rbmblocks != 0)
return(XR_BAD_RT_GEO_DATA);
if (sb->sb_rextslog != 0)
return(XR_BAD_RT_GEO_DATA);
if (sb->sb_frextents != 0)
return(XR_BAD_RT_GEO_DATA);
} else {
/*
* if we have a real-time partition, sanity-check geometry
*/
if (sb->sb_rblocks / sb->sb_rextsize != sb->sb_rextents)
return(XR_BAD_RT_GEO_DATA);
if (sb->sb_rextslog !=
libxfs_highbit32((unsigned int)sb->sb_rextents))
return(XR_BAD_RT_GEO_DATA);
if (sb->sb_rbmblocks != (xfs_extlen_t) howmany(sb->sb_rextents,
NBBY * sb->sb_blocksize))
return(XR_BAD_RT_GEO_DATA);
}
/*
* verify correctness of inode alignment if it's there
*/
if (!sb_validate_ino_align(sb))
return(XR_BAD_INO_ALIGN);
/*
* verify max. % of inodes (sb_imax_pct)
*/
if (sb->sb_imax_pct > 100)
return(XR_BAD_INO_MAX_PCT);
/*
* verify stripe alignment fields if present
*/
if (xfs_sb_version_hasdalign(sb)) {
if ((!sb->sb_unit && sb->sb_width) ||
(sb->sb_unit && sb->sb_agblocks % sb->sb_unit))
return(XR_BAD_SB_UNIT);
if ((sb->sb_unit && !sb->sb_width) ||
(sb->sb_width && sb->sb_unit && sb->sb_width % sb->sb_unit))
return(XR_BAD_SB_WIDTH);
} else if (sb->sb_unit || sb->sb_width)
return XR_BAD_SB_WIDTH;
/* Directory block log */
if (sb->sb_blocklog + sb->sb_dirblklog > XFS_MAX_BLOCKSIZE_LOG)
return XR_BAD_DIR_SIZE_DATA;
return(XR_OK);
}
void
write_primary_sb(xfs_sb_t *sbp, int size)
{
xfs_dsb_t *buf;
if (no_modify)
return;
buf = memalign(libxfs_device_alignment(), size);
if (buf == NULL) {
do_error(_("failed to memalign superblock buffer\n"));
return;
}
memset(buf, 0, size);
if (lseek(x.dfd, 0LL, SEEK_SET) != 0LL) {
free(buf);
do_error(_("couldn't seek to offset 0 in filesystem\n"));
}
libxfs_sb_to_disk(buf, sbp);
if (xfs_sb_version_hascrc(sbp))
xfs_update_cksum((char *)buf, size, XFS_SB_CRC_OFF);
if (write(x.dfd, buf, size) != size) {
free(buf);
do_error(_("primary superblock write failed!\n"));
}
free(buf);
}
/*
* get a possible superblock -- checks for internal consistency
*/
int
get_sb(xfs_sb_t *sbp, xfs_off_t off, int size, xfs_agnumber_t agno)
{
int error, rval;
xfs_dsb_t *buf;
buf = memalign(libxfs_device_alignment(), size);
if (buf == NULL) {
do_error(
_("error reading superblock %u -- failed to memalign buffer\n"),
agno);
exit(1);
}
memset(buf, 0, size);
memset(sbp, 0, sizeof(*sbp));
/* try and read it first */
if (lseek(x.dfd, off, SEEK_SET) != off) {
do_warn(
_("error reading superblock %u -- seek to offset %" PRId64 " failed\n"),
agno, off);
free(buf);
return(XR_EOF);
}
if ((rval = read(x.dfd, buf, size)) != size) {
error = errno;
do_warn(
_("superblock read failed, offset %" PRId64 ", size %d, ag %u, rval %d\n"),
off, size, agno, rval);
do_error("%s\n", strerror(error));
}
libxfs_sb_from_disk(sbp, buf);
rval = verify_sb((char *)buf, sbp, agno == 0);
free(buf);
return rval;
}
/* returns element on list with highest reference count */
static fs_geo_list_t *
get_best_geo(fs_geo_list_t *list)
{
int cnt = 0;
fs_geo_list_t *current, *rval = NULL;
current = list;
while (current != NULL) {
if (current->refs > cnt) {
rval = current;
cnt = current->refs;
}
current = current->next;
}
return(rval);
}
/* adds geometry info to linked list. returns (sometimes new) head of list */
static fs_geo_list_t *
add_geo(fs_geo_list_t *list, fs_geometry_t *geo_p, int index)
{
fs_geo_list_t *current = list;
while (current != NULL) {
if (memcmp(geo_p, &current->geo, sizeof(fs_geometry_t)) == 0) {
current->refs++;
return(list);
}
current = current->next;
}
if ((current = malloc(sizeof(fs_geo_list_t))) == NULL) {
do_error(_("couldn't malloc geometry structure\n"));
exit(1);
}
current->geo = *geo_p;
current->refs = 1;
current->next = list;
current->index = index;
return(current);
}
static void
free_geo(fs_geo_list_t *list)
{
fs_geo_list_t *next;
fs_geo_list_t *current;
for (current = list; current != NULL; current = next) {
next = current->next;
free(current);
}
}
void
get_sb_geometry(fs_geometry_t *geo, xfs_sb_t *sbp)
{
memset(geo, 0, sizeof(fs_geometry_t));
/*
* blindly set fields that we know are always good
*/
geo->sb_blocksize = sbp->sb_blocksize;
geo->sb_dblocks = sbp->sb_dblocks;
geo->sb_rblocks = sbp->sb_rblocks;
geo->sb_rextents = sbp->sb_rextents;
geo->sb_logstart = sbp->sb_logstart;
geo->sb_rextsize = sbp->sb_rextsize;
geo->sb_agblocks = sbp->sb_agblocks;
geo->sb_agcount = sbp->sb_agcount;
geo->sb_rbmblocks = sbp->sb_rbmblocks;
geo->sb_logblocks = sbp->sb_logblocks;
geo->sb_sectsize = sbp->sb_sectsize;
geo->sb_inodesize = sbp->sb_inodesize;
if (xfs_sb_version_hasalign(sbp))
geo->sb_ialignbit = 1;
if (xfs_sb_version_hasdalign(sbp))
geo->sb_salignbit = 1;
geo->sb_extflgbit = 1;
geo->sb_fully_zeroed = 1;
}
/*
* the way to verify that a primary sb is consistent with the
* filesystem is find the secondaries given the info in the
* primary and compare the geometries in the secondaries against
* the geometry indicated by the primary.
*
* returns 0 if ok, else error code (XR_EOF, XR_INSUFF_SEC_SB, etc).
*/
int
verify_set_primary_sb(xfs_sb_t *rsb,
int sb_index,
int *sb_modified)
{
xfs_off_t off;
fs_geometry_t geo;
xfs_sb_t *sb;
fs_geo_list_t *list;
fs_geo_list_t *current;
xfs_agnumber_t agno;
int num_sbs;
int size;
int num_ok;
int retval;
/*
* We haven't been able to validate the sector size yet properly
* (e.g. in the case of repairing an image in a file), so we need to
* take into account sector mismatches and so use the maximum possible
* sector size rather than the sector size in @rsb.
*/
size = NUM_AGH_SECTS * (1 << (XFS_MAX_SECTORSIZE_LOG));
list = NULL;
num_ok = 0;
*sb_modified = 0;
num_sbs = rsb->sb_agcount;
sb = (xfs_sb_t *) alloc_ag_buf(size);
/*
* put the primary sb geometry info onto the geometry list
*/
get_sb_geometry(&geo, rsb);
list = add_geo(list, &geo, sb_index);
/*
* scan the secondaries and check them off as we get them so we only
* process each one once
*/
for (agno = 1; agno < rsb->sb_agcount; agno++) {
off = (xfs_off_t)agno * rsb->sb_agblocks << rsb->sb_blocklog;
retval = get_sb(sb, off, size, agno);
if (retval == XR_EOF)
goto out_free_list;
if (retval == XR_OK) {
/*
* save away geometry info. don't bother checking the
* sb against the agi/agf as the odds of the sb being
* corrupted in a way that it is internally consistent
* but not consistent with the rest of the filesystem is
* really really low.
*/
get_sb_geometry(&geo, sb);
list = add_geo(list, &geo, agno);
num_ok++;
}
}
/*
* see if we have enough superblocks to bother with
*/
retval = 0;
if (num_ok < num_sbs / 2) {
retval = XR_INSUFF_SEC_SB;
goto out_free_list;
}
current = get_best_geo(list);
/*
* check that enough sbs agree that we're willing to
* go with this geometry. if not, print out the
* geometry and a message about the force option.
*/
switch (num_sbs) {
case 2:
/*
* If we only have two allocation groups, and the superblock
* in the second allocation group differs from the primary
* superblock we can't verify the geometry information.
* Warn the user about this situation and get out unless
* explicitly overridden.
*/
if (current->refs != 2) {
if (!force_geo) {
do_warn(
_("Only two AGs detected and they do not match - "
"cannot validate filesystem geometry.\n"
"Use the -o force_geometry option to proceed.\n"));
exit(1);
}
}
goto out_free_list;
case 1:
/*
* If we only have a single allocation group there is no
* secondary superblock that we can use to verify the geometry
* information. Warn the user about this situation and get
* out unless explicitly overridden.
*/
if (!force_geo) {
do_warn(
_("Only one AG detected - "
"cannot validate filesystem geometry.\n"
"Use the -o force_geometry option to proceed.\n"));
exit(1);
}
goto out_free_list;
default:
/*
* at least half of the probed superblocks have
* to agree. if they don't, this fs is probably
* too far gone anyway considering the fact that
* XFS normally doesn't alter the secondary superblocks.
*/
if (current->refs < num_sbs / 2) {
do_warn(
_("Not enough matching superblocks - cannot proceed.\n"));
exit(1);
}
}
/*
* set the geometry into primary superblock if necessary.
*/
if (current->index != sb_index) {
*sb_modified = 1;
off = (xfs_off_t)current->index * current->geo.sb_agblocks
* current->geo.sb_blocksize;
if (get_sb(sb, off, current->geo.sb_sectsize,
current->index) != XR_OK)
do_error(_("could not read superblock\n"));
copy_sb(sb, rsb);
/*
* turn off inprogress bit since this is the primary.
* also save away values that we need to ensure are
* consistent in the other secondaries.
*/
rsb->sb_inprogress = 0;
sb_inoalignmt = sb->sb_inoalignmt;
sb_unit = sb->sb_unit;
sb_width = sb->sb_width;
}
out_free_list:
free_geo(list);
free(sb);
return retval;
}