DskImage

A utility for reading and writing DOS diskette images on DOS machines

Overview

Download link: dskimage.zip (14KB)

In the 1980s diskettes were the primary storage mechanism for computers. Compared to what we have today, they don't hold a lot and they are not reliable. You also can't "send" a diskette through the Internet as it is a physical object. That is where diskette images come in.

A diskette image is a full sector-by-sector copy of the data on a diskette. Unlike a ZIP archive with files in it, a diskette image preserves the structure of the diskette, the reserved areas, the "free" areas that are not in active use, etc. This is required for accurately reproducing bootable diskettes or for doing forensic work on a diskette. Diskette images can be sent electronically, used in virtual machines, or just serve as backup copies of physical diskettes.

In 2006 I was trying to archive my DOS diskette collection and the tools I was using were not graceful when handling read errors. After a bit of frustration I wrote my own tool that deals with transient errors better.

Here is what DskImage can do for you:

The diskette image files are raw sector dumps of the diskette. There is no meta data added or compression applied. This makes the image files compatible with virtual floppy drives on virtual machines, Linux DD, Linux loopback mounting, etc.

DskImage uses BIOS calls to interact with the floppy drive. Therefore only BIOS supported floppy drives are supported.

Copy protected diskettes can not be imaged with DskImage.

Using DskImage

Bad sectors on floppy disks are often transient in nature; retrying the read a few times is often all that is needed to get past the error. DskImage will retry reading a bad sector five times by default, and you can set an environment variable (DSKIMAGE_RETRIES) to specify up to 10 retries. This gives you a better chance of imaging a disk that is in marginal condition.

If you are really having problems with a diskette take a few minutes to clean the drive heads; if they are contaminated with dirt or metal oxide shed from previous diskettes they will not read diskettes reliably. There is also quite a bit of variation from floppy drive to floppy drive, so trying to read the diskette in a different floppy drive might work too.

Detailed instructions can be found in the zip file.

Source code

I wrote this code in 2006 and I hold the copyright to it. It is not public domain, free, GPL, etc. I have posted it here for people who are curious about how it works.

The code can be compiled with Turbo C+ for DOS 3.0. Email me if you have questions ...



// Michael Brutman
// February 10th, 2006

#include
#include
#include
#include


static const char * copyright = "(C)opyright 2006 M. Brutman";



unsigned char *sectorBuffer;

int b_drive, b_tracks, b_sides, b_sectors;

// Reading from the BIOS drive or writing?
int reading;

int sectorsInError = 0;

int Retries = 5;


void help( void );


// The commented out error codes only make sense on fixed disks.
// They are here for documentation, but not enabled to save space.

char * errorToString( int e ) {

switch ( e ) {

case 0x00: return "No error";
case 0x01: return "Bad command";
case 0x02: return "Address mark not found";
case 0x03: return "Write protected disk";
case 0x04: return "Sector not found";
// case 0x05: return "Reset failed";
case 0x06: return "Change line active";
// case 0x07: return "Drive parameter activity failed";
case 0x08: return "DMA overrun";
case 0x09: return "DMA Boundary";
// case 0x0A: return "Bad sector flag";
// case 0x0B: return "Not implemented";
case 0x0C: return "Media type not available";
// case 0x0D: return "Invalid number of sectors";
// case 0x0E: return "Control data address mark detected";
// case 0x0F: return "DMA arbitration level out of range";
case 0x10: return "Uncorrectable CRC/ECC";
// case 0x11: return "ECC corrected";
case 0x20: return "Controller failed";
case 0x40: return "Seek failed";
case 0x80: return "Timeout";
// case 0xAA: return "Drive not ready";
// case 0xBB: return "Undefined error";
// case 0xCC: return "Write fault";
default: return "(No error text)";
}
}



// In case of a disk error we use this to reset the drive and
// try again.

void resetDisk( int drive ) {

union REGS inregs, outregs;

inregs.h.ah = 0;
inregs.h.dl = drive;
int86( 0x13, &inregs, &outregs );

if ( outregs.x.cflag != 0 ) {
printf( "Error resetting drive %d: 0x%02X %s\n",
drive, outregs.h.ah, errorToString( outregs.h.ah ) );
}

}



// Reads and writes sectors.
// 0 is a good return code. Anything else indicates an error.

int sectorOp( unsigned char *buf,
int reading,
int drive, int track, int head, int start, int sectors ) {

union REGS inregs, outregs;
struct SREGS segregs;

if ( reading ) {
inregs.h.ah = 2;
}
else {
inregs.h.ah = 3;
}

inregs.h.al = sectors;
inregs.h.dh = head;
inregs.h.dl = drive;
inregs.h.ch = track;
inregs.h.cl = start;
inregs.x.bx = FP_OFF( buf );

segregs.es = FP_SEG( buf );

int86x( 0x13, &inregs, &outregs, &segregs );

if ( outregs.x.cflag != 0 ) {
return outregs.h.ah;
}
else {
return 0;
}

}


// This routine is called to read a track, sector by sector
// instead of all at once. If an error is encountered the
// operation is retried a few times.

void slowTrack( int reading, int drive, int track, int head ) {

puts( "" );

resetDisk( drive );

for ( int i=1; i <= b_sectors; i++ ) {

unsigned char *buf = sectorBuffer + (i<<9)-512;

int goodOp = 0;
int attempts = 0;

for ( ; attempts < Retries; attempts++ ) {

int rc = sectorOp( buf, reading, drive, track, head, i, 1 );
if ( rc == 0 ) {
goodOp = 1;
break;
}

printf( "Error on %02d:%02d:%02d:%02d Rc = 0x%02X %s\n",
drive, track, head, i, rc, errorToString( rc ) );

resetDisk( drive );
}

if ( attempts > 0 ) {
if ( goodOp == 0 ) {
sectorsInError++;
puts( "Could not get past error on that sector." );
}
else {
printf( "Good read on that sector after %d tries.\n", attempts );
}
}

}

}


void copyToFilename( const char *s, char *filename ) {

int l = strlen( s );
if ( (l<1) || (l>80) ) {
puts( "Error - bad filename\n" );
help( );
}
strcpy( filename, s );
}


int isArgBIOSParm( const char *s ) {

unsigned int t_drive, t_tracks, t_sides, t_sectors;

int rc = sscanf( s, "%d:%d:%d:%d",
&t_drive, &t_tracks, &t_sides, &t_sectors );

if ( rc == 4 ) {

if ( t_drive > 3 ) {
puts( "Floppy drives only please - use 0 to 3 for the drive parameter.\n" );
help( );
}

if ( (t_tracks < 1) || (t_tracks > 80) ) {
puts( "Bad tracks parameter - should be from 1 to 80.\n" );
help( );
}

if ( (t_sides < 1) || (t_sides > 2) ) {
puts( "Sides/Heads should be 1 or 2.\n" );
help( );
}

if ( (t_sectors < 1) || (t_sectors > 21) ) {
puts( "Bad sectors per track - should be from 1 to 21.\n" );
help( );
}

b_drive = t_drive;
b_tracks = t_tracks;
b_sides = t_sides;
b_sectors = t_sectors;
return 1;
}
else {
return 0;
}
}



void help( void ) {
puts( "Usage: dskimage source target\n" );
puts( " source and target are either a BIOS disk specification or a filename.\n" );
puts( " BIOS disk specifications are of the form: d:t:h:s where" );
puts( " d is the BIOS disk drive number (0 to 3)" );
puts( " t is the number of tracks (usually 40 or 80)" );
puts( " h is the number of heads/sides (usually 2, could be 1)" );
puts( " s is the number of sectors per track (usually 9, 15, or 18)\n" );
puts( " The default number of retries when a bad sector is found is 5." );
puts( " Use the DSKIMAGE_RETRIES environment variable to adjust this." );
puts( " (Legal values are from 1 to 10)." );
exit( -1 );
}


int main( int argc, char *argv[] ) {

char filename[80];

puts( "\nDskImage: version 1.0 M. Brutman 2006-02-10\n" );

if ( argc < 3 ) {
help( );
}

// Try to parse the first parameter. If it looks like a BIOS drive
// description, use it as such.

if ( isArgBIOSParm( argv[1] ) ) {
reading = 1;
copyToFilename( argv[2], filename );
}
else {
reading = 0;
copyToFilename( argv[1], filename );
if ( isArgBIOSParm( argv[2] ) == 0 ) {
puts( "Source and target can't be the same type of media." );
puts( "One has to be a file, and one has to be a BIOS disk specification.\n" );
help( );
}
}

char *retryParm = getenv( "DSKIMAGE_RETRIES" );
if ( retryParm != NULL ) {
int tmp = atoi( retryParm );
if ( (tmp < 1) || (tmp > 10) ) {
printf( "Bad value for DSKIMAGE_RETRIES: %d\n\n", tmp );
help( );
}
Retries = tmp;
}


sectorBuffer = (unsigned char *)malloc( 512*b_sectors );


FILE *f;
char * op;

if ( reading ) {
f = fopen( filename, "wb" );
if ( f == NULL ) {
printf( "Error opening file %s for writing.\n", filename );
exit( -1 );
}
op = "Reading";
printf( "Source: %02d:%02d:%02d:%02d\nTarget: %s\n",
b_drive, b_tracks, b_sides, b_sectors, filename );
}
else {
f = fopen( filename, "rb" );
if ( f == NULL ) {
printf( "Error opening file %s for reading.\n", filename );
exit( -1 );
}
op = "Writing";
printf( "Source: %s\nTarget: %02d:%02d:%02d:%02d\n",
filename, b_drive, b_tracks, b_sides, b_sectors );
}

printf( "Retries: %d\n\nPress [Enter] to start ...\n\n", Retries );

int ch = getchar( );
if ( ch != '\n' ) {
printf( "You did not press [Enter] - quitting.\n" );
exit( -1 );
}

for ( int a=0; a < b_tracks; a++ ) {
for ( int b=0; b < b_sides; b++ ) {

printf( "\r%s %02d:%02d:%02d:%02d", op, b_drive, a, b, 1 );

if ( reading ) {

int rc = sectorOp( sectorBuffer, reading, b_drive, a, b, 1, b_sectors );

if ( rc != 0 ) {
// Failed somewhere in the track; go sector by sector
slowTrack( reading, b_drive, a, b );
}

int frc = fwrite( sectorBuffer, 512, b_sectors, f );
if ( frc != b_sectors ) {
puts( "\nError writing to file" );
}

} else {

int frc = fread( sectorBuffer, 512, b_sectors, f );
if ( frc != b_sectors ) {
puts( "\nError reading from file" );
}

int rc = sectorOp( sectorBuffer, reading, b_drive, a, b, 1, b_sectors );

if ( rc != 0 ) {
// Failed somewhere in the track; go sector by sector
slowTrack( reading, b_drive, a, b );
}



} // end if reading

}
}


if ( sectorsInError == 0 ) {
puts( "\nFinished: No sectors were unrecoverable." );
}
else {
printf( "\nFinished: %d sectors were unrecoverable.\n",
sectorsInError );
}

return sectorsInError;

}


Additional information

See "Working with Old Diskettes" for more information on how floppy drives work.


Created May 16th, 2021
(C)opyright Michael B. Brutman, mbbrutman at gmail.com