/* * recover: recover blown disk format. * Greg Lehey, 16 August 2002 * * $Id: recover.c,v 1.2 2002/08/29 05:18:20 grog Exp $ */ #define DKTYPENAMES /* to get some stuff from disklabel.h */ #include #include #include #include #include #include #include #include #include #include #include /* Header files shamelessly ripped out of the kernel */ #include #include #include #include int verbose; int quick; /* jump from one table to the next */ char *specialname; enum { BIG = MAXPHYS, KILOBYTE = 1024, MEGABYTE = 1048576, GIGABYTE = 1073741824 }; int64_t diskoffset; /* build up our partition list here */ struct { int64_t offset; int64_t length; int fstype; /* from fs_type field */ } partition [32]; int partitions; /* number of partitions */ unsigned char diskblock [BIG + 1]; int errno; void usage (char *name) { printf ("Usage:\n" "%s [-s ] [-v] [-q] special\n" "\t-s : Start searching at disk address \n" "\t-v: Verbose\n" "\t-q: quick (skip contents of found disk labels)\n", name ); exit (1); } int printable (char c) { if (isprint (c)) return c; return '~'; } char *roughlength (int64_t bytes, int lj) { static char description [16]; if (bytes > (int64_t) MEGABYTE * 10000) /* gigabytes */ sprintf (description, lj? "%lld GB": "%10lld GB", bytes / GIGABYTE); else if (bytes > KILOBYTE * 10000) /* megabytes */ sprintf (description, lj? "%lld MB": "%10lld MB", bytes / MEGABYTE); else if (bytes > 10000) /* kilobytes */ sprintf (description, lj? "%lld kB": "%10lld kB", bytes / KILOBYTE); else /* bytes */ sprintf (description, lj? "%lld B": "%10lld B", bytes); return description; } /* Stolen from disklabel(8) */ void display (const struct disklabel *lp) { int i, j; const struct partition *pp; if ((unsigned) lp->d_type < DKMAXTYPES) printf ("type: %s\n", dktypenames [lp->d_type]); else printf ("type: %u\n", lp->d_type); printf ("disk: %.*s\n", (int)sizeof (lp->d_typename), lp->d_typename); printf ("label: %.*s\n", (int)sizeof (lp->d_packname), lp->d_packname); printf ("flags:"); if (lp->d_flags & D_REMOVABLE) printf (" removeable"); if (lp->d_flags & D_ECC) printf (" ecc"); if (lp->d_flags & D_BADSECT) printf (" badsect"); printf ("\n"); printf ("bytes/sector: %lu\n", (u_long)lp->d_secsize); printf ("sectors/track: %lu\n", (u_long)lp->d_nsectors); printf ("tracks/cylinder: %lu\n", (u_long)lp->d_ntracks); printf ("sectors/cylinder: %lu\n", (u_long)lp->d_secpercyl); printf ("cylinders: %lu\n", (u_long)lp->d_ncylinders); printf ("sectors/unit: %lu\n", (u_long)lp->d_secperunit); printf ("rpm: %u\n", lp->d_rpm); printf ("interleave: %u\n", lp->d_interleave); printf ("trackskew: %u\n", lp->d_trackskew); printf ("cylinderskew: %u\n", lp->d_cylskew); printf ("headswitch: %lu\t\t# milliseconds\n", (u_long)lp->d_headswitch); printf ("track-to-track seek: %ld\t# milliseconds\n", (u_long)lp->d_trkseek); printf ("drivedata: "); for (i = NDDATA - 1; i >= 0; i--) if (lp->d_drivedata [i]) break; if (i < 0) i = 0; for (j = 0; j <= i; j++) printf ("%lu ", (u_long)lp->d_drivedata [j]); printf ("\n\n%u partitions:\n", lp->d_npartitions); printf ("# size offset fstype [fsize bsize bps/cpg]\n"); pp = lp->d_partitions; for (i = 0; i < lp->d_npartitions; i++, pp++) { if (pp->p_size) { printf (" %c: %8lu %8lu ", 'a' + i, (u_long)pp->p_size, (u_long)pp->p_offset); if ((unsigned) pp->p_fstype < FSMAXTYPES) printf ("%8.8s", fstypenames [pp->p_fstype]); else printf ("%8d", pp->p_fstype); switch (pp->p_fstype) { case FS_UNUSED: /* XXX */ printf (" %5lu %5lu %5.5s ", (u_long)pp->p_fsize, (u_long) (pp->p_fsize * pp->p_frag), ""); break; case FS_BSDFFS: printf (" %5lu %5lu %5u ", (u_long)pp->p_fsize, (u_long) (pp->p_fsize * pp->p_frag), pp->p_cpg); break; case FS_BSDLFS: printf (" %5lu %5lu %5d", (u_long)pp->p_fsize, (u_long) (pp->p_fsize * pp->p_frag), pp->p_cpg); break; default: printf ("%20.20s", ""); break; } printf ("\t# (Cyl. %4lu", (u_long) (pp->p_offset / lp->d_secpercyl)); if (pp->p_offset % lp->d_secpercyl) putc ('*', stdout); else putc (' ', stdout); printf ("- %lu", (u_long) ((pp->p_offset + pp->p_size + lp->d_secpercyl - 1) / lp->d_secpercyl - 1)); if (pp->p_size % lp->d_secpercyl) putc ('*', stdout); printf (")\n"); } } } /* * Take a number with an optional scale factor and convert * it to a number of bytes. * * The scale factors are: * * s sectors (of 512 bytes) * b blocks (of 512 bytes). This unit is deprecated, * because it's confusing, but maintained to avoid * confusing Veritas users. * k kilobytes (1024 bytes) * m megabytes (of 1024 * 1024 bytes) * g gigabytes (of 1024 * 1024 * 1024 bytes) */ u_int64_t sizespec(char *spec) { u_int64_t size; char *s; int sign = 1; /* -1 if negative */ size = 0; if (spec != NULL) { /* we have a parameter */ s = spec; if (*s == '-') { /* negative, */ sign = -1; s++; /* skip */ } if ((*s >= '0') && (*s <= '9')) { /* it's numeric */ while ((*s >= '0') && (*s <= '9')) /* it's numeric */ size = size * 10 + *s++ - '0'; /* convert it */ switch (*s) { case '\0': return size * sign; case 'B': case 'b': case 'S': case 's': return size * sign * 512; case 'K': case 'k': return size * sign * 1024; case 'M': case 'm': return size * sign * 1024 * 1024; case 'G': case 'g': return size * sign * 1024 * 1024 * 1024; } } fprintf(stderr, "Invalid length specification: %s", spec); exit (1); } fprintf(stderr, "Missing length specification"); exit (1); return 0; } int main (int argc, char *argv []) { int special; int count; int show_usage = 0; int i; int64_t diskoffset = 0; int64_t block; for (i = 1; i < argc; i++) { if (argv [i] [0] == '-') /* option */ switch (argv [i] [1]) { case 'q': /* quick scan */ quick = 1; break; case 's': if (argv [i] [2]) /* arg supplied */ diskoffset = sizespec (&argv [i] [2]); else if (i == argc - 1) /* last arg */ show_usage = 1; else { i++; diskoffset = sizespec (argv [i]); } break; case 'v': if (argv [i] [2]) /* arg supplied */ verbose = atoi (&argv [i] [2]); else verbose = 1; break; default: printf ("Unknown flag: %s\n", argv [i]); show_usage = 1; } else if (specialname) { printf ("Too many arguments: %s\n", argv [i]); show_usage = 1; } else specialname = argv [i]; } if (show_usage || (! specialname)) usage (argv [0]); special = open (specialname, O_RDONLY, 0); if (special < 0) { printf ("Can't open %s: %s (%d)\n", argv [1], strerror (errno), errno ); return 1; } if (diskoffset /* don't start at beginning */ && (lseek (special, diskoffset, SEEK_SET) < 0) ) { fprintf (stderr, "Can't seek to %lld: %s (%d)\n", diskoffset, strerror (errno), errno ); return 1; } while (1) { count = read (special, diskblock, BIG); if (count < 0) { printf ("Can't read %s at offset %qd: %s (%d)\n", argv [4], diskoffset, strerror (errno), errno); if (errno == EINVAL) return 1; diskoffset += BIG; } else if (count == 0) /* finished */ break; else /* got something, process */ { for (block = 0; block < count; /* look at each block we read */ block += DEV_BSIZE, diskoffset += DEV_BSIZE ) { if (* (int *) (&diskblock [block]) == DISKMAGIC) /* found a BSD label? */ /* * BSD label. The partition starts in the previous sector * and is lp->secperunit sectors long. */ { struct disklabel *lp = (struct disklabel *) &diskblock [block]; int64_t offset = (diskoffset >> DEV_BSHIFT) - 1; /* * It would be nice to use d_secperunit here, but it's not * reliable. Sometimes it's correct, other times it's too * high by the value of the partition offset. Use the * length of partition c instead. This isn't guaranteed to * be correct, but it's more often correct than * d_secperunit. */ /* int64_t length = lp->d_secperunit - offset; */ int64_t length = lp->d_partitions [2].p_size; printf ("Disk label found at sector %lld (%s), ", offset + 1, roughlength ((offset + 1) << DEV_BSHIFT, 1) ); printf ("length %lld sectors (%s)\n", length, roughlength (length << DEV_BSHIFT, 1) ); display (lp); printf ("*** fdisk partition: offset %lld\tlength %lld\ttype 165 (last sector %lld)\n", offset, length, offset + length - 1); if (quick) /* skip to end of slice */ { offset += length; /* point past the end */ if (lseek (special, offset << DEV_BSHIFT, SEEK_SET) < 0) { fprintf (stderr, "Can't seek past end of slice: %s (%d)\n", strerror (errno), errno ); return 1; } diskoffset = offset << DEV_BSHIFT; if (verbose) printf ("Skipping to sector %lld (%s)\n", diskoffset >> DEV_BSHIFT, roughlength (diskoffset, 1) ); break; } } else if (((diskblock [block] == 0xeb) || (diskblock [block] == 0xeb)) && (diskblock [block + 510] == BOOTSIG0) && (diskblock [block + 511] == BOOTSIG1) && (diskblock [block + 3] == 'M') ) /* guess at this */ /* * Could be a FAT file system. The sectors look like * this, but the definitions in msdosfs don't seem to be * correct. This needs more investigation. */ { /* 00000000 eb 58 90 4d 53 57 49 4e 34 2e 31 00 02 20 20 00 |.X.MSWIN4.1.. .| 00000010 02 00 00 00 00 f8 00 00 3f 00 f0 00 3f 00 00 00 |........?...?...| 00000020 31 e6 5d 00 45 2f 00 00 00 00 00 00 02 00 00 00 |1.].E/..........| 00000030 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000040 80 00 29 14 00 d0 07 4e 4f 20 4e 41 4d 45 20 20 |..)....NO NAME | 00000050 20 20 46 41 54 33 32 20 20 20 fa 33 c9 8e d1 bc | FAT32 .3....| 00000060 f8 7b 8e c1 bd 78 00 c5 76 00 1e 56 16 55 bf 22 |.{...x..v..V.U."| 00000070 05 89 7e 00 89 4e 02 b1 0b fc f3 a4 8e d9 bd 00 |..~..N..........| 00000080 7c c6 45 fe 0f 8b 46 18 88 45 f9 38 4e 40 7d 25 ||.E...F..E.8N@}%| 00000090 8b c1 99 bb 00 07 e8 97 00 72 1a 83 eb 3a 66 a1 |.........r...:f.| 000000a0 1c 7c 66 3b 07 8a 57 fc 75 06 80 ca 02 88 56 02 |.|f;..W.u.....V.| 000000b0 80 c3 10 73 ed bf 02 00 83 7e 16 00 75 45 8b 46 |...s.....~..uE.F| 000000c0 1c 8b 56 1e b9 03 00 49 40 75 01 42 bb 00 7e e8 |..V....I@u.B..~.| 000000d0 5f 00 73 26 b0 f8 4f 74 1d 8b 46 32 33 d2 b9 03 |_.s&..Ot..F23...| 000000e0 00 3b c8 77 1e 8b 76 0e 3b ce 73 17 2b f1 03 46 |.;.w..v.;.s.+..F| 000000f0 1c 13 56 1e eb d1 73 0b eb 27 83 7e 2a 00 77 03 |..V...s..'.~*.w.| 00000100 e9 fd 02 be 7e 7d ac 98 03 f0 ac 84 c0 74 17 3c |....~}.......t.<| 00000110 ff 74 09 b4 0e bb 07 00 cd 10 eb ee be 81 7d eb |.t............}.| 00000120 e5 be 7f 7d eb e0 98 cd 16 5e 1f 66 8f 04 cd 19 |...}.....^.f....| 00000130 41 56 66 6a 00 52 50 06 53 6a 01 6a 10 8b f4 60 |AVfj.RP.Sj.j...`| 00000140 80 7e 02 0e 75 04 b4 42 eb 1d 91 92 33 d2 f7 76 |.~..u..B....3..v| 00000150 18 91 f7 76 18 42 87 ca f7 76 1a 8a f2 8a e8 c0 |...v.B...v......| 00000160 cc 02 0a cc b8 01 02 8a 56 40 cd 13 61 8d 64 10 |........V@..a.d.| 00000170 5e 72 0a 40 75 01 42 03 5e 0b 49 75 b4 c3 03 18 |^r.@u.B.^.Iu....| 00000180 01 27 0d 0a 49 6e 76 61 6c 69 64 20 73 79 73 74 |.'..Invalid syst| 00000190 65 6d 20 64 69 73 6b ff 0d 0a 44 69 73 6b 20 49 |em disk...Disk I| 000001a0 2f 4f 20 65 72 72 6f 72 ff 0d 0a 52 65 70 6c 61 |/O error...Repla| 000001b0 63 65 20 74 68 65 20 64 69 73 6b 2c 20 61 6e 64 |ce the disk, and| 000001c0 20 74 68 65 6e 20 70 72 65 73 73 20 61 6e 79 20 | then press any | 000001d0 6b 65 79 0d 0a 00 00 00 49 4f 20 20 20 20 20 20 |key.....IO | 000001e0 53 59 53 4d 53 44 4f 53 20 20 20 53 59 53 7e 01 |SYSMSDOS SYS~.| 000001f0 00 57 49 4e 42 4f 4f 54 20 53 59 53 00 00 55 aa |.WINBOOT SYS..U.| 00000200 52 52 61 41 00 00 00 00 00 00 00 00 00 00 00 00 |RRaA............| */ struct bootsector710 *bp = (struct bootsector710 *) &diskblock [block]; struct bpb710 *bpbp = (struct bpb710 *) bp->bsPBP; printf ("MS-DOS boot sector found at offset %lld (%s)\n", diskoffset >> DEV_BSHIFT, roughlength (diskoffset, 1) ); printf ("OEM Name:\t\t\t\t%s\n" "Bytes per sector:\t\t\t%d\n" "sectors per cluster:\t\t\t%d\n" "number of reserved sectors:\t\t%d\n" "number of FATs:\t\t\t\t%d\n" "number of root directory entries:\t%d\n" "total number of sectors:\t\t%d\n" "media descriptor:\t\t\t%d\n" "number of sectors per FAT:\t\t%d\n" "sectors per track:\t\t\t%d\n" "number of heads:\t\t\t%d\n" "# of hidden sectors:\t\t\t%d\n" "# of sectors if bpbSectors == 0:\t%d\n" "like bpbFATsecs for FAT32:\t\t%d\n" "extended flags::\t\t\t%d\n" "filesystem version:\t\t\t%d\n" "start cluster for root directory:\t%d\n" "filesystem info structure sector:\t%d\n", bp->bsOEMName, bpbp->bpbBytesPerSec, /* bytes per sector */ bpbp->bpbSecPerClust, /* sectors per cluster */ bpbp->bpbResSectors, /* number of reserved sectors */ bpbp->bpbFATs, /* number of FATs */ bpbp->bpbRootDirEnts, /* number of root directory entries */ bpbp->bpbSectors, /* total number of sectors */ bpbp->bpbMedia, /* media descriptor */ bpbp->bpbFATsecs, /* number of sectors per FAT */ bpbp->bpbSecPerTrack, /* sectors per track */ bpbp->bpbHeads, /* number of heads */ bpbp->bpbHiddenSecs, /* # of hidden sectors */ bpbp->bpbHugeSectors, /* # of sectors if bpbSectors == 0 */ bpbp->bpbBigFATsecs, /* like bpbFATsecs for FAT32 */ bpbp->bpbExtFlags, /* extended flags: */ bpbp->bpbFSVers, /* filesystem version */ bpbp->bpbRootClust, /* start cluster for root directory */ bpbp->bpbFSInfo ); /* filesystem info structure sector */ printf ("*** fdisk partition: offset %lld\tlength %lld\ttype 6 (last sector %lld)\n", diskoffset >> DEV_BSHIFT, (int64_t) bpbp->bpbSectors, (diskoffset >> DEV_BSHIFT) + (int64_t) bpbp->bpbSectors - 1); #if 0 /* * The stuff above is just plain wrong. Don't use it to * calculate a skip. */ if (quick) /* skip to end of slice */ { if (lseek (special, bpbp->bpbSectors << DEV_BSHIFT, SEEK_CUR) < 0) { fprintf (stderr, "Can't seek past end of slice: %s (%d)\n", strerror (errno), errno ); return 1; } diskoffset += bpbp->bpbSectors; } #endif } /* UFS super block? */ else if (((struct fs *) (&diskblock [block]))->fs_magic == FS_MAGIC) /* found a ufs super block? */ /* * UFS super block. The partition starts in the previous * sector and is lp->secperunit sectors long. */ { struct fs *fsp = (struct fs *) (&diskblock [block]); int64_t offset = (diskoffset >> DEV_BSHIFT); /* * The header files don't make clear what size blocks are implied * in fs_size. By observation, it appears to be fragments. */ int64_t length = ((int64_t) fsp->fs_size * fsp->fs_fsize) >> DEV_BSHIFT; printf ("UFS super block found at sector %lld (%s), ", offset + 1, roughlength ((offset + 1) << DEV_BSHIFT, 1) ); printf ("Super block offset in file system: %d\n", fsp->fs_sblkno); offset -= fsp->fs_sblkno; /* point to start of fs */ printf ("length %lld sectors (%s)\n", length, roughlength (length << DEV_BSHIFT, 1) ); printf ("block size %d, frag size %d\n", fsp->fs_bsize, fsp->fs_fsize); printf ("file system last modified at %s", ctime (&fsp->fs_time)); /* no \n */ /* * fs_fsmnt seems only to be set for primary super blocks. * Use it as a test, since I can't find any other. */ if (fsp->fs_fsmnt [0]) { printf ("last mounted on %s\n", fsp->fs_fsmnt); printf ("disklabel entry: " "*x*: %lld\t%lld\t4.2BSD\t%d\t%d\t%d\n\n", length, offset, fsp->fs_fsize, fsp->fs_bsize, fsp->fs_cpg); } else printf ("Appears to be a secondary super block\n\n"); if (quick) /* skip to end of slice */ { offset += length; /* point past the end */ if (lseek (special, offset << DEV_BSHIFT, SEEK_SET) < 0) { fprintf (stderr, "Can't seek past end of slice: %s (%d)\n", strerror (errno), errno ); return 1; } diskoffset = offset << DEV_BSHIFT; if (verbose) printf ("Skipping to sector %lld (%s)\n", diskoffset >> DEV_BSHIFT, roughlength (diskoffset, 1) ); break; } } else if ((* (uint64_t *) (&diskblock [block]) == VINUM_MAGIC) || (* (uint64_t *) (&diskblock [block]) == VINUM_NOMAGIC)) /* Vinum label. The drive starts 8 sectors earlier. */ { struct vinum_hdr *v = (struct vinum_hdr *) &diskblock [block]; time_t t; char *config; printf ("Found a Vinum drive at offset %lld (%s)\n", diskoffset >> DEV_BSHIFT, roughlength (diskoffset, 1) ); if (v->magic == VINUM_NOMAGIC) /* obliterated */ printf ("*** Configuration has been obliterated ***\n"); printf ("Drive %s:\n", v->label.name); t = v->label.date_of_birth.tv_sec; printf ("\t\tCreated on %s at %s", v->label.sysname, ctime (&t)); t = v->label.last_update.tv_sec; printf ("\t\tConfig last updated %s", /* care: \n at end */ ctime (&t)); printf ("\t\tSize: %16lld bytes (%lld MB)\n", (int64_t) v->label.drive_size, /* bytes used */ (int64_t) (v->label.drive_size / MEGABYTE) ); /* * Vinum config starts in the next sector. It's in string * format, so we just need to print it, but it might go * beyond the end of the buffer. We can still print it, * because we have a \0 at the end of the buffer, but if we * finish there, we need to read the following block to * print the rest of the config. */ config = &diskblock [block + DEV_BSIZE]; /* config info */ printf ("%s", config); if (strlen (config) == (count - block - DEV_BSIZE)) /* to end of block, */ { diskoffset += count; count = read (special, diskblock, BIG); if (count < 0) { printf ("Can't read %s at offset %qd: %s (%d)\n", specialname, diskoffset, strerror (errno), errno); diskoffset += BIG; } else if (count == 0) /* finished */ break; printf ("%s", config); /* rest of config */ } printf ("\n"); } } if (verbose) { printf ("%s\r", roughlength (diskoffset, 0)); fflush (stdout); } } } return 0; }