/* * $Id: //devel/tools/main/scandrive/scandrive.cpp#1 $ * * written by : Stephen J. Friedl (2002/02/01) * Software Consultant * OrangeCounty, California USA * steve@unixwiz.net -or- www.unixwiz.net * * ================================================================ * ==== This program is in the publid domain, and you can do ==== * ==== anything with it you like with no restrictions. ==== * ================================================================ * * This program runs through a hard drive and looks for any sectors * that look "interesting". The tests for "interesting" are pretty * broad on the assumption that a later program can run through * them and make a much more detailed analysis. * * "BLOCKS" * -------- * * The term "block" is very much overloaded, because its meaning * depends on its context (filesystem? device?). So we get around * this whole problem by talking about disk "sectors", which are * always 512 bytes. * * As far as this program is concerned, the first sector on the * drive is 0, not 1, and we're not so sure that everybody agrees * with this notion. * * BUFFERING * --------- * * We take pretty substantial steps to buffer I/O in large chunks * to make this not take so damn long. This would be much easier if * everything we were looking at would fit in a single sector, but * a super block is *two* sectors. This makes the buffering a lot * more complicated because a super block might cross buffering * boundaries. * * So we start by filling our buffer, and scan all the way to the * end of it. Then we copy the last block over to the start of the * buffer, then refill the buffer starting with sector 2. This * gives one sector of overlap and allows us to not miss anything * for the odd case. * * UPDATE: we have decided to cheat: it seems that in a super * block, all the "interesting" stuff appears in the first * sector - lots of padding at the end - so we can make this run * a lot easier We just allocate a bit more space in the I/O * buffer (that we never touch directly) to allow any superblock * refs to span without memory faults. * * WHAT'S "INTERESTING" * -------------------- * * We have a very broad definition of what an "interesting" sector * is, because trying to make it very smart might cause it to miss * some sectors that otherwise would be interesting. We figure that * by creating a list of "interesting" sectors, a later program can * be used to perform a more detailed analysis. * * So these things are "interesting" * * * any sector with the 0x55AA signature at the end is considered * to be a potential partition table. * * * any sector with the EXT2 magic number in the proper place is * considered to be a potential ext2 super block * * * any sector that looks like a root directory entry * * COMMAND LINE * ------------ * * -v DEV DEV is the device we're to read from to scan. * This is always *readonly* as far as this program * is concerned. * * -C #### DEV capacity is ### sectors. This is mainly used * for progress reporting (% done), plus can limit * the scan to a partial drive. * * -B ### I/O buffer size is ### 512-byte sectors (default=256) * * -L FILE Results are summarized into FILE: this is in a more * limited format that is meant for later processing. * * TODO * ---- * * * We don't behave very well upon errors: we just exit. We need to * fix this. * * * add "starting" and "ending" sector options to scan just a limited * subset. This would be really helpful, but the llseek() (long seek) * operation doesn't seem to be working for us. Dunno why... * * HISTORY * ------- * 1.01 - 2009/11/08 - added a bit of help text * 1.00 - 2002/02/01 - initial release */ #include #include #include #include #include #include #include #include #include #include static const char Version[] = "scandrive 1.01 - 2009-11-08 - http://unixwiz.net/tools/"; #define SECTORSIZE 512 static const char *device = 0; static const char *logfile = 0; static int reporttime = 1; static void die(const char *format, ...) __attribute__((format(printf,1,2))) __attribute__((noreturn)); static void lprintf(const char *format, ...) __attribute__((format(printf,1,2))); static void newline(); static int looks_like_ext2fs(const unsigned char *p); static int looks_like_ptable(const unsigned char *p); static int looks_like_rootdir(const unsigned char *p); static int disk_fd = -1; static FILE *logfp = 0; static unsigned char *iobuf = 0; // *big* chunk of memory static int iobuf_alloc = 256; // max blocks in iobuf static long curr_sectorno = 0; // # of the first block in the buffer static long device_capacity = 0; // in sectors, if we know it static int nl_pending = 0; // TRUE if we're not at the start of line static const char * const helptext[] = { "", "Usage: scandrive [options] -v dev", "", " -V Show version info, then exit", " -v dev Set the device to (required)", " -L LF Set logfile to ", " -C N Set drive capacity to N sectors", " -B N Set the buffer size to N sectors", 0 }; static void show_help_and_exit(int ecode) { for (const char * const *p = helptext; *p; p++) { puts(*p); } exit(ecode); } int main(int argc, char **argv) { int c; while ( (c = getopt(argc, argv, "v:L:B:C:V")) != EOF ) { switch (c) { case 'V': puts(Version); exit(EXIT_SUCCESS); case 'C': /* -C capacity (sectors) */ device_capacity = atol(optarg); if ( device_capacity <= 0 ) die("ERROR: bogus capacity {%s}", optarg); break; case 'B': /* -B bufsize (sectors) */ iobuf_alloc = atoi(optarg); if ( iobuf_alloc <= 0 ) die("ERROR: bogus buffersize {%s}", optarg); break; case 'v': /* -v DEVICE */ device = optarg; break; case 'L': /* -L logfile */ logfile = optarg; break; default: show_help_and_exit(EXIT_FAILURE); } } puts(Version); /*---------------------------------------------------------------- * SANITY CHECK ON PARAMETERS * * As much we can, look for stuff that doesn't look right from the * user on the command line. */ if ( device == 0 ) { fprintf(stderr, "ERROR: missing \"-v device\" parameter\n"); show_help_and_exit(EXIT_FAILURE); } if ( iobuf_alloc <= 1 ) die("ERROR: missing/bogus I/O buffer size"); /*---------------------------------------------------------------- * ALLOCATE I/O BUFFER * * The I/O buffer should be very large, and it's a fatal error if * we cannot get the memory for it. Note that we allocate one sector * larger than we are asking for to allow the super block tests * to expand one sector past the end. We also zerofill the whole * thing (though only the *last* sector really needs this). */ if ( (iobuf = (unsigned char *)calloc(iobuf_alloc + 1, SECTORSIZE)) == 0 ) die("ERROR: cannot allocate I/O buffer of %d sectors", iobuf_alloc); printf("I/O buffer: %d sectors of %d bytes\n", iobuf_alloc, SECTORSIZE); /*---------------------------------------------------------------- * OPEN DISK DRIVE * * The drive is opened for readonly access: it's an error if we * can't open it. * * ===TODO: do we have to specify anything for "largefile" access? * Most drives we mess with are larger than 4G (32bit file offset). */ if ( (disk_fd = open(device, O_RDONLY)) < 0 ) { die("ERROR: cannot open %s [%s]", device, strerror(errno)); } if ( device_capacity > 0) { printf("Device %s is open (capacity = %ld sectors)\n", device, device_capacity); } else printf("Device %s is open\n", device); /*---------------------------------------------------------------- * OPEN LOGFILE * * The logfile is used to record what we've found in our scan, and * it's always *append*. We also put a header in the file to remind * the reader what we were doing. */ if ( logfile && (logfp = fopen(logfile, "a")) == 0 ) { die("ERROR: cannot create output file %s [%s]", logfile, strerror(errno)); } if ( logfp ) { time_t now; time(&now); printf("Logfile %s is open\n", logfile); lprintf("# Started scan of %s at %s", device, ctime(&now)); lprintf("# buffer size = %d sectors", iobuf_alloc); if ( device_capacity > 0 ) lprintf("# capacity = %ld sectors", device_capacity); } time_t lastreport = 0; for (int loopno = 0 ; (device_capacity == 0) || (curr_sectorno < device_capacity) ; loopno++ ) { /*-------------------------------------------------------- * REPORT PROGRESS * * Every so often we have to report our progress, which is * the sector we're working on now, and (if we know it) * what % through the process this is. * * On each I/O refill loop, we check the time and report * the progress only if it's been too long since the last * report. By default we report each second. */ time_t now; time(&now); if ( lastreport == 0 || (now - lastreport) > reporttime ) { lastreport = now; printf("\rLoop %d: scanning sector %ld", loopno, curr_sectorno ); if ( device_capacity > 0 ) { printf(" (%5.2f%%)", (curr_sectorno * 100.) / device_capacity); } printf("... "); nl_pending = 1; fflush(stdout); } /*-------------------------------------------------------- * REFILL DISK BUFFER */ const int nr = read(disk_fd, iobuf, iobuf_alloc*SECTORSIZE); if ( nr < 0 ) { newline(); printf("ERROR ON READ: sector %ld [%s]\n", curr_sectorno, strerror(errno)); break; } else if ( nr == 0 ) { newline(); printf("Got EOF on sector %ld\n", curr_sectorno); break; } else if ( (nr % SECTORSIZE) != 0 ) { newline(); printf("ERROR: read not even sector count (nr=%d)\n", nr); break; } /*-------------------------------------------------------- * SNIFF THE SECTORS! * * Now the I/O buffer contains as much as we were able to * read from the input, */ const unsigned char *p = iobuf; const unsigned char *pmax = iobuf + nr; for ( ; p < pmax; p += SECTORSIZE ) { if ( looks_like_ptable(p) ) { newline(); printf("Found ptable magic at sector %ld\n", curr_sectorno); lprintf("pt\t%ld", curr_sectorno); } if ( looks_like_ext2fs(p) ) { const struct ext2_super_block *sbp = (const struct ext2_super_block *)p; newline(); printf("Found ext2 magic at sector %ld (size %d, #%d)\n", curr_sectorno, sbp->s_blocks_count, sbp->s_block_group_nr); lprintf("ext2\t%ld\t# size=%d #%d", curr_sectorno, sbp->s_blocks_count, sbp->s_block_group_nr); } if ( looks_like_rootdir(p) ) { newline(); printf("Found ext2 root dir at sector %ld\n", curr_sectorno); lprintf("root\t%ld", curr_sectorno); } curr_sectorno++; } } newline(); printf("Finished scanning.\n"); return 0; } /* * die() * * Given a printf-like argument list, format it to the standard * error stream, append a newline, and exit with error status. */ static void die(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); putc('\n', stderr); va_end(args); exit(EXIT_FAILURE); } /* * lprintf() * * Send the given printf-style string to the log file if one is * open. We append a newline automatically and flush the file * after each write. */ static void lprintf(const char *format, ...) { if ( logfp ) { va_list args; va_start(args, format); vfprintf(logfp, format, args); va_end(args); putc('\n', logfp); fflush(logfp); } } /* * newline() * * When we update our progress, we do it by rewriting the same line * over and over with a CR, and end with *NO* newline. This leaves * the cursor sitting off in the middle of the screen somewhere. * * This function issues a newline only if we need one. This is only * for aesthetic purposes - we're anal about this. */ static void newline() { if ( nl_pending ) { putchar('\n'); nl_pending = 0; } } /* * looks_like_ptable() * * Does this block look like a partition table? Return TRUE if so, * and FALSE if not. All we really look at is the 55AA signature * at the end. */ static int looks_like_ptable(const unsigned char *p) { assert(p != 0); return p[510] == 0x55 && p[511] == 0xAA; } /* * looks_like_ext2fs() * * Does this block have the signature for the ext2 filesystem? We * only care about the magic number: a more detailed look can come * later. */ static int looks_like_ext2fs(const unsigned char *p) { const struct ext2_super_block *sbp = (const struct ext2_super_block *)p; assert(p != 0); return (sbp->s_magic == EXT2_SUPER_MAGIC); } #if 0 static void dumpdirs(const char *id, unsigned char *ptr) { assert(id != 0); assert(ptr != 0); printf("dumping dir info for %s\n", id); for (int i = 0; i < 12; i++ ) printf(" %02X", *ptr++); putchar('\n'); for (int i = 0; i < 12; i++ ) printf(" %02X", *ptr++); putchar('\n'); } #endif /* * looks_like_rootdir() * * Does this directory block look like the *root* directory? Though * directories are variable length, the format of the root dir should * be fixed. So we define our own directory structure and test as * much of this as we can safely rely on. * * Note that the "file type" is supposed to be "2" for directories, * but we think we've seen directories with this set to zero (for * "unknown"), so we rely on other programs to refine our test of * "interesting". */ static int looks_like_rootdir(const unsigned char *p) { struct dir { __u32 inode; // always 2 for root __u16 rec_len; // 12 bytes __u8 name_len; // "." = 1 / ".." = 2 __u8 file_type; // *unused* __u32 namebuf; // }; /*---------------------------------------------------------------- * Convert a proper-endian version for the "." and ".." strings * so we can test them with an integral compare instead of a string * compare. This is only done on the *first* time we run through * this routine. */ static __u32 DIR_dot = 0; static __u32 DIR_dotdot = 0; if ( DIR_dot == 0 ) /* first time here */ { char buf[4]; buf[0] = '.'; buf[1] = 0; buf[2] = 0; buf[3] = 0; buf[1] = '\0'; memcpy((char *)&DIR_dot, buf, 4); buf[1] = '.' ; memcpy((char *)&DIR_dotdot, buf, 4); } /*---------------------------------------------------------------- * Now see if the pointed-to sector looks like a directory entry. */ const struct dir *dp = (struct dir *)p; if ( ( dp[0].inode == 2 && dp[1].inode == 2 ) && ( dp[0].rec_len == 12 && dp[1].rec_len == 12 ) && ( dp[0].name_len == 1 && dp[1].name_len == 2 ) // && ( dp[0].file_type == 2 && dp[1].file_type == 2 ) && ( dp[0].namebuf == DIR_dot ) && ( dp[1].namebuf == DIR_dotdot ) ) { return 1; } return 0; }