// 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"

#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;
	xfs_ino_t	metadirino;
	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;
	metadirino = dest->sb_metadirino;

	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_metadirino = metadirino;

	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.data.fd, off, SEEK_SET) != off)
			done = 1;

		if (!done && (bsize = read(x.data.fd, 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, (struct xfs_dsb *)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,
	struct libxfs_init	*x)
{
	struct fs_topology	ft;
	int			blocklog;
	uint64_t		dblocks;
	int			multidisk;

	libxfs_get_topology(x, &ft, true);

	/*
	 * get geometry from get_topology result.
	 * Use default block size (2^12)
	 */
	blocklog = 12;
	multidisk = ft.data.swidth | ft.data.sunit;
	dblocks = x->data.size >> (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);

	if (retval && xfs_sb_version_hasmetadir(rsb))
		do_warn(_("quota accounting and enforcement flags lost\n"));

	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;
}

static int
verify_sb_rtgroups(
	struct xfs_sb		*sbp)
{
	uint64_t		groups;

	if (sbp->sb_rextsize == 0)
		return XR_BAD_RT_GEO_DATA;

	if (sbp->sb_rgextents > XFS_MAX_RGBLOCKS / sbp->sb_rextsize)
		return XR_BAD_RT_GEO_DATA;

	if (sbp->sb_rgextents < XFS_MIN_RGEXTENTS)
		return XR_BAD_RT_GEO_DATA;

	if (sbp->sb_rgcount > XFS_MAX_RGNUMBER)
		return XR_BAD_RT_GEO_DATA;

	groups = howmany_64(sbp->sb_rextents, sbp->sb_rgextents);
	if (groups != sbp->sb_rgcount)
		return XR_BAD_RT_GEO_DATA;

	if (!(sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_EXCHRANGE))
		return XR_BAD_RT_GEO_DATA;

	if (sbp->sb_rgblklog != libxfs_compute_rgblklog(sbp->sb_rgextents,
							sbp->sb_rextsize))
		return XR_BAD_RT_GEO_DATA;

	return 0;
}

/*
 * 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)
		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);
	}

	if (!libxfs_validate_rt_geometry(sb))
		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;

	if (xfs_sb_version_hasmetadir(sb)) {
		if (memchr_inv(sb->sb_pad, 0, sizeof(sb->sb_pad)))
			return XR_SB_GEO_MISMATCH;

		ret = verify_sb_rtgroups(sb);
		if (ret)
			return ret;
	}

	return(XR_OK);
}

void
write_primary_sb(xfs_sb_t *sbp, int size)
{
	struct xfs_dsb	*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.data.fd, 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.data.fd, 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;
	struct xfs_dsb *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.data.fd, 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.data.fd, 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;
}
