git: e68866164212 - main - Move the ability to search for alternate UFS superblocks from fsck_ffs(8) into ffs_sbsearch() to allow use by other parts of the system.

From: Kirk McKusick <mckusick_at_FreeBSD.org>
Date: Sat, 13 Aug 2022 19:45:22 UTC
The branch main has been updated by mckusick:

URL: https://cgit.FreeBSD.org/src/commit/?id=e68866164212d62b8158e93f09bd29554428eef0

commit e68866164212d62b8158e93f09bd29554428eef0
Author:     Kirk McKusick <mckusick@FreeBSD.org>
AuthorDate: 2022-08-13 19:41:53 +0000
Commit:     Kirk McKusick <mckusick@FreeBSD.org>
CommitDate: 2022-08-13 19:43:40 +0000

    Move the ability to search for alternate UFS superblocks from fsck_ffs(8)
    into ffs_sbsearch() to allow use by other parts of the system.
    
    Historically only fsck_ffs(8), the UFS filesystem checker, had code
    to track down and use alternate UFS superblocks. Since fsdb(8) used
    much of the fsck_ffs(8) implementation it had some ability to track
    down alternate superblocks.
    
    This change extracts the code to track down alternate superblocks
    from fsck_ffs(8) and puts it into a new function ffs_sbsearch() in
    sys/ufs/ffs/ffs_subr.c. Like ffs_sbget() and ffs_sbput() also found
    in ffs_subr.c, these functions can be used directly by the kernel
    subsystems. Additionally they are exported to the UFS library,
    libufs(8) so that they can be used by user-level programs. The new
    functions added to libufs(8) are sbfind(3) that is an alternative
    to sbread(3) and sbsearch(3) that is an alternative to sbget(3).
    See their manual pages for further details.
    
    The utilities that have been changed to search for superblocks are
    dumpfs(8), fsdb(8), ffsinfo(8), and fsck_ffs(8). Also, the prtblknos(8)
    tool found in tools/diag/prtblknos searches for superblocks.
    
    The UFS specific mount code uses the superblock search interface
    when mounting the root filesystem and when the administrator doing
    a mount(8) command specifies the force flag (-f). The standalone UFS
    boot code (found in stand/libsa/ufs.c) uses the superblock search
    code in the hope of being able to get the system up and running so
    that fsck_ffs(8) can be used to get the filesystem cleaned up.
    
    The following utilities have not been changed to search for
    superblocks: clri(8), tunefs(8), snapinfo(8), fstyp(8), quot(8),
    dump(8), fsirand(8), growfs(8), quotacheck(8), gjournal(8), and
    glabel(8). When these utilities fail, they do report the cause of
    the failure. The one exception is the tasting code used to try and
    figure what a given disk contains. The tasting code will remain
    silent so as not to put out a slew of messages as it trying to taste
    every new mass storage device that shows up.
    
    Reviewed by: kib
    Reviewed by: Warner Losh
    Tested by:   Peter Holm
    Differential Revision: https://reviews.freebsd.org/D36053
    Sponsored by: The FreeBSD Foundation
---
 lib/libufs/Makefile         |   2 +
 lib/libufs/libufs.h         |   4 +
 lib/libufs/sblock.c         |  54 ++++++++++++--
 lib/libufs/sbread.3         |  69 ++++++++++++++----
 sbin/dumpfs/dumpfs.c        |   9 +--
 sbin/ffsinfo/ffsinfo.c      |   5 +-
 sbin/fsck_ffs/fsck.h        |   3 +-
 sbin/fsck_ffs/globs.c       |   2 -
 sbin/fsck_ffs/main.c        |   2 +-
 sbin/fsck_ffs/setup.c       | 103 +++++++++++++-------------
 sbin/fsdb/fsdb.c            |   2 +-
 stand/libsa/ufs.c           |   6 +-
 sys/ufs/ffs/ffs_extern.h    |   2 +
 sys/ufs/ffs/ffs_subr.c      | 174 ++++++++++++++++++++++++++++++++++++++++++++
 sys/ufs/ffs/ffs_vfsops.c    |  13 ++--
 sys/ufs/ffs/fs.h            |   8 ++
 tools/diag/prtblknos/main.c |   6 +-
 17 files changed, 364 insertions(+), 100 deletions(-)

diff --git a/lib/libufs/Makefile b/lib/libufs/Makefile
index a767546f457f..623d5ae541a0 100644
--- a/lib/libufs/Makefile
+++ b/lib/libufs/Makefile
@@ -20,6 +20,8 @@ MLINKS+= cgread.3 cgput.3
 MLINKS+= getinode.3 putinode.3
 MLINKS+= sbread.3 sbwrite.3
 MLINKS+= sbread.3 sbget.3
+MLINKS+= sbread.3 sbsearch.3
+MLINKS+= sbread.3 sbfind.3
 MLINKS+= sbread.3 sbput.3
 MLINKS+= ufs_disk_close.3 ufs_disk_fillout.3
 MLINKS+= ufs_disk_close.3 ufs_disk_fillout_blank.3
diff --git a/lib/libufs/libufs.h b/lib/libufs/libufs.h
index cb8774454b38..5dbf9c2a5c28 100644
--- a/lib/libufs/libufs.h
+++ b/lib/libufs/libufs.h
@@ -111,6 +111,8 @@ void	ffs_clusteracct(struct fs *, struct cg *, ufs1_daddr_t, int);
 void	ffs_fragacct(struct fs *, int, int32_t [], int);
 int	ffs_isblock(struct fs *, u_char *, ufs1_daddr_t);
 int	ffs_isfreeblock(struct fs *, u_char *, ufs1_daddr_t);
+int	ffs_sbsearch(void *, struct fs **, int, char *,
+	    int (*)(void *, off_t, void **, int));
 void	ffs_setblock(struct fs *, u_char *, ufs1_daddr_t);
 int	ffs_sbget(void *, struct fs **, off_t, int, char *,
 	    int (*)(void *, off_t, void **, int));
@@ -149,9 +151,11 @@ int putinode(struct uufsd *);
  * sblock.c
  */
 int sbread(struct uufsd *);
+int sbfind(struct uufsd *, int);
 int sbwrite(struct uufsd *, int);
 /* low level superblock read/write functions */
 int sbget(int, struct fs **, off_t, int);
+int sbsearch(int, struct fs **, int);
 int sbput(int, struct fs *, int);
 
 /*
diff --git a/lib/libufs/sblock.c b/lib/libufs/sblock.c
index 5708d50aa4f9..4dfa0e79a711 100644
--- a/lib/libufs/sblock.c
+++ b/lib/libufs/sblock.c
@@ -73,6 +73,30 @@ sbread(struct uufsd *disk)
 	return (handle_disk_read(disk, fs, error));
 }
 
+/*
+ * Make an extensive search to find a superblock. If the superblock
+ * in the standard place cannot be used, try looking for one of the
+ * backup superblocks.
+ *
+ * The flags parameter is made up of the following or'ed together options:
+ *
+ * UFS_NOMSG indicates that superblock inconsistency error messages
+ *    should not be printed.
+ *
+ * UFS_NOCSUM causes only the superblock itself to be returned, but does
+ *    not read in any auxillary data structures like the cylinder group
+ *    summary information.
+ */
+int
+sbfind(struct uufsd *disk, int flags)
+{
+	struct fs *fs;
+	int error;
+
+	error = sbsearch(disk->d_fd, &fs, flags);
+	return (handle_disk_read(disk, fs, error));
+}
+
 static int
 handle_disk_read(struct uufsd *disk, struct fs *fs, int error)
 {
@@ -174,8 +198,27 @@ static int use_pwrite(void *devfd, off_t loc, void *buf, int size);
 int
 sbget(int devfd, struct fs **fsp, off_t sblockloc, int flags)
 {
+	int error;
+
+	error = ffs_sbget(&devfd, fsp, sblockloc, flags, "user", use_pread);
+	fflush(NULL); /* flush any messages */
+	return (error);
+}
+
+/*
+ * Make an extensive search of the devfd device to find a superblock.
+ * If the superblock in the standard place cannot be used, try looking
+ * for one of the backup superblocks. If found, memory is allocated and
+ * returned in fsp.
+ */
+int
+sbsearch(int devfd, struct fs **fsp, int flags)
+{
+	int error;
 
-	return (ffs_sbget(&devfd, fsp, sblockloc, flags, "user", use_pread));
+	error = ffs_sbsearch(&devfd, fsp, flags, "user", use_pread);
+	fflush(NULL); /* flush any messages */
+	return (error);
 }
 
 /*
@@ -209,11 +252,10 @@ sbput(int devfd, struct fs *fs, int numaltwrite)
 	off_t savedactualloc;
 	int i, error;
 
-	if ((error = ffs_sbput(&devfd, fs, fs->fs_sblockactualloc,
-	     use_pwrite)) != 0)
+	error = ffs_sbput(&devfd, fs, fs->fs_sblockactualloc, use_pwrite);
+	fflush(NULL); /* flush any messages */
+	if (error != 0 || numaltwrite == 0)
 		return (error);
-	if (numaltwrite == 0)
-		return (0);
 	savedactualloc = fs->fs_sblockactualloc;
 	if (fs->fs_si != NULL) {
 		savedcsp = fs->fs_csp;
@@ -223,6 +265,7 @@ sbput(int devfd, struct fs *fs, int numaltwrite)
 		fs->fs_sblockactualloc = dbtob(fsbtodb(fs, cgsblock(fs, i)));
 		if ((error = ffs_sbput(&devfd, fs, fs->fs_sblockactualloc,
 		     use_pwrite)) != 0) {
+			fflush(NULL); /* flush any messages */
 			fs->fs_sblockactualloc = savedactualloc;
 			fs->fs_csp = savedcsp;
 			return (error);
@@ -231,6 +274,7 @@ sbput(int devfd, struct fs *fs, int numaltwrite)
 	fs->fs_sblockactualloc = savedactualloc;
 	if (fs->fs_si != NULL)
 		fs->fs_csp = savedcsp;
+	fflush(NULL); /* flush any messages */
 	return (0);
 }
 
diff --git a/lib/libufs/sbread.3 b/lib/libufs/sbread.3
index f579fc7ffdf1..460699842036 100644
--- a/lib/libufs/sbread.3
+++ b/lib/libufs/sbread.3
@@ -3,19 +3,21 @@
 .\" Description:
 .\" 	Manual page for libufs functions:
 .\"		sbget(3)
+.\"		sbsearch(3)
 .\"		sbput(3)
 .\"		sbread(3)
+.\"		sbfind(3)
 .\"		sbwrite(3)
 .\"
 .\" This file is in the public domain.
 .\"
 .\" $FreeBSD$
 .\"
-.Dd September 2, 2020
+.Dd August 8, 2022
 .Dt SBREAD 3
 .Os
 .Sh NAME
-.Nm sbget , sbput , sbread , sbwrite
+.Nm sbget , sbsearch , sbput , sbread , sbfind , sbwrite
 .Nd read and write superblocks of a UFS file system
 .Sh LIBRARY
 .Lb libufs
@@ -29,16 +31,22 @@
 .Ft int
 .Fn sbget "int devfd" "struct fs **fsp" "off_t sblockloc" "int flags"
 .Ft int
+.Fn sbsearch "int devfd" "struct fs **fsp" "int flags"
+.Ft int
 .Fn sbput "int devfd" "struct fs *fs" "int numaltwrite"
 .Ft int
 .Fn sbread "struct uufsd *disk"
 .Ft int
+.Fn sbfind "struct uufsd *disk" "int flags"
+.Ft int
 .Fn sbwrite "struct uufsd *disk" "int all"
 .Sh DESCRIPTION
 The
-.Fn sbget
+.Fn sbget ,
+.Fn sbsearch ,
+.Fn sbread ,
 and
-.Fn sbread
+.Fn sbfind
 functions provide superblock reads for
 .Xr libufs 3
 consumers.
@@ -52,7 +60,9 @@ consumers.
 .Pp
 The
 .Fn sbget
-function first allocates a buffer to hold the superblock.
+and
+.Fn sbsearch
+functions first allocate a buffer to hold the superblock.
 Using the
 .Va devfd
 file descriptor that references the filesystem disk,
@@ -65,26 +75,38 @@ The value
 may be specified for
 .Va sblockloc
 to request that the standard location for the superblock be read.
+The
+.Fn sbsearch
+function uses the
+.Va devfd
+file descriptor that references the filesystem disk,
+to search first for the superblock at the standard location.
+If it is not found or is too damaged to use
+.Fn sbsearch
+will attempt to find one of the filesystem's alternate superblocks.
 Flags are specified by
 .Em or Ns 'ing
 the following values:
 .Pp
-.Bl -tag -width UFS_NOHASHFAIL
-.It Cm UFS_NOHASHFAIL
-Will note if the check hash is wrong but will still return the superblock.
+.Bl -tag -width UFS_NOCSUM
+.It Cm UFS_NOCSUM
+Causes only the superblock itself to be returned, but does not read in any
+auxiliary data structures like the cylinder group summary information.
 .It Cm UFS_NOMSG
 Indicates that superblock inconsistency error messages should not be printed.
-.It Cm UFS_NOCSUM
-Causes only the superblock itself to be returned, but does not read in any auxiliary data structures like the cylinder group summary information.
 .El
 .Pp
 If successful,
 .Fn sbget
-returns a pointer to the buffer containing the superblock in
+and
+.Fn sbsearch
+functions return a pointer to the buffer containing the superblock in
 .Va fsp .
 The
 .Fn sbget
-function is safe to use in threaded applications.
+and
+.Fn sbsearch
+functions are safe to use in threaded applications.
 .Pp
 The
 .Fn sbput
@@ -113,7 +135,19 @@ modified and the on-disk copy needs to be updated.
 .Pp
 The
 .Fn sbread
-function reads the standard filesystem superblock into the
+function reads the standard filesystem superblock.
+The
+.Fn sbfind
+function tries to find a usable superblock.
+It searchs first for the superblock at the standard location.
+If it is not found or is too damaged to use
+.Fn sbfind
+will attempt to find one of the filesystem's alternate superblocks.
+If successful
+.Fn sbread
+and
+.Fn sbfind
+return a superblock in the
 .Va d_sb ,
 structure embedded in the given user-land UFS disk structure.
 .Pp
@@ -131,16 +165,19 @@ value is non-zero.
 .Sh RETURN VALUES
 .Rv -std sbread sbwrite
 The
-.Fn sbget
+.Fn sbget ,
+.Fn sbsearch ,
 and
 .Fn sbput
 functions return the value 0 if successful;
 otherwise they return one of the errors described below.
 .Sh ERRORS
 The errors returned by
-.Fn sbget
+.Fn sbget ,
+.Fn sbsearch ,
+.Fn sbread ,
 and
-.Fn sbread
+.Fn sbfind ,
 include any of the errors specified for the library function
 .Xr bread 3 .
 Additionally, they may follow the
diff --git a/sbin/dumpfs/dumpfs.c b/sbin/dumpfs/dumpfs.c
index 6a586ffb95ec..4983645156ff 100644
--- a/sbin/dumpfs/dumpfs.c
+++ b/sbin/dumpfs/dumpfs.c
@@ -130,13 +130,8 @@ main(int argc, char *argv[])
 		usage();
 
 	while ((name = *argv++) != NULL) {
-		if (ufs_disk_fillout_blank(&disk, name) == -1) {
-			ufserr(name);
-			eval |= 1;
-			continue;
-		}
-		disk.d_lookupflags |= UFS_NOHASHFAIL;
-		if (sbread(&disk) == -1) {
+		if (ufs_disk_fillout_blank(&disk, name) == -1 ||
+		    sbfind(&disk, 0) == -1) {
 			ufserr(name);
 			eval |= 1;
 			continue;
diff --git a/sbin/ffsinfo/ffsinfo.c b/sbin/ffsinfo/ffsinfo.c
index 33ec5f175cbd..9d447d209ffd 100644
--- a/sbin/ffsinfo/ffsinfo.c
+++ b/sbin/ffsinfo/ffsinfo.c
@@ -223,8 +223,9 @@ main(int argc, char **argv)
 		device = special;
 	}
 
-	if (ufs_disk_fillout(&disk, device) == -1)
-		err(1, "ufs_disk_fillout(%s) failed: %s", device, disk.d_error);
+	if (ufs_disk_fillout_blank(&disk, device) == -1 ||
+	    sbfind(&disk, 0) == -1)
+		err(1, "superblock fetch(%s) failed: %s", device, disk.d_error);
 
 	DBG_OPEN(out_file);	/* already here we need a superblock */
 
diff --git a/sbin/fsck_ffs/fsck.h b/sbin/fsck_ffs/fsck.h
index 1fb0df0c5124..65c7056532be 100644
--- a/sbin/fsck_ffs/fsck.h
+++ b/sbin/fsck_ffs/fsck.h
@@ -363,7 +363,6 @@ extern char preen;		/* just fix normal inconsistencies */
 extern char rerun;		/* rerun fsck. Only used in non-preen mode */
 extern char resolved;		/* cleared if unresolved changes => not clean */
 extern int returntosingle;	/* 1 => return to single user mode on exit */
-extern int sbhashfailed;	/* when reading superblock check hash failed */
 extern long secsize;		/* actual disk sector size */
 extern char skipclean;		/* skip clean file systems if preening */
 extern char snapname[BUFSIZ];	/* when doing snapshots, the name of the file */
@@ -502,7 +501,7 @@ void		pfatal(const char *fmt, ...) __printflike(1, 2);
 void		propagate(void);
 void		prtinode(struct inode *);
 void		pwarn(const char *fmt, ...) __printflike(1, 2);
-int		readsb(int listerr);
+int		readsb(void);
 int		reply(const char *question);
 void		rwerror(const char *mesg, ufs2_daddr_t blk);
 void		sblock_init(void);
diff --git a/sbin/fsck_ffs/globs.c b/sbin/fsck_ffs/globs.c
index 09dbcc6694a2..306944fa95b9 100644
--- a/sbin/fsck_ffs/globs.c
+++ b/sbin/fsck_ffs/globs.c
@@ -96,7 +96,6 @@ char	preen;			/* just fix normal inconsistencies */
 char	rerun;			/* rerun fsck. Only used in non-preen mode */
 int	returntosingle;		/* 1 => return to single user mode on exit */
 char	resolved;		/* cleared if unresolved changes => not clean */
-int	sbhashfailed;		/* when reading superblock check hash failed */
 char	havesb;			/* superblock has been read */
 char	skipclean;		/* skip clean file systems if preening */
 int	fsmodified;		/* 1 => write done to file system */
@@ -156,7 +155,6 @@ fsckinit(void)
 	resolved = 0;
 	havesb = 0;
 	fsmodified = 0;
-	sbhashfailed = 0;
 	fsreadfd = -1;
 	fswritefd = -1;
 	maxfsblock = 0;
diff --git a/sbin/fsck_ffs/main.c b/sbin/fsck_ffs/main.c
index 18634a93c05c..b638d31040dd 100644
--- a/sbin/fsck_ffs/main.c
+++ b/sbin/fsck_ffs/main.c
@@ -271,7 +271,7 @@ checkfilesys(char *filesys)
 	 */
 	sblock_init();
 	sbreadfailed = 0;
-	if (openfilesys(filesys) == 0 || readsb(0) == 0)
+	if (openfilesys(filesys) == 0 || readsb() == 0)
 		sbreadfailed = 1;
 	if (bkgrdcheck) {
 		if (sbreadfailed)
diff --git a/sbin/fsck_ffs/setup.c b/sbin/fsck_ffs/setup.c
index cb95d18859b0..022799117eb0 100644
--- a/sbin/fsck_ffs/setup.c
+++ b/sbin/fsck_ffs/setup.c
@@ -60,6 +60,7 @@ __FBSDID("$FreeBSD$");
 
 struct inoinfo **inphead, **inpsort;	/* info about all inodes */
 
+static int sbhashfailed;
 #define POWEROF2(num)	(((num) & ((num) - 1)) == 0)
 
 static int calcsb(char *dev, int devfd, struct fs *fs);
@@ -74,39 +75,15 @@ static int chkrecovery(int devfd);
 int
 setup(char *dev)
 {
-	long cg, bmapsize;
-	struct fs proto;
+	long bmapsize;
 
 	/*
-	 * We are expected to have an open file descriptor
+	 * We are expected to have an open file descriptor and a superblock.
 	 */
-	if (fsreadfd < 0)
+	if (fsreadfd < 0 || havesb == 0) {
+		if (debug)
+			printf("setup: bad fsreadfd or missing superblock\n");
 		return (0);
-	/*
-	 * If we do not yet have a superblock, read it in looking
-	 * for alternates if necessary.
-	 */
-	if (havesb == 0 && readsb(1) == 0) {
-		skipclean = 0;
-		if (bflag || preen || calcsb(dev, fsreadfd, &proto) == 0)
-			return(0);
-		if (reply("LOOK FOR ALTERNATE SUPERBLOCKS") == 0)
-			return (0);
-		for (cg = 0; cg < proto.fs_ncg; cg++) {
-			bflag = fsbtodb(&proto, cgsblock(&proto, cg));
-			if (readsb(0) != 0)
-				break;
-		}
-		if (cg >= proto.fs_ncg) {
-			printf("SEARCH FOR ALTERNATE SUPER-BLOCK FAILED. "
-			    "YOU MUST USE THE\n-b OPTION TO FSCK TO SPECIFY "
-			    "THE LOCATION OF AN ALTERNATE\nSUPER-BLOCK TO "
-			    "SUPPLY NEEDED INFORMATION; SEE fsck_ffs(8).\n");
-			bflag = 0;
-			return(0);
-		}
-		pwarn("USING ALTERNATE SUPERBLOCK AT %jd\n", bflag);
-		bflag = 0;
 	}
 	if (preen == 0)
 		printf("** %s", dev);
@@ -243,40 +220,64 @@ openfilesys(char *dev)
  * Read in the super block and its summary info.
  */
 int
-readsb(int listerr)
+readsb(void)
 {
-	off_t super;
-	int ret, flags;
 	struct fs *fs;
 
-	super = bflag ? bflag * dev_bsize : UFS_STDSB;
-	flags = sbhashfailed ? UFS_NOHASHFAIL | UFS_NOMSG : UFS_NOMSG;
+	sbhashfailed = 0;
 	readcnt[sblk.b_type]++;
-	while ((ret = sbget(fsreadfd, &fs, super, flags)) != 0) {
-		switch (ret) {
+	/*
+	 * If bflag is given, then check just that superblock.
+	 */
+	if (bflag) {
+		switch (sbget(fsreadfd, &fs, bflag * dev_bsize, UFS_NOMSG)) {
+		case 0:
+			goto goodsb;
 		case EINTEGRITY:
-			if (bflag || (super == UFS_STDSB &&
-			    flags == (UFS_NOHASHFAIL | UFS_NOMSG)))
-				return (0);
-			super = UFS_STDSB;
-			flags = UFS_NOHASHFAIL | UFS_NOMSG;
-			sbhashfailed = 1;
-			continue;
+			printf("Check hash failed for superblock at %jd\n",
+			    bflag);
+			return (0);
 		case ENOENT:
-			if (bflag)
-				printf("%jd is not a file system "
-				    "superblock\n", super / dev_bsize);
-			else
-				printf("Cannot find file system "
-				    "superblock\n");
+			printf("%jd is not a file system superblock\n", bflag);
 			return (0);
 		case EIO:
 		default:
-			printf("I/O error reading %jd\n",
-			    super / dev_bsize);
+			printf("I/O error reading %jd\n", bflag);
 			return (0);
 		}
 	}
+	/*
+	 * Check for the standard superblock and use it if good.
+	 */
+	if (sbget(fsreadfd, &fs, UFS_STDSB, UFS_NOMSG) == 0)
+		goto goodsb;
+	/*
+	 * Check if the only problem is a check-hash failure.
+	 */
+	skipclean = 0;
+	if (sbget(fsreadfd, &fs, UFS_STDSB, UFS_NOMSG | UFS_NOHASHFAIL) == 0) {
+		sbhashfailed = 1;
+		goto goodsb;
+	}
+	/*
+	 * Do an exhaustive search for a usable superblock.
+	 */
+	switch (sbsearch(fsreadfd, &fs, 0)) {
+	case 0:
+		goto goodsb;
+	case ENOENT:
+		printf("SEARCH FOR ALTERNATE SUPER-BLOCK FAILED. "
+		    "YOU MUST USE THE\n-b OPTION TO FSCK TO SPECIFY "
+		    "THE LOCATION OF AN ALTERNATE\nSUPER-BLOCK TO "
+		    "SUPPLY NEEDED INFORMATION; SEE fsck_ffs(8).\n");
+		return (0);
+	case EIO:
+	default:
+		printf("I/O error reading a usable superblock\n");
+		return (0);
+	}
+
+goodsb:
 	memcpy(&sblock, fs, fs->fs_sbsize);
 	free(fs);
 	/*
diff --git a/sbin/fsdb/fsdb.c b/sbin/fsdb/fsdb.c
index c935f88952b4..f9775c7da089 100644
--- a/sbin/fsdb/fsdb.c
+++ b/sbin/fsdb/fsdb.c
@@ -111,7 +111,7 @@ main(int argc, char *argv[])
 		fsys = argv[0];
 
 	sblock_init();
-	if (openfilesys(fsys) == 0 || readsb(0) == 0 || setup(fsys) == 0)
+	if (openfilesys(fsys) == 0 || readsb() == 0 || setup(fsys) == 0)
 		errx(1, "cannot set up file system `%s'", fsys);
 	if (fswritefd < 0)
 		nflag++;
diff --git a/stand/libsa/ufs.c b/stand/libsa/ufs.c
index ef7cb07e75da..65cda349879a 100644
--- a/stand/libsa/ufs.c
+++ b/stand/libsa/ufs.c
@@ -151,7 +151,7 @@ static int	search_directory(char *, struct open_file *, ino_t *);
 static int	ufs_use_sa_read(void *, off_t, void **, int);
 
 /* from ffs_subr.c */
-int	ffs_sbget(void *, struct fs **, off_t, int, char *,
+int	ffs_sbsearch(void *, struct fs **, int, char *,
 	    int (*)(void *, off_t, void **, int));
 
 /*
@@ -529,8 +529,8 @@ ufs_open(const char *upath, struct open_file *f)
 	if (mnt == NULL) {
 		/* read super block */
 		twiddle(1);
-		if ((rc = ffs_sbget(f, &fs, UFS_STDSB, UFS_NOHASHFAIL, "stand",
-		     ufs_use_sa_read)) != 0) {
+		if ((rc = ffs_sbsearch(f, &fs, 0, "stand", ufs_use_sa_read))
+		    != 0) {
 			goto out;
 		}
 	} else {
diff --git a/sys/ufs/ffs/ffs_extern.h b/sys/ufs/ffs/ffs_extern.h
index 54820fc5b3ce..4f17b71dd20f 100644
--- a/sys/ufs/ffs/ffs_extern.h
+++ b/sys/ufs/ffs/ffs_extern.h
@@ -86,6 +86,8 @@ int	ffs_isblock(struct fs *, u_char *, ufs1_daddr_t);
 int	ffs_isfreeblock(struct fs *, u_char *, ufs1_daddr_t);
 void	ffs_oldfscompat_write(struct fs *, struct ufsmount *);
 int	ffs_own_mount(const struct mount *mp);
+int	ffs_sbsearch(void *, struct fs **, int, struct malloc_type *,
+	    int (*)(void *, off_t, void **, int));
 int	ffs_reallocblks(struct vop_reallocblks_args *);
 int	ffs_realloccg(struct inode *, ufs2_daddr_t, ufs2_daddr_t,
 	    ufs2_daddr_t, int, int, int, struct ucred *, struct buf **);
diff --git a/sys/ufs/ffs/ffs_subr.c b/sys/ufs/ffs/ffs_subr.c
index c6043765cfb0..8c081313f873 100644
--- a/sys/ufs/ffs/ffs_subr.c
+++ b/sys/ufs/ffs/ffs_subr.c
@@ -378,6 +378,40 @@ validate_sblock(struct fs *fs, int flags)
 	prtmsg = ((flags & UFS_NOMSG) == 0);
 	warnerr = (flags & UFS_NOWARNFAIL) == UFS_NOWARNFAIL ? 0 : ENOENT;
 	wmsg = warnerr ? "" : " (Ignored)";
+	/*
+	 * If just validating for recovery, then do just the minimal
+	 * checks needed for the superblock fields needed to find
+	 * alternate superblocks.
+	 */
+	if ((flags & UFS_FSRONLY) == UFS_FSRONLY &&
+	    (fs->fs_magic == FS_UFS1_MAGIC || fs->fs_magic == FS_UFS2_MAGIC)) {
+		if (fs->fs_magic == FS_UFS2_MAGIC) {
+			FCHK(fs->fs_sblockloc, !=, SBLOCK_UFS2, %#jx);
+		} else if (fs->fs_magic == FS_UFS1_MAGIC) {
+			FCHK(fs->fs_sblockloc, <, 0, %jd);
+			FCHK(fs->fs_sblockloc, >, SBLOCK_UFS1, %jd);
+		}
+		FCHK(fs->fs_frag, <, 1, %jd);
+		FCHK(fs->fs_frag, >, MAXFRAG, %jd);
+		FCHK(fs->fs_bsize, <, MINBSIZE, %jd);
+		FCHK(fs->fs_bsize, >, MAXBSIZE, %jd);
+		FCHK(fs->fs_bsize, <, roundup(sizeof(struct fs), DEV_BSIZE),
+		    %jd);
+		FCHK(fs->fs_fsize, <, sectorsize, %jd);
+		FCHK(fs->fs_fsize * fs->fs_frag, !=, fs->fs_bsize, %jd);
+		FCHK(powerof2(fs->fs_fsize), ==, 0, %jd);
+		FCHK(fs->fs_fpg, <, 3 * fs->fs_frag, %jd);
+		FCHK(fs->fs_ncg, <, 1, %jd);
+		FCHK(fs->fs_fsbtodb, !=, ILOG2(fs->fs_fsize / sectorsize), %jd);
+		FCHK(fs->fs_old_cgoffset, <, 0, %jd);
+		FCHK2(fs->fs_old_cgoffset, >, 0, ~fs->fs_old_cgmask, <, 0, %jd);
+		FCHK(fs->fs_old_cgoffset * (~fs->fs_old_cgmask), >, fs->fs_fpg,
+		    %jd);
+		FCHK(fs->fs_sblkno, !=, roundup(
+		    howmany(fs->fs_sblockloc + SBLOCKSIZE, fs->fs_fsize),
+		    fs->fs_frag), %jd);
+		return (error);
+	}
 	if (fs->fs_magic == FS_UFS2_MAGIC) {
 		if ((flags & UFS_ALTSBLK) == 0)
 			FCHK2(fs->fs_sblockactualloc, !=, SBLOCK_UFS2,
@@ -530,6 +564,146 @@ validate_sblock(struct fs *fs, int flags)
 	return (error);
 }
 
+/*
+ * Make an extensive search to find a superblock. If the superblock
+ * in the standard place cannot be used, try looking for one of the
+ * backup superblocks.
+ *
+ * Flags are made up of the following or'ed together options:
+ *
+ * UFS_NOMSG indicates that superblock inconsistency error messages
+ *    should not be printed.
+ *
+ * UFS_NOCSUM causes only the superblock itself to be returned, but does
+ *    not read in any auxillary data structures like the cylinder group
+ *    summary information.
+ */
+int
+ffs_sbsearch(void *devfd, struct fs **fsp, int reqflags,
+    struct malloc_type *filltype,
+    int (*readfunc)(void *devfd, off_t loc, void **bufp, int size))
+{
+	struct fsrecovery *fsr;
+	struct fs *protofs;
+	void *fsrbuf;
+	char *cp;
+	long nocsum, flags, msg, cg;
+	off_t sblk, secsize;
+	int error;
+
+	msg = (reqflags & UFS_NOMSG) == 0;
+	nocsum = reqflags & UFS_NOCSUM;
+	/*
+	 * Try normal superblock read and return it if it works.
+	 *
+	 * Suppress messages if it fails until we find out if
+	 * failure can be avoided.
+	 */
+	flags = UFS_NOMSG | nocsum;
+	if (ffs_sbget(devfd, fsp, UFS_STDSB, flags, filltype, readfunc) == 0)
+		return (0);
+	/*
+	 * First try: ignoring hash failures.
+	 */
+	flags |= UFS_NOHASHFAIL;
+	if (msg)
+		flags &= ~UFS_NOMSG;
+	if (ffs_sbget(devfd, fsp, UFS_STDSB, flags, filltype, readfunc) == 0)
+		return (0);
+	/*
+	 * Next up is to check if fields of the superblock that are
+	 * needed to find backup superblocks are usable.
+	 */
+	if (msg)
+		printf("Attempted recovery for standard superblock: failed\n");
+	flags = UFS_FSRONLY | UFS_NOHASHFAIL | UFS_NOMSG;
+	if (ffs_sbget(devfd, &protofs, UFS_STDSB, flags, filltype,
+	    readfunc) == 0) {
+		if (msg)
+			printf("Attempted extraction of recovery data from "
+			    "standard superblock: ");
+	} else {
+		/*
+		 * Final desperation is to see if alternate superblock
+		 * parameters have been saved in the boot area.
+		 */
+		if (msg)
+			printf("Attempted extraction of recovery data from "
+			    "standard superblock: failed\nAttempt to find "
+			    "boot zone recovery data: ");
+		/*
+		 * Look to see if recovery information has been saved.
+		 * If so we can generate a prototype superblock based
+		 * on that information.
+		 *
+		 * We need fragments-per-group, number of cylinder groups,
+		 * location of the superblock within the cylinder group, and
+		 * the conversion from filesystem fragments to disk blocks.
+		 *
+		 * When building a UFS2 filesystem, newfs(8) stores these
+		 * details at the end of the boot block area at the start
+		 * of the filesystem partition. If they have been overwritten
+		 * by a boot block, we fail.  But usually they are there
+		 * and we can use them.
+		 *
+		 * We could ask the underlying device for its sector size,
+		 * but some devices lie. So we just try a plausible range.
+		 */
+		error = ENOENT;
+		for (secsize = dbtob(1); secsize <= SBLOCKSIZE; secsize *= 2)
+			if ((error = (*readfunc)(devfd, (SBLOCK_UFS2 - secsize),
+			    &fsrbuf, secsize)) == 0)
+				break;
+		if (error != 0)
+			goto trynowarn;
+		cp = fsrbuf; /* type change to keep compiler happy */
+		fsr = (struct fsrecovery *)&cp[secsize - sizeof *fsr];
+		if (fsr->fsr_magic != FS_UFS2_MAGIC ||
+		    (protofs = UFS_MALLOC(SBLOCKSIZE, filltype, M_NOWAIT))
+		    == NULL) {
+			UFS_FREE(fsrbuf, filltype);
+			goto trynowarn;
+		}
+		memset(protofs, 0, sizeof(struct fs));
+		protofs->fs_fpg = fsr->fsr_fpg;
+		protofs->fs_fsbtodb = fsr->fsr_fsbtodb;
+		protofs->fs_sblkno = fsr->fsr_sblkno;
+		protofs->fs_magic = fsr->fsr_magic;
+		protofs->fs_ncg = fsr->fsr_ncg;
+		UFS_FREE(fsrbuf, filltype);
+	}
+	/*
+	 * Scan looking for alternative superblocks.
+	 */
+	for (cg = 0; cg < protofs->fs_ncg; cg++) {
+		sblk = dbtob(fsbtodb(protofs, cgsblock(protofs, cg)));
+		if (ffs_sbget(devfd, fsp, sblk, UFS_NOMSG | nocsum, filltype,
+		    readfunc) == 0) {
+			if (msg)
+				printf("succeeded with alternate superblock "
+				    "at %jd\n", (intmax_t)btodb(sblk));
+			UFS_FREE(protofs, filltype);
+			return (0);
+		}
+	}
+	UFS_FREE(protofs, filltype);
+	/*
+	 * Our alternate superblock strategies failed. Our last ditch effort
+	 * is to see if the standard superblock has only non-critical errors.
+	 */
+trynowarn:
+	flags = UFS_NOWARNFAIL | UFS_NOMSG | nocsum;
+	if (msg) {
+		printf("failed\n");
+		flags &= ~UFS_NOMSG;
+	}
+	if (ffs_sbget(devfd, fsp, UFS_STDSB, flags, filltype, readfunc) != 0)
+		return (ENOENT);
+	if (msg)
+		printf("Using standard superblock with non-critical errors.\n");
+	return (0);
+}
+
 /*
  * Write a superblock to the devfd device from the memory pointed to by fs.
  * Write out the superblock summary information if it is present.
diff --git a/sys/ufs/ffs/ffs_vfsops.c b/sys/ufs/ffs/ffs_vfsops.c
index 2944d0c5077d..38d5be895318 100644
--- a/sys/ufs/ffs/ffs_vfsops.c
+++ b/sys/ufs/ffs/ffs_vfsops.c
@@ -913,8 +913,7 @@ ffs_mountfs(struct vnode *odevvp, struct mount *mp, struct thread *td)
 	struct g_consumer *cp;
 	struct mount *nmp;
 	struct vnode *devvp;
-	int candelete, canspeedup, flags;
-	off_t loc;
+	int candelete, canspeedup;
 
 	fs = NULL;
 	ump = NULL;
@@ -958,12 +957,12 @@ ffs_mountfs(struct vnode *odevvp, struct mount *mp, struct thread *td)
 		goto out;
 	}
 	/* fetch the superblock and summary information */
-	loc = UFS_STDSB;
-	flags = 0;
 	if ((mp->mnt_flag & (MNT_ROOTFS | MNT_FORCE)) != 0)
-		flags = UFS_NOHASHFAIL;
-	if ((error = ffs_sbget(devvp, &fs, loc, flags, M_UFSMNT, ffs_use_bread))
-	    != 0)
+		error = ffs_sbsearch(devvp, &fs, 0, M_UFSMNT, ffs_use_bread);
+	else
+		error = ffs_sbget(devvp, &fs, UFS_STDSB, 0, M_UFSMNT,
+		    ffs_use_bread);
+	if (error != 0)
 		goto out;
 	fs->fs_flags &= ~FS_UNCLEAN;
 	if (fs->fs_clean == 0) {
diff --git a/sys/ufs/ffs/fs.h b/sys/ufs/ffs/fs.h
index 0a546e40e82a..bd2b9157e467 100644
--- a/sys/ufs/ffs/fs.h
+++ b/sys/ufs/ffs/fs.h
@@ -102,11 +102,19 @@
  * UFS_NOWARNFAIL will warn about inconsistencies but still return the
  *    superblock. It includes UFS_NOHASHFAIL. UFS_NOWARNFAIL is used by
  *    programs like fsck_ffs(8) to debug broken filesystems.
+ *
+ * UFS_FSRONLY will only validate the superblock fields needed to
+ *    calculate where the backup filesystem superblocks are located.
+ *    If these values pass their validation tests, then the superblock
+ *    is returned. This flag is used as part of the attempt to find
+ *    alternate superblocks when using ffs_sbsearch().
  */
 #define	UFS_NOHASHFAIL	0x0001	/* Ignore check-hash failure */
 #define	UFS_NOWARNFAIL	0x0003	/* Ignore non-fatal inconsistencies */
 #define	UFS_NOMSG	0x0004	/* Print no error message */
 #define	UFS_NOCSUM	0x0008	/* Read just the superblock without csum */
+#define	UFS_FSRONLY	0x0010	/* Validate only values needed for recovery
+				   of alternate superblocks */
 #define	UFS_ALTSBLK	0x1000	/* Flag used internally */
 
 /*
diff --git a/tools/diag/prtblknos/main.c b/tools/diag/prtblknos/main.c
index 25a717760922..65efa786b700 100644
--- a/tools/diag/prtblknos/main.c
+++ b/tools/diag/prtblknos/main.c
@@ -56,7 +56,6 @@ main(argc, argv)
 	char ibuf[64];
 	char *fsname, *filename;
 	ino_t inonum;
-	int error;
 
 	filename = NULL;
 	if (argc == 2) {
@@ -83,7 +82,8 @@ main(argc, argv)
 	fsname = *++argv;
 
 	/* get the superblock. */
-	if ((error = ufs_disk_fillout(&disk, fsname)) < 0)
+	if (ufs_disk_fillout_blank(&disk, fsname) == -1 ||
+	    sbfind(&disk, 0) == -1)
 		err(1, "Cannot access file system superblock on %s", fsname);
 	fs = (struct fs *)&disk.d_sb;
 
@@ -99,7 +99,7 @@ main(argc, argv)
 			(void)printf("%s (inode #%jd): ", filename,
 			    (intmax_t)inonum);
 
-		if ((error = getinode(&disk, &dp, inonum)) < 0)
+		if (getinode(&disk, &dp, inonum) < 0)
 			warn("Read of inode %jd on %s failed: %s",
 			    (intmax_t)inonum, fsname, disk.d_error);