scan_ffs, take 2

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

scan_ffs, take 2

gwes-2
This is an update to scan_ffs to (a) identify UFS2 superblocks
and (b) output a great deal of information about what it finds.

It distinguishes primary superblocks from alternate ones.
It does its best to give enough information to untangle
good partitions from remnants of obsolete partitions.
It deduces enough information from alternate superblocks
to recreate a disklabel even if the primany superblock is absent.

If alternate superblocks carried their own logical block
number as a back-reference this program would be a lot simpler.

It works on the disks on my systems which are
either 6.3 or 6.4 amd64 with both UFS1 and UFS2 partitions.
Test data (voluminous) available on request.

geoff steckel


Index: scan_ffs.c
===================================================================
RCS file: /cvs/src/sbin/scan_ffs/scan_ffs.c,v
retrieving revision 1.21
diff -u -p -u -r1.21 scan_ffs.c
--- scan_ffs.c 23 Nov 2015 19:19:30 -0000 1.21
+++ scan_ffs.c 17 Feb 2019 02:07:28 -0000
@@ -1,7 +1,6 @@
-/* $OpenBSD: scan_ffs.c,v 1.21 2015/11/23 19:19:30 deraadt Exp $ */
-
 /*
  * Copyright (c) 1998 Niklas Hallqvist, Tobias Weingartner
+ * Copyright (c) 2019 Geoff Steckel
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -25,6 +24,7 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <sys/param.h>
 #include <sys/types.h>
 #include <sys/fcntl.h>
 #include <ufs/ffs/fs.h>
@@ -37,95 +37,188 @@
 #include <err.h>
 #include <util.h>
 
-#define SBCOUNT 64 /* XXX - Should be configurable */
+#define RDCOUNT 64 /* XXX - Should be configurable */
 
 /* Flags to control ourselves... */
 #define FLAG_VERBOSE 1
 #define FLAG_SMART 2
 #define FLAG_LABELS 4
 
+#define is_ufs1(fs) ((fs)->fs_magic == FS_UFS1_MAGIC)
+#define is_ufs2(fs) ((fs)->fs_magic == FS_UFS2_MAGIC)
+
+#define x_strof(x) #x
+#define s_strof(y) x_strof(y)
+#define S_MAXMNTLEN s_strof(MAXMNTLEN)
+
+#define NFSIDS 20
+
+struct fsident {
+ int32_t fsi_id[2]; /* id from 1st sb seen */
+ off_t fsi_end; /* last block for this sb */
+ off_t fsi_beg; /* base for this sb */
+ char fsi_mnt[MAXMNTLEN + 1]; /* name of last mount */
+} fsidtab[NFSIDS];
+
+int nfsident ;
+
 static void usage(void);
+static void ffs1_compat_read(struct fs *);
+static int is_sblock(const struct fs *);
+static int is_cg(const struct cg *);
+static int ufsscan(daddr_t, daddr_t);
+static daddr_t check_block(struct fs *, daddr_t);
+static int try_find_base(struct fs *, daddr_t, daddr_t *);
+static struct fsident *find_end_id(daddr_t, int *);
+static int find_enter_id(struct fs *, daddr_t, daddr_t);
+static void remove_id(struct fsident *);
+
+int flags;
+int disk_fd;
 
 static int
-ufsscan(int fd, daddr_t beg, daddr_t end, int flags)
+ufsscan(daddr_t beg_blkno, daddr_t end_blkno)
 {
- static char lastmount[MAXMNTLEN];
- static u_int8_t buf[SBSIZE * SBCOUNT];
+ static u_int8_t buf[SBSIZE * (RDCOUNT + 1)];
  struct fs *sb;
- daddr_t blk, lastblk;
+ daddr_t blk;
  int n;
+ ssize_t nread;
+ daddr_t blkno;
+ daddr_t jump;
 
- lastblk = -1;
- memset(lastmount, 0, MAXMNTLEN);
-
- for (blk = beg; blk <= ((end<0)?blk:end); blk += (SBCOUNT*SBSIZE/512)){
- memset(buf, 0, SBSIZE * SBCOUNT);
- if (lseek(fd, (off_t)blk * 512, SEEK_SET) < 0)
+ for (blk = beg_blkno; blk <= end_blkno; blk += (RDCOUNT * SBSIZE / DEV_BSIZE)) {
+ if (lseek(disk_fd, (off_t)blk * DEV_BSIZE, SEEK_SET) < 0)
     err(1, "lseek");
- if (read(fd, buf, SBSIZE * SBCOUNT) < 0)
+ /* read 1 sblock-size extra to prevent running off end of buf */
+ nread = read(disk_fd, buf, SBSIZE * (RDCOUNT + 1)) ;
+ if (nread == -1)
  err(1, "read");
+ nread -= SBSIZE;
 
- for (n = 0; n < (SBSIZE * SBCOUNT); n += 512){
- sb = (struct fs*)(&buf[n]);
- if (sb->fs_magic == FS_MAGIC) {
- if (flags & FLAG_VERBOSE)
- printf("block %lld id %x,%x size %d\n",
-    (long long)(blk + (n/512)),
-    sb->fs_id[0], sb->fs_id[1],
-    sb->fs_ffs1_size);
-
- if (((blk+(n/512)) - lastblk) == (SBSIZE/512)) {
- if (flags & FLAG_LABELS ) {
- printf("X: %lld %lld 4.2BSD %d %d %d # %s\n",
-    ((off_t)sb->fs_ffs1_size *
-    sb->fs_fsize / 512),
-    (long long)(blk + (n/512) -
-    (2*SBSIZE/512)),
-    sb->fs_fsize, sb->fs_bsize,
-    sb->fs_cpg, lastmount);
- } else {
- /* XXX 2038 */
- time_t t = sb->fs_ffs1_time;
-
- printf("ffs at %lld size %lld "
-    "mount %s time %s",
-    (long long)(blk+(n/512) -
-    (2*SBSIZE/512)),
-    (long long)(off_t)sb->fs_ffs1_size *
-    sb->fs_fsize,
-    lastmount, ctime(&t));
- }
-
- if (flags & FLAG_SMART) {
- off_t size = (off_t)sb->fs_ffs1_size *
-    sb->fs_fsize;
-
- if ((n + size) < (SBSIZE * SBCOUNT))
- n += size;
- else {
- blk += (size/512 -
-    (SBCOUNT*SBCOUNT));
- break;
- }
- }
- }
-
- /* Update last potential FS SBs seen */
- lastblk = blk + (n/512);
- memcpy(lastmount, sb->fs_fsmnt, MAXMNTLEN);
+ for (n = 0; n < nread; n += DEV_BSIZE) {
+ blkno = blk + n / DEV_BSIZE;
+ sb = (struct fs *)(&buf[n]);
+ jump = check_block(sb, blkno);
+ if (jump) {
+    blk = jump;
+    break;
  }
  }
+ if (nread != RDCOUNT * SBSIZE)
+ break;
  }
  return(0);
 }
 
+static daddr_t
+check_block(struct fs *sb, daddr_t blk)
+{
+ daddr_t filesys_base; /* in DEV_BSIZE blocks */
+ daddr_t filesys_size; /* in DEV_BSIZE blocks */
+ char hsizebuf[FMT_SCALED_STRSIZE + 1];
+ int v;
+ int i;
+ int ndeep;
+ struct fsident *fsid;
+
+ while ((fsid = find_end_id(blk, &ndeep))) {
+ for (i = 0; i < ndeep; i++)
+ printf("  ");
+ printf("end of %x.%x %s @ %lld = %lld + %lld\n",
+ fsid->fsi_id[0], fsid->fsi_id[1],
+ fsid->fsi_mnt, blk, fsid->fsi_beg,
+ fsid->fsi_end - fsid->fsi_beg);
+ remove_id(fsid);
+ }
+ if ( ! is_sblock(sb))
+ return(0);
+ ffs1_compat_read(sb);
+ v = try_find_base(sb, blk, &filesys_base);
+ filesys_size = ((off_t)sb->fs_size * sb->fs_fsize / DEV_BSIZE);
+ if (v == -1) {
+ if (flags & FLAG_VERBOSE)
+ printf("orphan superblock: ");
+ else
+ return(0);
+ }
+ ndeep = find_enter_id(sb, filesys_base, filesys_base + filesys_size);
+ if (! (flags & FLAG_LABELS))
+ for (i = 0; i < ndeep; i++)
+ printf("  ");
+ if (v > 0)
+ printf("alt sb #%d: ", v - 1);
+ if (flags & FLAG_LABELS ) {
+ printf("X: %lld %lld 4.2BSD %d %d %d # "
+    "%." S_MAXMNTLEN "s\n",
+    filesys_size,
+    filesys_base,
+    sb->fs_fsize, sb->fs_bsize,
+    sb->fs_cpg, sb->fs_fsmnt);
+ } else {
+ fmt_scaled(filesys_size * DEV_BSIZE, hsizebuf);
+ printf("ffs at %lld size %lld (%s) %dK/%dK ",
+    filesys_base, filesys_size, hsizebuf,
+    sb->fs_fsize / 1024, sb->fs_bsize / 1024);
+ if (sb->fs_fsmnt[0] != '\0')
+ printf("mount %." S_MAXMNTLEN "s ", sb->fs_fsmnt);
+ printf("id %x.%x  time %s",
+    sb->fs_id[0], sb->fs_id[1], ctime(&sb->fs_time));
+ }
+
+ if (v != -1 && (flags & FLAG_SMART)) {
+ blk = filesys_base + filesys_size -
+    (RDCOUNT * SBSIZE / DEV_BSIZE);
+ return(blk);
+ }
+ return(0);
+}
+
+static int
+try_find_base(struct fs *fs, daddr_t blk, daddr_t *baseptr)
+{
+ static char megabuf[SBSIZE];
+ size_t readsize;
+ size_t nread;
+ off_t start;
+ struct cg *cg;
+ int cgno;
+ daddr_t fsblk_this;
+
+ start = fsbtodb(fs, cgsblock(fs, 0)) * DEV_BSIZE - fs->fs_sblockloc ;
+
+ readsize = SBSIZE;
+ if (lseek(disk_fd, (off_t) blk * DEV_BSIZE + start, SEEK_SET) == -1)
+ err(1, "fseek cg/sb");
+ nread = read(disk_fd, megabuf, readsize);
+ if (nread != readsize)
+ err(1, "read sb for sb");
+ if (is_ufs1((struct fs *) &megabuf) || is_ufs2((struct fs *) &megabuf)) {
+ *baseptr = blk - fs->fs_sblockloc / DEV_BSIZE;
+ return(0);
+ }
+ start = (fs->fs_bsize > SBSIZE ? fs->fs_bsize : SBSIZE) + blk * DEV_BSIZE;
+ if (lseek(disk_fd, start, SEEK_SET) == -1)
+ err(1, "fseek cg/sb");
+ nread = read(disk_fd, megabuf, readsize);
+ if (nread != readsize)
+ err(1, "read cg for sb");
+ cg = (struct cg *) &megabuf;
+ if (is_cg(cg)) {
+ cgno = cg->cg_cgx;
+ fsblk_this = fsbtodb(fs, cgsblock(fs, cgno));
+ *baseptr = blk - fsblk_this ;
+ return(cgno + 1);
+ }
+ return(-1);
+}
 
 static void
 usage(void)
 {
  extern char *__progname;
 
- fprintf(stderr, "usage: %s [-lsv] [-b begin] [-e end] device\n",
+ fprintf(stderr, "usage: %s [-lsv] [-b begin_blockno] [-e end_blockno] device\n",
     __progname);
  exit(1);
 }
@@ -134,13 +227,15 @@ usage(void)
 int
 main(int argc, char *argv[])
 {
- int ch, fd, flags = 0;
- daddr_t beg = 0, end = -1;
+ int ch;
+ daddr_t beg = 0;
+ daddr_t end = LLONG_MAX ;
  const char *errstr;
 
  if (pledge("stdio rpath disklabel", NULL) == -1)
  err(1, "pledge");
 
+ flags = 0;
  while ((ch = getopt(argc, argv, "lsvb:e:")) != -1)
  switch(ch) {
  case 'b':
@@ -172,12 +267,124 @@ main(int argc, char *argv[])
  if (argc != 1)
  usage();
 
- fd = opendev(argv[0], O_RDONLY, OPENDEV_PART, NULL);
- if (fd < 0)
+ disk_fd = opendev(argv[0], O_RDONLY, OPENDEV_PART, NULL);
+ if (disk_fd == -1)
  err(1, "%s", argv[0]);
 
  if (pledge("stdio", NULL) == -1)
  err(1, "pledge");
 
- return (ufsscan(fd, beg, end, flags));
+ return (ufsscan(beg, end));
+}
+
+/*
+ * Auxiliary function for reading FFS1 super blocks.
+ * from ufs_vfsops.c
+ */
+void
+ffs1_compat_read(struct fs *fs)
+{
+ if (fs->fs_magic == FS_UFS2_MAGIC)
+ return; /* UFS2 */
+ if (fs->fs_sblockloc == 0)
+ fs->fs_sblockloc = SBLOCK_UFS1;
+ fs->fs_flags = fs->fs_ffs1_flags;
+ fs->fs_maxbsize = fs->fs_bsize;
+ fs->fs_time = fs->fs_ffs1_time;
+ fs->fs_size = fs->fs_ffs1_size;
+ fs->fs_dsize = fs->fs_ffs1_dsize;
+ fs->fs_csaddr = fs->fs_ffs1_csaddr;
+ fs->fs_cstotal.cs_ndir = fs->fs_ffs1_cstotal.cs_ndir;
+ fs->fs_cstotal.cs_nbfree = fs->fs_ffs1_cstotal.cs_nbfree;
+ fs->fs_cstotal.cs_nifree = fs->fs_ffs1_cstotal.cs_nifree;
+ fs->fs_cstotal.cs_nffree = fs->fs_ffs1_cstotal.cs_nffree;
+}
+
+#define IS_POWER_OF_2(x) (((x) & -(x)) == (x))
+
+/*
+ * compromise validation of superblock contents
+ */
+
+static int
+is_sblock(const struct fs *fs)
+{
+ if ( ! is_ufs1(fs) && ! is_ufs2(fs))
+ return(0);
+ switch (fs->fs_frag) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ break;
+ default:
+ return(0);
+ }
+ if (fs->fs_bsize > MAXBSIZE || fs->fs_bsize < MINBSIZE ||
+    ! IS_POWER_OF_2(fs->fs_bsize))
+ return(0);
+ if (fs->fs_fsize * fs->fs_frag != fs->fs_bsize)
+ return(0);
+ return(1);
+}
+
+static int
+is_cg(const struct cg *cg)
+{
+ if (cg->cg_magic != CG_MAGIC)
+ return(0);
+ if (cg->cg_cgx > 10000 || cg->cg_cgx < 0)
+ return(0);
+ if (cg->cg_ncyl < 0)
+ return(0);
+ return(1);
+}
+
+static int
+find_enter_id(struct fs *fs, daddr_t beg, daddr_t end) {
+ int i ;
+
+ for (i = 0; i < nfsident; i++) {
+ if (fs->fs_id[0] == fsidtab[i].fsi_id[0] &&
+    fs->fs_id[1] == fsidtab[i].fsi_id[1])
+ return(i);
+ }
+ fsidtab[i].fsi_id[0] = fs->fs_id[0];
+ fsidtab[i].fsi_id[1] = fs->fs_id[1];
+ fsidtab[i].fsi_beg = beg;
+ fsidtab[i].fsi_end = end;
+ strlcpy(fsidtab[i].fsi_mnt, fs->fs_fsmnt, sizeof fsidtab[0].fsi_mnt);
+ nfsident++;
+ return(i);
+}
+
+static struct fsident *
+find_end_id(daddr_t blk, int *np) {
+ int i;
+
+ for (i = 0; i < nfsident; i++) {
+ if (blk == fsidtab[i].fsi_end)
+ {
+ *np = i;
+ return(&fsidtab[i]);
+ }
+ }
+ return(0);
+}
+
+static void
+remove_id(struct fsident *fid) {
+ int i;
+
+ for (i = 0; i < nfsident; i++)
+ if (fid == &fsidtab[i])
+ break;
+ if (i == nfsident) {
+ printf("internal error - remove_id not found\n");
+ return;
+ }
+ if (i != nfsident - 1)
+ memmove(&fsidtab[i], &fsidtab[i + 1],
+    (nfsident - i - 1) * sizeof (fsidtab[0]));
+ --nfsident;
 }




Index: scan_ffs.8
===================================================================
RCS file: /cvs/src/sbin/scan_ffs/scan_ffs.8,v
retrieving revision 1.16
diff -u -p -u -r1.16 scan_ffs.8
--- scan_ffs.8 23 Mar 2008 23:28:46 -0000 1.16
+++ scan_ffs.8 17 Feb 2019 03:01:51 -0000
@@ -42,8 +42,8 @@ If you have ever been working too long,
 and just happened to type 'disklabel -w sd0 floppy', instead of 'disklabel
 -w fd0 floppy', you know what I am talking about.
 .Pp
-This little program will take a raw disk device (which you might have to
-create) that covers the whole disk, and finds all probable UFS/FFS partitions
+This little program will take a raw disk device
+and finds all probable UFS/FFS partitions
 on the disk.
 It has various options to make it go faster, and to print out
 information to help in the reconstruction of the disklabel.
@@ -83,7 +83,10 @@ to be verbose about what it is doing, an
 This specifies which device
 .Nm
 should use to scan for filesystems.
-Usually this device should cover the whole disk in question.
+Normally this would be the
+.Ar c
+partition which is always present even if there is
+no disklabel.
 .El
 .Pp
 The basic operation of this program is as follows:
@@ -103,17 +106,6 @@ printouts, backups
 screendumps, and whatever other method you can think of.
 The more information you have, the better your chances are in recovering the
 disklabel of the disk.
-.It
-Create a disklabel on the affected disk, which covers the whole disk, and has
-at least one partition which covers the whole disk.
-As the
-.Dq c
-partition
-usually covers the whole disk anyways, this sounds like a good place to start.
-.It
-Run
-.Nm
-over this partition.
 If you have any information about the disklabel
 which used to exist on the disk, keep that in mind while
 .Nm
@@ -133,9 +125,48 @@ I've seen them reconstructed after some
 awesome fumbles.
 If you can't have backups, at least have funky tools to help
 you out of a jam when they happen.
+.Pp
+.Nm
+tentatively identifies superblocks by their magic number
+and checks that the block size, fragment size,
+and number of fragments are sane.
+.Pp
+Primary superblocks are followed by alternate superblock #0.
+The start of the file system is known from their position.
+They are likely to have a "last mount" value.
+.Pp
+Alternate superblocks are followed by a cylinder group.
+The start of the file system is calculated from the position
+of the cylinder group, the number of the cylinder group,
+and information in the superblock.
+They do not have a "last mount" value.
+.Pp
+If neither of these are true, the block is an orphan.
+It is unlikely to be a valid superblock.
+.Pp
+In normal mode (without
+.Fl s
+)
+.Nm
+outputs data about primary and alternate superblocks in
+exhaustive detail.
+.Pp
+Size, date and mount point on primary superblocks are useful clues.
+On a disk where only the disklabel has been damaged,
+.Fl s
+works well.
+The less damage done to the disk metadata, the better
+job
+.Nm
+does.
+.Sh CAVEAT
+.Pp
+Beware information about obsolete file systems
+which apparently overlap good file systems.
 .Sh SEE ALSO
 .Xr disklabel 8
 .Sh BUGS
-It is not perfect, and could do a lot more things with date/time information
-in the superblocks it finds, but this program has saved more than one butt,
+It is not perfect.
+Much more could be done interpreting the superblocks it finds,
+but this program has saved more than one butt,
 more than once.