/*
	File:		BasicDiskImage.c

	Contains:	Puts the contents of a disk in a file.

	Written by:	Q

	Copyright:	Copyright © 1999 by Apple Computer, Inc., All Rights Reserved.

				You may incorporate this Apple sample source code into your program(s) without
				restriction. This Apple sample source code has been provided "AS IS" and the
				responsibility for its operation is yours. You are not permitted to redistribute
				this Apple sample source code as "Apple sample source code" after having made
				changes. If you're going to re-distribute the source, we require that you make
				it clear in the source that the code was descended from Apple sample source
				code, but that you've made changes.

	Change History (most recent first):

*/

/////////////////////////////////////////////////////////////////

// MIB Setup

#include "MoreSetup.h"

// Mac OS Interfaces

#include <Files.h>
#include <Devices.h>
#include <Memory.h>
//#include <Navigation.h>
#include <StandardFile.h>


// Standard C interfaces

#include <stdio.h>
#include <stdlib.h>

// MoreIsBetter Interfaces

#include "MoreDisks.h"

/////////////////////////////////////////////////////////////////

static void SetWidePosOffset(UInt32 blockOffset, XIOParamPtr pb)
    // Set up ioPosMode and either ioPosOffset or ioWPosOffset for a
    // device _Read or _Write.
    //
    // Code stolen from Technote 1189.
{
    pb->ioWPosOffset.lo = blockOffset << 9;  // convert block number
    pb->ioWPosOffset.hi = blockOffset >> 23; // to wide byte offset
 
    if ( pb->ioWPosOffset.hi != 0 ) {
        // Offset on drive is >= 4G, so use wide positioning mode
        pb->ioPosMode = fsFromStart | (1 << kWidePosOffsetBit);
    } else {
        // Offset on drive is < 4G, so use regular positioning mode,
        // and move the offset into ioPosOffset
        pb->ioPosMode = fsFromStart;
        ((IOParam *)pb)->ioPosOffset = pb->ioWPosOffset.lo;
    }
}

/////////////////////////////////////////////////////////////////

enum {
	kBufferSize = 1024L * 1024L		// Do the copy in 1 MB chunks.
};

static OSStatus ImageDiskToFile(SInt16 driveNumber, UInt32 startBlock, UInt32 numBlocks, const FSSpec *fss)
	// Creates a file at fss containing the contents of blocks [startBlock..startBlock + numBlocks)
	// of the drive specified by driveNumber.
{
	OSStatus err;
	OSStatus junk;
	Ptr bufferPtr;
	SInt16 fileRefNum;
	XIOParam pb;
	UInt32 thisBlock;
	UInt32 lastBlock;
	UInt32 blocksThisTime;
	SInt32 bytesToWrite;

	bufferPtr = nil;
	fileRefNum = 0;
	
	// Create and open the file, and then allocate our memory buffer.
	
	junk = FSpCreate(fss, 'hDmp', 'BINA', 0);
	err  = FSpOpenDF(fss, fsWrPerm, &fileRefNum);
	if (err == noErr) {
		bufferPtr = NewPtr(kBufferSize);
		err = MemError();
	}
	if (err == noErr) {

		// Set up constant parts of param block.

		pb.ioRefNum    = MoreGetDriveRefNum(driveNumber);
		pb.ioVRefNum   = driveNumber;
		pb.ioBuffer    = bufferPtr;
		pb.ioPosMode   = fsFromStart;

		// Loop, reading blocks from the disk and writing them to the file until we're out of blocks.
		
		thisBlock = startBlock;
		lastBlock = startBlock + numBlocks;
		while ( (err == noErr) && (thisBlock != lastBlock) ) {
			if ( (lastBlock - thisBlock) > (kBufferSize / 512) ) {
				blocksThisTime = (kBufferSize / 512);
			} else {
				blocksThisTime = (lastBlock - thisBlock);
			}
			pb.ioReqCount  = blocksThisTime * 512;
			SetWidePosOffset(thisBlock, &pb);
			err = PBReadSync( (ParmBlkPtr) &pb);
			if (err == noErr) {
				bytesToWrite = blocksThisTime * 512;
				err = FSWrite(fileRefNum, &bytesToWrite, bufferPtr);
			}
			if (err == noErr) {
				thisBlock += blocksThisTime;
				
				// Show some progress.
				
				printf(".");
				fflush(stdout);
			}
		}
	}

	// Clean up.
	
	if ( fileRefNum != 0 ) {
		junk = FSClose(fileRefNum);
		MoreAssertQ(junk == noErr);
	}
	if (bufferPtr != nil) {
		DisposePtr(bufferPtr);
		MoreAssertQ(MemError() == noErr);
	}
	
	return err;
}

/////////////////////////////////////////////////////////////////

static void DoListDriveQueue(void)
	// Prints a list of drives and their sizes.  Stolen from the test
	// code for the MoreDisks module of MoreIsBetter.
{
    SInt16 index;
    DrvQElPtr thisDrv;
    UInt32 sizeInBlocks;
    MoreDisksCDROMResponse isCDResponse;
    char cdChar;

	printf("List of drives:\n");
	printf("DrvNum Size\n");
	index = 1;
	do {
		thisDrv = MoreGetIndDrive(index);
		if (thisDrv != nil) {
			if ( MoreGetDriveSize(thisDrv->dQDrive, &sizeInBlocks) != noErr ) {
				sizeInBlocks = 0;
			}
			MoreIsDriveCDROM(thisDrv->dQDrive, &isCDResponse);
			switch (isCDResponse) {
				case kMoreDriveUnableToDetermineCDROM: cdChar = '?'; break;
				case kMoreDriveIsCDROM: cdChar = 'C'; break;
				case kMoreDriveIsNotCDROM: cdChar = 'N'; break;
				default:
					MoreAssertQ(false);
					break;
			}
			
			printf("%6d %8ld (%c)\n", thisDrv->dQDrive, sizeInBlocks, cdChar);
			index += 1;
		}
	} while (thisDrv != nil);
}

/////////////////////////////////////////////////////////////////

// I wrote all the code to allow the user to choose where to save the 
// file using Nav, then remembered that I wanted to be able to run this
// program even on systems that don't have Nav.  So it was back to Standard
// File.

#if 0

static OSStatus NavExtractSingleReply(const NavReplyRecord *reply, FSSpec *fss)
{
	OSStatus err;
	SInt32 itemCount;
	AEKeyword junkKeyword;
	DescType junkDescType;
	Size junkSize;
	
	MoreAssertQ( (AECountItems(&reply->selection, &itemCount) == noErr) && (itemCount == 1));
	
	err = AEGetNthPtr(&reply->selection,
								 1,
								 typeFSS,
								 &junkKeyword,
								 &junkDescType,
								 fss,
								 sizeof(*fss),
								 &junkSize);
	if (err == noErr) {
		MoreAssertQ(junkDescType == typeFSS);
		MoreAssertQ(junkSize == sizeof(*fss));
		
		// If Nav gave us a bogus FSSpec, fix it.
		
		if ( fss->name[0] == 0 ) {
			(void) FSMakeFSSpec(fss->vRefNum, fss->parID, fss->name, fss);
		}
	}
	return err;
}

{
	NavReplyRecord reply;
	NavDialogOptions options;
	
	err = NavGetDefaultDialogOptions(&options);
	if (err == noErr) {
		err = NavPutFile(nil, &reply, &options, nil, 'BINA', 'hDmp', nil);
		if (err == noErr) {
			err = NavExtractSingleReply(&reply, &fss);
		}
		junk = NavDisposeReply(&reply);
		MoreAssertQ(junk == noErr);
	}
}

#endif

/////////////////////////////////////////////////////////////////

#if 0
void main(void)
	// The main entry point.  Ask the user to choose a disk, select
	// a section of the disk to save, and then choose a file for
	// where to place the disk image.
{
	OSStatus err;
	char tmpStr[256];
	SInt16 driveNum;
	UInt32 sizeInBlocks;
	UInt32 startBlock;
	UInt32 blocksToRead;
	FSSpec fss;
	
	printf("BasicDiskImage\n");
	printf("-- A program to copy the entire contents of a disk to a file.\n");

	DoListDriveQueue();

	printf("Enter a drive number:\n");
	gets(tmpStr);
	driveNum = atol(tmpStr);
	
	err = MoreGetDriveSize(driveNum, &sizeInBlocks);
	if (err == noErr) {
		printf("Enter the first block number to image (return for 0).\n");
		gets(tmpStr);
		if (tmpStr[0] == 0) {
			startBlock = 0;
		} else {
			startBlock = atol(tmpStr);
		}
		if (startBlock >= sizeInBlocks) {
			printf("startBlock must be less than sizeInBlocks.\n");
			err = userCanceledErr;
		}
	}
	if (err == noErr) {
		printf("Enter the number of blocks to image (return for all).\n");
		gets(tmpStr);
		if (tmpStr[0] == 0) {
			blocksToRead = sizeInBlocks - startBlock;
		} else {
			blocksToRead = atol(tmpStr);
		}
		if (blocksToRead > sizeInBlocks) {
			printf("blocksToRead must be less than sizeInBlocks.\n");
			err = userCanceledErr;
		}
		if ((startBlock + blocksToRead) > sizeInBlocks) {
			printf("startBlock + blocksToRead must be less than or equal to sizeInBlocks.\n");
			err = userCanceledErr;
		}
	}
	if (err == noErr) {
		StandardFileReply reply;
		
		StandardPutFile("\pSave a basic disk image:", "\pBasic Disk Image", &reply);
		if (reply.sfGood) {
			fss = reply.sfFile;
			if (reply.sfReplacing) {
				err = FSpDelete(&fss);
			}
		} else {
			err = userCanceledErr;
		}
	}
	if (err == noErr) {
		err = ImageDiskToFile(driveNum, startBlock, blocksToRead, &fss);
		printf("\n");
	}

	if (err == noErr) {
		printf("Success.\n");
	} else {
		printf("Failed with error %ld.\n", err);
	}
	
	printf("Done.  Press command-Q to Quit.\n");
}

#endif