/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ #ifdef _WIN32 /* * Structures and functions for performing block-level I/O on Win32 logical * and physical volumes. * * Under Win2K/XP this is pretty straightforward: open the volume and * issue seek and read calls. It's not quite that simple -- reads need to * be in 512-byte sectors for floppy and hard drives, seeks need to be on * sector boundaries, and you can't seek from the end, which makes it hard * to figure out how big the volume is -- but it's palatable. * * Under Win95/Win98/WinME, life is more difficult. You need to use the * Int21h/7305h services to access logical volumes. Of course, those weren't * available until Win95 OSR2, before which you used Int25h/6000h, but those * don't work with FAT32 volumes. Access to physical devices requires Int13h, * which is fine for floppies but requires 16-bit flat thunks for hard drives * (see Q137176: "DeviceIoControl Int 13h Does Not Support Hard Disks"). * * If Win98 can't recognize the volume on a floppy, it tries to reacquire * the volume information every time you ask it to read a sector. This makes * things *VERY* slow. The solution is to use the physical drive Int13h * services. These come in two variants, one of which will work on just * about any machine but only works with floppies. The other will work on * anything built since about 1996. * * Figuring out whether something is or isn't a floppy requires yet * another call. All things considered it's quite an ordeal. The block I/O * functions are wrapped up in classes so nobody else has to worry about all * this mess. * * Implementation note: this class is broken down by how the devices are * opened, e.g. logical, physical, or ASPI address. Breaking it down by device * type seems more appropriate, but Win98 vs Win2K can require completely * different approaches (e.g. physical vs. logical for floppy disk, logical * vs. ASPI for CD-ROM). There is no perfect decomposition. * * Summary: * Win9x/ME physical drive: Int13h (doesn't work for hard drives) * Win9x/ME logical drive: Int21h/7305h * Win9x/ME SCSI drive or CD-ROM drive: ASPI * Win2K/XP physical drive: CreateFile("\\.\PhysicalDriveN") * Win2K/XP logical drive: CreateFile("\\.\X") * Win2K/XP SCSI drive or CD-ROM drive: SPTI */ #ifndef DISKIMG_WIN32BLOCKIO_H #define DISKIMG_WIN32BLOCKIO_H namespace DiskImgLib { extern bool IsWin9x(void); /* * Cache a contiguous set of blocks. This was originally motivated by poor * write performance, but that problem was largely solved in other ways. * It's still handy to write an entire track at once under Win98 though. * * Only storing continuous runs of blocks makes the cache less useful, but * much easier to write, and hence less likely to break in unpleasant ways. * * This class just manages the blocks. The FlushCache() function in * Win32LogicalVolume is responsible for actually pushing the writes through. * * (I'm not entirely happy with this, especially since it doesn't take into * account the underlying device block size. This could've been a good place * to handle the 2048-byte CD-ROM block size, rather than caching it again in * the CD-ROM handler.) */ class CBCache { public: CBCache(void) : fFirstBlock(kEmpty), fNumBlocks(0) { for (int i = 0; i < kMaxCachedBlocks; i++) fDirty[i] = false; } virtual ~CBCache(void) { Purge(); } enum { kEmpty = -1 }; // is the block we want in the cache? bool IsBlockInCache(long blockNum) const; // read block out of cache (after verifying that it's present) DIError GetFromCache(long blockNum, void* buf); // can the cache store this block? bool IsRoomInCache(long blockNum) const; // write block to cache (after verifying that it will fit) DIError PutInCache(long blockNum, const void* buf, bool isDirty); // are there any dirty blocks in the cache? bool IsDirty(void) const; // get start, count, and buffer so we can write the cached data void GetCachePointer(long* pFirstBlock, int* pNumBlocks, void** pBuf) const; // clear all the dirty flags void Scrub(void); // purge all cache entries (ideally after writing w/help from GetCachePtr) void Purge(void); private: enum { kMaxCachedBlocks = 18, // one track on 1.4MB floppy kBlockSize = 512, // must match with Win32LogicalVolume:: }; long fFirstBlock; // set to kEmpty when cache is empty int fNumBlocks; bool fDirty[kMaxCachedBlocks]; unsigned char fCache[kMaxCachedBlocks * kBlockSize]; }; /* * This class encapsulates block access to a logical or physical volume. */ class Win32VolumeAccess { public: Win32VolumeAccess(void) : fTotalBlocks(-1), fpBlockAccess(NULL) {} virtual ~Win32VolumeAccess(void) { if (fpBlockAccess != NULL) { FlushCache(true); fpBlockAccess->Close(); } } // "deviceName" has the form "X:\" (logical), "81:\" (physical), or // "ASPI:x:y:z\" (ASPI) DIError Open(const WCHAR* deviceName, bool readOnly); // close the device void Close(void); // is the device open and working? bool Ready(void) const { return fpBlockAccess != NULL; } // return the volume's EOF long GetTotalBlocks(void) const { return fTotalBlocks; } // return the block size for this volume (always a power of 2) int GetBlockSize(void) const { return BlockAccess::kBlockSize; } // read one or more consecutive blocks DIError ReadBlocks(long startBlock, short blockCount, void* buf); // write one or more consecutive blocks DIError WriteBlocks(long startBlock, short blockCount, const void* buf); // flush our internal cache DIError FlushCache(bool purge); private: /* * Abstract base class with some handy functions. */ class BlockAccess { public: BlockAccess(void) { fIsWin9x = DiskImgLib::IsWin9x(); } virtual ~BlockAccess(void) {} typedef struct { int numCyls; int numHeads; int numSectors; long blockCount; // total #of blocks on this kind of disk } DiskGeometry; // generic interfaces virtual DIError Open(const WCHAR* deviceName, bool readOnly) = 0; virtual DIError DetectCapacity(long* pNumBlocks) = 0; virtual DIError ReadBlocks(long startBlock, short blockCount, void* buf) = 0; virtual DIError WriteBlocks(long startBlock, short blockCount, const void* buf) = 0; virtual DIError Close(void) = 0; static bool BlockToCylinderHeadSector(long blockNum, const DiskGeometry* pGeometry, int* pCylinder, int* pHead, int* pSector, long* pLastBlockOnTrack); enum { kNumLogicalVolumes = 26, // A-Z kBlockSize = 512, kCDROMSectorSize = 2048, kMaxFloppyRetries = 3, // retry floppy reads/writes }; // BIOS floppy disk drive type; doubles here as media type typedef enum { kFloppyUnknown = 0, kFloppy525_360 = 1, kFloppy525_1200 = 2, kFloppy35_720 = 3, kFloppy35_1440 = 4, kFloppy35_2880 = 5, kFloppyMax } FloppyKind; protected: static DIError GetFloppyDriveKind(HANDLE handle, int unitNum, FloppyKind* pKind); // detect the #of blocks on the volume static DIError ScanCapacity(BlockAccess* pThis, long* pNumBlocks); // determine whether a block is readable static bool CanReadBlock(BlockAccess* pThis, long blockNum); // try to detect device capacity using SPTI DIError DetectCapacitySPTI(HANDLE handle, bool isCDROM, long* pNumBlocks); static int ReadBlocksInt13h(HANDLE handle, int unitNum, int cylinder, int head, int sector, short blockCount, void* buf); static DIError ReadBlocksInt13h(HANDLE handle, int unitNum, const DiskGeometry* pGeometry, long startBlock, short blockCount, void* buf); static int WriteBlocksInt13h(HANDLE handle, int unitNum, int cylinder, int head, int sector, short blockCount, const void* buf); static DIError WriteBlocksInt13h(HANDLE handle, int unitNum, const DiskGeometry* pGeometry, long startBlock, short blockCount, const void* buf); static DIError ReadBlocksInt21h(HANDLE handle, int driveNum, long startBlock, short blockCount, void* buf); static DIError WriteBlocksInt21h(HANDLE handle, int driveNum, long startBlock, short blockCount, const void* buf); static DIError ReadBlocksWin2K(HANDLE handle, long startBlock, short blockCount, void* buf); static DIError WriteBlocksWin2K(HANDLE handle, long startBlock, short blockCount, const void* buf); bool fIsWin9x; // Win9x/ME=true, Win2K/XP=false }; /* * Access to a logical volume (e.g. "C:\") under Win9x and Win2K/XP. */ class LogicalBlockAccess : public BlockAccess { public: LogicalBlockAccess(void) : fHandle(NULL), fIsCDROM(false), fDriveNum(-1), fLastSectorCache(NULL), fLastSectorNum(-1) {} virtual ~LogicalBlockAccess(void) { if (fHandle != NULL) { //LOGI("HEY: LogicalBlockAccess: forcing close"); Close(); } delete[] fLastSectorCache; } virtual DIError Open(const WCHAR* deviceName, bool readOnly); virtual DIError DetectCapacity(long* pNumBlocks) { /* use SCSI length value if at all possible */ DIError dierr; dierr = DetectCapacitySPTI(fHandle, fIsCDROM, pNumBlocks); if (fIsCDROM) return dierr; // SPTI should always work for CD-ROM if (dierr != kDIErrNone) return ScanCapacity(this, pNumBlocks); // fall back on scan else return dierr; } virtual DIError ReadBlocks(long startBlock, short blockCount, void* buf) { if (fIsCDROM) return ReadBlocksCDROM(fHandle, startBlock, blockCount, buf); if (fIsWin9x) return ReadBlocksInt21h(fHandle, fDriveNum, startBlock, blockCount, buf); else return ReadBlocksWin2K(fHandle, startBlock, blockCount, buf); } virtual DIError WriteBlocks(long startBlock, short blockCount, const void* buf) { if (fIsCDROM) return kDIErrWriteProtected; if (fIsWin9x) return WriteBlocksInt21h(fHandle, fDriveNum, startBlock, blockCount, buf); else return WriteBlocksWin2K(fHandle, startBlock, blockCount, buf); } virtual DIError Close(void); private: //DIError DetectCapacitySPTI(long* pNumBlocks); DIError ReadBlocksCDROM(HANDLE handle, long startBlock, short numBlocks, void* buf); // Win2K/XP and Win9x/ME HANDLE fHandle; bool fIsCDROM; // set for CD-ROM devices // Win9x/ME int fDriveNum; // 1=A, 3=C, etc // CD-ROM goodies unsigned char* fLastSectorCache; long fLastSectorNum; }; /* * Access to a physical volume (e.g. 00h or 80h) under Win9x and * Win2K/XP. */ class PhysicalBlockAccess : public BlockAccess { public: PhysicalBlockAccess(void) : fHandle(NULL), fInt13Unit(-1), fFloppyKind(kFloppyUnknown) {} virtual ~PhysicalBlockAccess(void) {} virtual DIError Open(const WCHAR* deviceName, bool readOnly); virtual DIError DetectCapacity(long* pNumBlocks) { /* try SPTI in case it happens to work */ DIError dierr; dierr = DetectCapacitySPTI(fHandle, false, pNumBlocks); if (dierr != kDIErrNone) return ScanCapacity(this, pNumBlocks); else return dierr; } virtual DIError ReadBlocks(long startBlock, short blockCount, void* buf) { if (fIsWin9x) return ReadBlocksInt13h(fHandle, fInt13Unit, &fGeometry, startBlock, blockCount, buf); else return ReadBlocksWin2K(fHandle, startBlock, blockCount, buf); } virtual DIError WriteBlocks(long startBlock, short blockCount, const void* buf) { if (fIsWin9x) return WriteBlocksInt13h(fHandle, fInt13Unit, &fGeometry, startBlock, blockCount, buf); else return WriteBlocksWin2K(fHandle, startBlock, blockCount, buf); } virtual DIError Close(void); private: DIError DetectFloppyGeometry(void); // Win2K/XP HANDLE fHandle; // Win9x/ME int fInt13Unit; // 00h=floppy #1, 80h=HD#1 FloppyKind fFloppyKind; DiskGeometry fGeometry; }; #ifdef WANT_ASPI /* * Access to a SCSI volume via the ASPI interface. */ class ASPIBlockAccess : public BlockAccess { public: ASPIBlockAccess(void) : fpASPI(NULL), fAdapter(0xff), fTarget(0xff), fLun(0xff), fReadOnly(false), fLastChunkCache(NULL), fLastChunkNum(-1), fChunkSize(-1) {} virtual ~ASPIBlockAccess(void) { delete[] fLastChunkCache; } virtual DIError Open(const char* deviceName, bool readOnly); virtual DIError DetectCapacity(long* pNumBlocks); virtual DIError ReadBlocks(long startBlock, short blockCount, void* buf); virtual DIError WriteBlocks(long startBlock, short blockCount, const void* buf); virtual DIError Close(void); private: int ExtractInt(const char** pStr, int* pResult); ASPI* fpASPI; unsigned char fAdapter; unsigned char fTarget; unsigned char fLun; bool fReadOnly; // block cache unsigned char* fLastChunkCache; long fLastChunkNum; long fChunkSize; // set by DetectCapacity }; #endif /*WANT_ASPI*/ // write a series of blocks to the volume DIError DoWriteBlocks(long startBlock, short blockCount, const void* buf) { return fpBlockAccess->WriteBlocks(startBlock, blockCount, buf); } long fTotalBlocks; BlockAccess* fpBlockAccess; // really LogicalBA or PhysicalBA CBCache fBlockCache; }; }; // namespace DiskImgLib #endif /*DISKIMG_WIN32BLOCKIO_H*/ #endif /*_WIN32*/