ciderpress/diskimg/Win32BlockIO.cpp

2165 lines
58 KiB
C++
Raw Normal View History

2007-03-27 17:47:10 +00:00
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Win32 block I/O routines.
*
* See the header file for an explanation of why.
*/
#include "StdAfx.h"
#ifdef _WIN32
#include "DiskImgPriv.h"
#include "SCSIDefs.h"
#include "ASPI.h"
#include "SPTI.h"
/*
* This is an ugly hack until I can figure out the right way to do this. To
* help prevent people from trashing their Windows volumes, we refuse to
* open "C:\" or physical0 for writing. On most systems, this is fine.
*
* The problem is that, on some systems with a mix of SATA and IDE devices,
* the boot disk is not physical disk 0. There should be a way to determine
* which physical disk is used for booting, or which physical disk
* corresponds to "C:", but I haven't been able to find it.
*
* So, for now, allow a global setting that disables the protection. I'm
* doing it this way rather than passing a parameter through because it
* requires adding an argument to several layers of "open", and I'm hoping
* to make this go away.
*/
//extern bool DiskImgLib::gAllowWritePhys0;
/*
* ===========================================================================
* Win32VolumeAccess
* ===========================================================================
*/
/*
* Open a logical volume.
*/
DIError
Win32VolumeAccess::Open(const char* deviceName, bool readOnly)
{
DIError dierr = kDIErrNone;
assert(deviceName != nil);
if (fpBlockAccess != nil) {
assert(false);
return kDIErrAlreadyOpen;
}
if (strncmp(deviceName, kASPIDev, strlen(kASPIDev)) == 0) {
fpBlockAccess = new ASPIBlockAccess;
if (fpBlockAccess == nil) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = fpBlockAccess->Open(deviceName, readOnly);
if (dierr != kDIErrNone)
goto bail;
} else if (deviceName[0] >= 'A' && deviceName[0] <= 'Z') {
fpBlockAccess = new LogicalBlockAccess;
if (fpBlockAccess == nil) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = fpBlockAccess->Open(deviceName, readOnly);
if (dierr != kDIErrNone)
goto bail;
} else if (deviceName[0] >= '0' && deviceName[0] <= '9') {
fpBlockAccess = new PhysicalBlockAccess;
if (fpBlockAccess == nil) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = fpBlockAccess->Open(deviceName, readOnly);
if (dierr != kDIErrNone)
goto bail;
} else {
WMSG1(" Win32VA: '%s' isn't the name of a device\n", deviceName);
return kDIErrInternal;
}
// Need to do this now so we can use floppy geometry.
dierr = fpBlockAccess->DetectCapacity(&fTotalBlocks);
if (dierr != kDIErrNone)
goto bail;
assert(fTotalBlocks >= 0);
bail:
if (dierr != kDIErrNone) {
delete fpBlockAccess;
fpBlockAccess = nil;
}
return dierr;
}
/*
* Close the device.
*/
void
Win32VolumeAccess::Close(void)
{
if (fpBlockAccess != nil) {
DIError dierr;
WMSG0(" Win32VolumeAccess closing\n");
dierr = FlushCache(true);
if (dierr != kDIErrNone) {
WMSG1("WARNING: Win32VA Close: FlushCache failed (err=%ld)\n",
dierr);
// not much we can do
}
dierr = fpBlockAccess->Close();
if (dierr != kDIErrNone) {
WMSG1("WARNING: Win32VolumeAccess BlockAccess Close failed (err=%ld)\n",
dierr);
}
delete fpBlockAccess;
fpBlockAccess = nil;
}
}
/*
* Read a range of blocks from the device.
*
* Because some things like to read partial blocks, we cache the last block
* we read whenever the caller asks for a single block. This results in
* increased data copying, but since we know we're reading from a logical
* volume it's safe to assume that memory is *many* times faster than
* reading from this handle.
*
* Returns with an error if any of the blocks could not be read.
*/
DIError
Win32VolumeAccess::ReadBlocks(long startBlock, short blockCount,
void* buf)
{
DIError dierr = kDIErrNone;
assert(startBlock >= 0);
assert(blockCount > 0);
assert(buf != nil);
assert(fpBlockAccess != nil);
if (blockCount == 1) {
if (fBlockCache.IsBlockInCache(startBlock)) {
fBlockCache.GetFromCache(startBlock, buf);
return kDIErrNone;
}
} else {
// If they're reading in large stretches, we don't need to use
// the cache. There is some chance that what we're about to
// read might include dirty blocks currently in the cache, so
// we flush what we have before the read.
dierr = FlushCache(true);
if (dierr != kDIErrNone)
return dierr;
}
/* go read from the volume */
dierr = fpBlockAccess->ReadBlocks(startBlock, blockCount, buf);
if (dierr != kDIErrNone)
goto bail;
/* if we're doing single-block reads, put it in the cache */
if (blockCount == 1) {
if (!fBlockCache.IsRoomInCache(startBlock)) {
dierr = FlushCache(true); // make room
if (dierr != kDIErrNone)
goto bail;
}
// after flushing, this should never fail
dierr = fBlockCache.PutInCache(startBlock, buf, false);
if (dierr != kDIErrNone)
goto bail;
}
bail:
return dierr;
}
/*
* Write a range of blocks to the device. For the most part this just
* writes to the cache.
*
* Returns with an error if any of the blocks could not be read.
*/
DIError
Win32VolumeAccess::WriteBlocks(long startBlock, short blockCount,
const void* buf)
{
DIError dierr = kDIErrNone;
assert(startBlock >= 0);
assert(blockCount > 0);
assert(buf != nil);
if (blockCount == 1) {
/* is this block already in the cache? */
if (!fBlockCache.IsBlockInCache(startBlock)) {
/* not present, make sure it fits */
if (!fBlockCache.IsRoomInCache(startBlock)) {
dierr = FlushCache(true); // make room
if (dierr != kDIErrNone)
goto bail;
}
}
// after flushing, this should never fail
dierr = fBlockCache.PutInCache(startBlock, buf, true);
if (dierr != kDIErrNone)
goto bail;
} else {
// If they're writing in large stretches, we don't need to use
// the cache. We need to flush the cache in case what we're about
// to write would overwrite something in the cache -- if we don't,
// what's in the cache will effectively revert what we're about to
// write.
dierr = FlushCache(true);
if (dierr != kDIErrNone)
goto bail;
dierr = DoWriteBlocks(startBlock, blockCount, buf);
if (dierr != kDIErrNone)
goto bail;
}
bail:
return dierr;
}
/*
* Write all blocks in the cache to disk if any of them are dirty. In some
* ways this is wasteful -- we could be writing stuff that isn't dirty,
* which isn't a great idea on (say) a CF volume -- but in practice we
* don't mix a lot of adjacent reads and writes, so this is pretty
* harmless.
*
* The goal was to write whole tracks on floppies. It's easy enough to
* disable the cache for CF devices if need be.
*
* If "purge" is set, we discard the blocks after writing them.
*/
DIError
Win32VolumeAccess::FlushCache(bool purge)
{
DIError dierr = kDIErrNone;
//WMSG1(" Win32VA: FlushCache (%d)\n", purge);
if (fBlockCache.IsDirty()) {
long firstBlock;
int numBlocks;
void* buf;
fBlockCache.GetCachePointer(&firstBlock, &numBlocks, &buf);
WMSG3("FlushCache writing first=%d num=%d (purge=%d)\n",
firstBlock, numBlocks, purge);
dierr = DoWriteBlocks(firstBlock, numBlocks, buf);
if (dierr != kDIErrNone) {
WMSG1(" Win32VA: FlushCache write blocks failed (err=%d)\n",
dierr);
goto bail;
}
// all written, clear the dirty flags
fBlockCache.Scrub();
}
if (purge)
fBlockCache.Purge();
bail:
return dierr;
}
/*
* ===========================================================================
* BlockAccess
* ===========================================================================
*/
/*
* Detect the capacity of a drive using the SCSI READ CAPACITY command. Only
* works on CD-ROM drives and SCSI devices.
*
* Unfortunately, if you're accessing a hard drive through the BIOS, SPTI
* doesn't work. There must be a better way.
*
* On success, "*pNumBlocks" gets the number of 512-byte blocks.
*/
DIError
Win32VolumeAccess::BlockAccess::DetectCapacitySPTI(HANDLE handle,
bool isCDROM, long* pNumBlocks)
{
#ifndef HAVE_WINDOWS_CDROM
if (isCDROM)
return kDIErrCDROMNotSupported;
#endif
DIError dierr = kDIErrNone;
unsigned long lba, blockLen;
dierr = SPTI::GetDeviceCapacity(handle, &lba, &blockLen);
if (dierr != kDIErrNone)
goto bail;
WMSG3("READ CAPACITY reports lba=%lu blockLen=%lu (total=%lu)\n",
lba, blockLen, lba*blockLen);
if (isCDROM && blockLen != kCDROMSectorSize) {
WMSG1("Unacceptable CD-ROM blockLen=%ld, bailing\n", blockLen);
dierr = kDIErrReadFailed;
goto bail;
}
// The LBA is the last valid block on the disk. To get the disk size,
// we need to add one.
*pNumBlocks = (blockLen/512) * (lba+1);
WMSG1(" SPTI returning 512-byte block count %ld\n", *pNumBlocks);
bail:
return dierr;
}
/*
* Figure out how large this disk volume is by probing for readable blocks.
* We take some guesses for common sizes and then binary-search if necessary.
*
* CF cards typically don't have as much space as they're rated for, possibly
* because of bad-block mapping (either bad blocks or space reserved for when
* blocks do go bad).
*
* This sets "*pNumBlocks" on success. The largest size this will detect
* is currently 8GB.
*/
/*static*/ DIError
Win32VolumeAccess::BlockAccess::ScanCapacity(BlockAccess* pThis, long* pNumBlocks)
{
DIError dierr = kDIErrNone;
// max out at 8GB (-1 block)
const long kLargest = DiskImgLib::kVolumeMaxBlocks;
const long kTypicalSizes[] = {
// these must be in ascending order
720*1024 / BlockAccess::kBlockSize, // 720K floppy
1440*1024 / BlockAccess::kBlockSize, // 1.44MB floppy
32*1024*1024 / BlockAccess::kBlockSize, // 32MB flash card
64*1024*1024 / BlockAccess::kBlockSize, // 64MB flash card
128*1024*1024 / BlockAccess::kBlockSize, // 128MB flash card
256*1024*1024 / BlockAccess::kBlockSize, // 256MB flash card
//512*1024*1024 / BlockAccess::kBlockSize, // 512MB flash card
2*1024*1024*(1024/BlockAccess::kBlockSize), // 2GB mark
kLargest
};
long totalBlocks = 0;
int i;
// Trivial check to make sure *anything* works, and to establish block 0
// as valid in case we have to bin-search.
if (!CanReadBlock(pThis, 0)) {
WMSG0(" Win32VolumeAccess: can't read block 0\n");
dierr = kDIErrReadFailed;
goto bail;
}
for (i = 0; i < NELEM(kTypicalSizes); i++) {
if (!CanReadBlock(pThis, kTypicalSizes[i])) {
/* failed reading, see if N-1 is readable */
if (CanReadBlock(pThis, kTypicalSizes[i] - 1)) {
/* found it */
totalBlocks = kTypicalSizes[i];
break;
} else {
/* we overshot, binary-search backwards */
WMSG1("OVERSHOT at %ld\n", kTypicalSizes[i]);
long good, bad;
if (i == 0)
good = 0;
else
good = kTypicalSizes[i-1]; // know this one is good
bad = kTypicalSizes[i]-1; // know this one is bad
while (good != bad-1) {
long check = (good + bad) / 2;
assert(check > good);
assert(check < bad);
if (CanReadBlock(pThis, check))
good = check;
else
bad = check;
}
totalBlocks = bad;
break;
}
}
}
if (totalBlocks == 0) {
if (i == NELEM(kTypicalSizes)) {
/* huge volume, we never found a bad block */
totalBlocks = kLargest;
} else {
/* we never found a good block */
WMSG0(" Win32VolumeAccess unable to determine size\n");
dierr = kDIErrReadFailed;
goto bail;
}
}
if (totalBlocks > (3 * 1024 * 1024) / BlockAccess::kBlockSize) {
WMSG2(" GFDWinVolume: size is %ld (%.2fMB)\n",
totalBlocks, (float) totalBlocks / (2048.0));
} else {
WMSG2(" GFDWinVolume: size is %ld (%.2fKB)\n",
totalBlocks, (float) totalBlocks / 2.0);
}
*pNumBlocks = totalBlocks;
bail:
return dierr;
}
/*
* Figure out if the block at "blockNum" exists.
*/
/*static*/ bool
Win32VolumeAccess::BlockAccess::CanReadBlock(BlockAccess* pThis, long blockNum)
{
DIError dierr;
unsigned char buf[BlockAccess::kBlockSize];
dierr = pThis->ReadBlocks(blockNum, 1, buf);
if (dierr == kDIErrNone) {
WMSG1(" +++ Checked %ld (good)\n", blockNum);
return true;
} else {
WMSG1(" +++ Checked %ld (bad)\n", blockNum);
return false;
}
}
#ifndef INVALID_SET_FILE_POINTER
# define INVALID_SET_FILE_POINTER 0xFFFFFFFF
#endif
/*
* Most of these definitions come from MSFT knowledge base article #174569,
* "BUG: Int 21 Read/Write Track on Logical Drive Fails on OSR2 and Later".
*/
#define VWIN32_DIOC_DOS_IOCTL 1 // Int 21h 4400h through 4411h
#define VWIN32_DIOC_DOS_INT25 2
#define VWIN32_DIOC_DOS_INT26 3
#define VWIN32_DIOC_DOS_INT13 4
#define VWIN32_DIOC_DOS_DRIVEINFO 6 // Int 21h 730x commands
typedef struct _DIOC_REGISTERS {
DWORD reg_EBX;
DWORD reg_EDX;
DWORD reg_ECX;
DWORD reg_EAX;
DWORD reg_EDI;
DWORD reg_ESI;
DWORD reg_Flags;
} DIOC_REGISTERS, *PDIOC_REGISTERS;
#define CARRY_FLAG 1
#pragma pack(1)
typedef struct _DISKIO {
DWORD dwStartSector; // starting logical sector number
WORD wSectors; // number of sectors
DWORD dwBuffer; // address of read/write buffer
} DISKIO, *PDISKIO;
typedef struct _DRIVEMAPINFO {
BYTE dmiAllocationLength;
BYTE dmiInfoLength;
BYTE dmiFlags;
BYTE dmiInt13Unit;
DWORD dmiAssociatedDriveMap;
DWORD dmiPartitionStartRBA_lo;
DWORD dmiPartitionStartRBA_hi;
} DRIVEMAPINFO, *PDRIVEMAPINFO;
#pragma pack()
#define kInt13StatusMissingAddrMark 2 // sector number above media format
#define kInt13StatusWriteProtected 3 // disk is write protected
#define kInt13StatusSectorNotFound 4 // sector number above drive cap
// == 10 for bad blocks??
#define kInt13StatusTimeout 128 // drive not responding
#if 0
/*
* Determine the mapping between a logical device number (A=1, B=2, etc)
* and the Int13h unit number (floppy=00h, hard drive=80h, etc).
*
* Pass in the vwin32 handle.
*/
/*static*/ DIError
Win32VolumeAccess::BlockAccess::GetInt13Unit(HANDLE handle, int driveNum, int* pInt13Unit)
{
DIError dierr = kDIErrNone;
BOOL result;
DWORD cb;
DRIVEMAPINFO driveMapInfo = {0};
DIOC_REGISTERS reg = {0};
const int kGetDriveMapInfo = 0x6f;
const int kDeviceCategory1 = 0x08; // for older stuff
const int kDeviceCategory2 = 0x48; // for FAT32
assert(handle != nil);
assert(driveNum > 0 && driveNum <= kNumLogicalVolumes);
assert(pInt13Unit != nil);
*pInt13Unit = -1;
driveMapInfo.dmiAllocationLength = sizeof(DRIVEMAPINFO); // should be 16
reg.reg_EAX = 0x440d; // generic IOCTL
reg.reg_EBX = driveNum;
reg.reg_ECX = MAKEWORD(kGetDriveMapInfo, kDeviceCategory1);
reg.reg_EDX = (DWORD) &driveMapInfo;
result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_IOCTL,
&reg, sizeof(reg),
&reg, sizeof(reg), &cb, 0);
if (result == 0) {
WMSG1(" DeviceIoControl(Int21h, 6fh) FAILED (err=%ld)\n",
GetLastError());
dierr = LastErrorToDIError();
goto bail;
}
if (reg.reg_Flags & CARRY_FLAG) {
WMSG1(" --- retrying GDMI with alternate device category (ax=%ld)\n",
reg.reg_EAX);
reg.reg_EAX = 0x440d; // generic IOCTL
reg.reg_EBX = driveNum;
reg.reg_ECX = MAKEWORD(kGetDriveMapInfo, kDeviceCategory2);
reg.reg_EDX = (DWORD) &driveMapInfo;
result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_IOCTL,
&reg, sizeof(reg),
&reg, sizeof(reg), &cb, 0);
}
if (result == 0) {
WMSG1(" DeviceIoControl(Int21h, 6fh)(retry) FAILED (err=%ld)\n",
GetLastError());
dierr = LastErrorToDIError();
goto bail;
}
if (reg.reg_Flags & CARRY_FLAG) {
WMSG1(" --- GetDriveMapInfo call (retry) failed (ax=%ld)\n",
reg.reg_EAX);
dierr = kDIErrReadFailed; // close enough
goto bail;
}
WMSG3(" +++ DriveMapInfo len=%d flags=%d unit=%d\n",
driveMapInfo.dmiInfoLength, driveMapInfo.dmiFlags,
driveMapInfo.dmiInt13Unit);
WMSG3(" +++ driveMap=0x%08lx RBA=0x%08lx 0x%08lx\n",
driveMapInfo.dmiAssociatedDriveMap,
driveMapInfo.dmiPartitionStartRBA_hi,
driveMapInfo.dmiPartitionStartRBA_lo);
if (driveMapInfo.dmiInfoLength < 4) {
/* results not covered in reply? */
dierr = kDIErrReadFailed; // not close, but it'll do
goto bail;
}
*pInt13Unit = driveMapInfo.dmiInt13Unit;
bail:
return dierr;
}
#endif
#if 0
/*
* Look up the geometry for a floppy disk whose total size is "totalBlocks".
* There is no BIOS function to detect the media size and geometry, so
* we have to do it this way. PC "FAT" disks have a size indication
* in the boot block, but we can't rely on that.
*
* Returns "true" if the geometry is known, "false" otherwise. When "true"
* is returned, "*pNumTracks", "*pNumHeads", and "*pNumSectors" will receive
* values if the pointers are non-nil.
*/
/*static*/ bool
Win32VolumeAccess::BlockAccess::LookupFloppyGeometry(long totalBlocks,
DiskGeometry* pGeometry)
{
static const struct {
FloppyKind kind;
long blockCount; // total #of blocks on the disk
int numCyls; // #of cylinders
int numHeads; // #of heads per cylinder
int numSectors; // #of sectors/track
} floppyGeometry[] = {
{ kFloppyUnknown, -1, -1, -1, -1 },
{ kFloppy525_360, 360*2, 40, 2, 9 },
{ kFloppy525_1200, 1200*2, 80, 2, 15 },
{ kFloppy35_720, 720*2, 80, 2, 9 },
{ kFloppy35_1440, 1440*2, 80, 2, 18 },
{ kFloppy35_2880, 2880*2, 80, 2, 36 }
};
/* verify that we can directly index the table with the enum */
for (int chk = 0; chk < NELEM(floppyGeometry); chk++) {
assert(floppyGeometry[chk].kind == chk);
}
assert(chk == kFloppyMax);
if (totalBlocks <= 0) {
// still auto-detecting volume size?
return false;
}
int i;
for (i = 0; i < NELEM(floppyGeometry); i++)
if (floppyGeometry[i].blockCount == totalBlocks)
break;
if (i == NELEM(floppyGeometry)) {
WMSG1( "GetFloppyGeometry: no match for blocks=%ld\n", totalBlocks);
return false;
}
pGeometry->numCyls = floppyGeometry[i].numCyls;
pGeometry->numHeads = floppyGeometry[i].numHeads;
pGeometry->numSectors = floppyGeometry[i].numSectors;
return true;
}
#endif
/*
* Convert a block number to a cylinder/head/sector offset. Also figures
* out what the last block on the current track is. Sectors are returned
* in 1-based form.
*
* Returns "true" on success, "false" on failure.
*/
/*static*/ bool
Win32VolumeAccess::BlockAccess::BlockToCylinderHeadSector(long blockNum,
const DiskGeometry* pGeometry, int* pCylinder, int* pHead,
int* pSector, long* pLastBlockOnTrack)
{
int cylinder, head, sector;
long lastBlockOnTrack;
int leftOver;
cylinder = blockNum / (pGeometry->numSectors * pGeometry->numHeads);
leftOver = blockNum - cylinder * (pGeometry->numSectors * pGeometry->numHeads);
head = leftOver / pGeometry->numSectors;
sector = leftOver - (head * pGeometry->numSectors);
assert(cylinder >= 0 && cylinder < pGeometry->numCyls);
assert(head >= 0 && head < pGeometry->numHeads);
assert(sector >= 0 && sector < pGeometry->numSectors);
lastBlockOnTrack = blockNum + (pGeometry->numSectors - sector -1);
if (pCylinder != nil)
*pCylinder = cylinder;
if (pHead != nil)
*pHead = head;
if (pSector != nil)
*pSector = sector+1;
if (pLastBlockOnTrack != nil)
*pLastBlockOnTrack = lastBlockOnTrack;
return true;
}
/*
* Get the floppy drive kind (*not* the media kind) using Int13h func 8.
*/
/*static*/ DIError
Win32VolumeAccess::BlockAccess::GetFloppyDriveKind(HANDLE handle, int unitNum,
FloppyKind* pKind)
{
DIOC_REGISTERS reg = {0};
DWORD cb;
BOOL result;
reg.reg_EAX = MAKEWORD(0, 0x08); // Read Diskette Drive Parameters
reg.reg_EDX = MAKEWORD(unitNum, 0);
result = DeviceIoControl(handle, VWIN32_DIOC_DOS_INT13, &reg,
sizeof(reg), &reg, sizeof(reg), &cb, 0);
if (result == 0 || (reg.reg_Flags & CARRY_FLAG)) {
WMSG3(" GetFloppyKind failed: result=%d flags=0x%04x AH=%d\n",
result, reg.reg_Flags, HIBYTE(reg.reg_EAX));
return kDIErrGeneric;
}
int bl = LOBYTE(reg.reg_EBX);
if (bl > 0 && bl < 6)
*pKind = (FloppyKind) bl;
else {
WMSG1(" GetFloppyKind: unrecognized kind %d\n", bl);
return kDIErrGeneric;
}
return kDIErrNone;
}
/*
* Read one or more blocks using Int13h services. This only works on
* floppy drives due to Win9x limitations.
*
* The average BIOS will only read one or two tracks reliably, and may
* not work well when straddling tracks. It's up to the caller to
* ensure that the parameters are set properly.
*
* "cylinder" and "head" are 0-based, "sector" is 1-based.
*
* Returns 0 on success, the status code from AH on failure. If the call
* fails but AH is zero, -1 is returned.
*/
/*static*/ int
Win32VolumeAccess::BlockAccess::ReadBlocksInt13h(HANDLE handle, int unitNum,
int cylinder, int head, int sector, short blockCount, void* buf)
{
DIOC_REGISTERS reg = {0};
DWORD cb;
BOOL result;
if (unitNum < 0 || unitNum >= 4) {
assert(false);
return kDIErrInternal;
}
for (int retry = 0; retry < kMaxFloppyRetries; retry++) {
reg.reg_EAX = MAKEWORD(blockCount, 0x02); // read N sectors
reg.reg_EBX = (DWORD) buf;
reg.reg_ECX = MAKEWORD(sector, cylinder);
reg.reg_EDX = MAKEWORD(unitNum, head);
//WMSG4(" DIOC Int13h read c=%d h=%d s=%d rc=%d\n",
// cylinder, head, sector, blockCount);
result = DeviceIoControl(handle, VWIN32_DIOC_DOS_INT13, &reg,
sizeof(reg), &reg, sizeof(reg), &cb, 0);
if (result != 0 && !(reg.reg_Flags & CARRY_FLAG))
break; // success!
/* if it's an invalid sector request, bail out immediately */
if (HIBYTE(reg.reg_EAX) == kInt13StatusSectorNotFound ||
HIBYTE(reg.reg_EAX) == kInt13StatusMissingAddrMark)
{
break;
}
WMSG1(" DIOC soft read failure, ax=0x%08lx\n", reg.reg_EAX);
}
if (!result || (reg.reg_Flags & CARRY_FLAG)) {
int ah = HIBYTE(reg.reg_EAX);
WMSG2(" DIOC read failed, result=%d ah=%ld\n", result, ah);
if (ah != 0)
return ah;
else
return -1;
}
return 0;
}
/*
* Read one or more blocks using Int13h services. This only works on
* floppy drives due to Win9x limitations.
*
* It's important to be able to read multiple blocks for performance
* reasons. Because this is fairly "raw", we have to retry it 3x before
* giving up.
*/
/*static*/ DIError
Win32VolumeAccess::BlockAccess::ReadBlocksInt13h(HANDLE handle, int unitNum,
const DiskGeometry* pGeometry, long startBlock, short blockCount,
void* buf)
{
int cylinder, head, sector;
int status, runCount;
long lastBlockOnTrack;
if (unitNum < 0 || unitNum >= 4) {
assert(false);
return kDIErrInternal;
}
if (startBlock < 0 || blockCount <= 0)
return kDIErrInvalidArg;
if (startBlock + blockCount > pGeometry->blockCount) {
WMSG2(" ReadInt13h: invalid request for start=%ld count=%d\n",
startBlock, blockCount);
return kDIErrReadFailed;
}
while (blockCount) {
int result;
result = BlockToCylinderHeadSector(startBlock, pGeometry,
&cylinder, &head, &sector, &lastBlockOnTrack);
assert(result);
/*
* According to "The Undocumented PC", the average BIOS will read
* at most one or two tracks reliably. It's really geared toward
* writing a single track or cylinder with one call. We want to
* be sure that our read doesn't straddle tracks, so we break it
* down as needed.
*/
runCount = lastBlockOnTrack - startBlock +1;
if (runCount > blockCount)
runCount = blockCount;
//WMSG3("R runCount=%d lastBlkOnT=%d start=%d\n",
// runCount, lastBlockOnTrack, startBlock);
assert(runCount > 0);
status = ReadBlocksInt13h(handle, unitNum, cylinder, head, sector,
runCount, buf);
if (status != 0) {
WMSG1(" DIOC read failed (status=%d)\n", status);
return kDIErrReadFailed;
}
startBlock += runCount;
blockCount -= runCount;
}
return kDIErrNone;
}
/*
* Write one or more blocks using Int13h services. This only works on
* floppy drives due to Win9x limitations.
*
* "cylinder" and "head" are 0-based, "sector" is 1-based.
*
* It's important to be able to write multiple blocks for performance
* reasons. Because this is fairly "raw", we have to retry it 3x before
* giving up.
*/
/*static*/ int
Win32VolumeAccess::BlockAccess::WriteBlocksInt13h(HANDLE handle, int unitNum,
int cylinder, int head, int sector, short blockCount,
const void* buf)
{
DIOC_REGISTERS reg = {0};
DWORD cb;
BOOL result;
for (int retry = 0; retry < kMaxFloppyRetries; retry++) {
reg.reg_EAX = MAKEWORD(blockCount, 0x03); // write N sectors
reg.reg_EBX = (DWORD) buf;
reg.reg_ECX = MAKEWORD(sector, cylinder);
reg.reg_EDX = MAKEWORD(unitNum, head);
WMSG4(" DIOC Int13h write c=%d h=%d s=%d rc=%d\n",
cylinder, head, sector, blockCount);
result = DeviceIoControl(handle, VWIN32_DIOC_DOS_INT13, &reg,
sizeof(reg), &reg, sizeof(reg), &cb, 0);
if (result != 0 && !(reg.reg_Flags & CARRY_FLAG))
break; // success!
if (HIBYTE(reg.reg_EAX) == kInt13StatusWriteProtected)
break; // no point retrying this
WMSG1(" DIOC soft write failure, ax=0x%08lx\n", reg.reg_EAX);
}
if (!result || (reg.reg_Flags & CARRY_FLAG)) {
int ah = HIBYTE(reg.reg_EAX);
WMSG2(" DIOC write failed, result=%d ah=%ld\n", result, ah);
if (ah != 0)
return ah;
else
return -1;
}
return 0;
}
/*
* Write one or more blocks using Int13h services. This only works on
* floppy drives.
*
* It's important to be able to write multiple blocks for performance
* reasons. Because this is fairly "raw", we have to retry it 3x before
* giving up.
*
* Returns "true" on success, "false" on failure.
*/
/*static*/ DIError
Win32VolumeAccess::BlockAccess::WriteBlocksInt13h(HANDLE handle, int unitNum,
const DiskGeometry* pGeometry, long startBlock, short blockCount,
const void* buf)
{
int cylinder, head, sector;
int status, runCount;
long lastBlockOnTrack;
// make sure this is a floppy drive and we know which unit it is
if (unitNum < 0 || unitNum >= 4) {
assert(false);
return kDIErrInternal;
}
if (startBlock < 0 || blockCount <= 0)
return kDIErrInvalidArg;
if (startBlock + blockCount > pGeometry->blockCount) {
WMSG2(" WriteInt13h: invalid request for start=%ld count=%d\n",
startBlock, blockCount);
return kDIErrWriteFailed;
}
while (blockCount) {
int result;
result = BlockToCylinderHeadSector(startBlock, pGeometry,
&cylinder, &head, &sector, &lastBlockOnTrack);
assert(result);
runCount = lastBlockOnTrack - startBlock +1;
if (runCount > blockCount)
runCount = blockCount;
//WMSG3("W runCount=%d lastBlkOnT=%d start=%d\n",
// runCount, lastBlockOnTrack, startBlock);
assert(runCount > 0);
status = WriteBlocksInt13h(handle, unitNum, cylinder, head, sector,
runCount, buf);
if (status != 0) {
WMSG1(" DIOC write failed (status=%d)\n", status);
if (status == kInt13StatusWriteProtected)
return kDIErrWriteProtected;
else
return kDIErrWriteFailed;
}
startBlock += runCount;
blockCount -= runCount;
}
return kDIErrNone;
}
/*
* Read blocks from a Win9x logical volume, using Int21h func 7305h. Pass in
* a handle to vwin32 and the logical drive number (A=1, B=2, etc).
*
* Works on Win95 OSR2 and later. Earlier versions require Int25 or Int13.
*/
/*static*/ DIError
Win32VolumeAccess::BlockAccess::ReadBlocksInt21h(HANDLE handle, int driveNum,
long startBlock, short blockCount, void* buf)
{
#if 0
assert(false); // discouraged
BOOL result;
DWORD cb;
DIOC_REGISTERS reg = {0};
DISKIO dio = {0};
dio.dwStartSector = startBlock;
dio.wSectors = (WORD) blockCount;
dio.dwBuffer = (DWORD) buf;
reg.reg_EAX = fDriveNum - 1; // Int 25h drive numbers are 0-based.
reg.reg_EBX = (DWORD)&dio;
reg.reg_ECX = 0xFFFF; // use DISKIO struct
WMSG3(" Int25 read start=%d count=%d\n",
startBlock, blockCount);
result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_INT25,
&reg, sizeof(reg),
&reg, sizeof(reg), &cb, 0);
// Determine if the DeviceIoControl call and the read succeeded.
result = result && !(reg.reg_Flags & CARRY_FLAG);
WMSG2(" +++ read from block %ld (result=%d)\n", startBlock, result);
if (!result)
return kDIErrReadFailed;
#else
BOOL result;
DWORD cb;
DIOC_REGISTERS reg = {0};
DISKIO dio = {0};
dio.dwStartSector = startBlock;
dio.wSectors = (WORD) blockCount;
dio.dwBuffer = (DWORD) buf;
reg.reg_EAX = 0x7305; // Ext_ABSDiskReadWrite
reg.reg_EBX = (DWORD)&dio;
reg.reg_ECX = -1;
reg.reg_EDX = driveNum; // Int 21h, fn 7305h drive numbers are 1-based
assert(reg.reg_ESI == 0); // read/write flag
WMSG2(" Int21/7305h read start=%d count=%d\n",
startBlock, blockCount);
result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_DRIVEINFO,
&reg, sizeof(reg),
&reg, sizeof(reg), &cb, 0);
// Determine if the DeviceIoControl call and the read succeeded.
result = result && !(reg.reg_Flags & CARRY_FLAG);
WMSG4(" +++ RB21h %ld %d (result=%d lastError=%ld)\n",
startBlock, blockCount, result, GetLastError());
if (!result)
return kDIErrReadFailed;
#endif
return kDIErrNone;
}
/*
* Write blocks to a Win9x logical volume. Pass in a handle to vwin32 and
* the logical drive number (A=1, B=2, etc).
*
* Works on Win95 OSR2 and later. Earlier versions require Int26 or Int13.
*/
/*static*/ DIError
Win32VolumeAccess::BlockAccess::WriteBlocksInt21h(HANDLE handle, int driveNum,
long startBlock, short blockCount, const void* buf)
{
BOOL result;
DWORD cb;
DIOC_REGISTERS reg = {0};
DISKIO dio = {0};
dio.dwStartSector = startBlock;
dio.wSectors = (WORD) blockCount;
dio.dwBuffer = (DWORD) buf;
reg.reg_EAX = 0x7305; // Ext_ABSDiskReadWrite
reg.reg_EBX = (DWORD)&dio;
reg.reg_ECX = -1;
reg.reg_EDX = driveNum; // Int 21h, fn 7305h drive numbers are 1-based
reg.reg_ESI = 0x6001; // write normal data (bit flags)
//WMSG2(" Int21/7305h write start=%d count=%d\n",
// startBlock, blockCount);
result = ::DeviceIoControl(handle, VWIN32_DIOC_DOS_DRIVEINFO,
&reg, sizeof(reg),
&reg, sizeof(reg), &cb, 0);
// Determine if the DeviceIoControl call and the read succeeded.
result = result && !(reg.reg_Flags & CARRY_FLAG);
WMSG4(" +++ WB21h %ld %d (result=%d lastError=%ld)\n",
startBlock, blockCount, result, GetLastError());
if (!result)
return kDIErrWriteFailed;
return kDIErrNone;
}
/*
* Read blocks from a Win2K logical or physical volume.
*/
/*static*/ DIError
Win32VolumeAccess::BlockAccess::ReadBlocksWin2K(HANDLE handle,
long startBlock, short blockCount, void* buf)
{
/*
* Try to read the blocks. Under Win2K the seek and read calls appear
* to succeed, but the value in "actual" is set to zero to indicate
* that we're trying to read past EOF.
*/
DWORD posn, actual;
/*
* Win2K: the 3rd argument holds the high 32 bits of the distance to
* move. This isn't supported in Win9x, which means Win9x is limited
* to 2GB files.
*/
LARGE_INTEGER li;
li.QuadPart = (LONGLONG) startBlock * (LONGLONG) kBlockSize;
posn = ::SetFilePointer(handle, li.LowPart, &li.HighPart,
FILE_BEGIN);
if (posn == INVALID_SET_FILE_POINTER) {
DWORD lerr = GetLastError();
WMSG1(" GFDWinVolume ReadBlock: SFP failed (err=%ld)\n", lerr);
return LastErrorToDIError();
}
//WMSG2(" ReadFile block start=%d count=%d\n", startBlock, blockCount);
BOOL result;
result = ::ReadFile(handle, buf, blockCount * kBlockSize, &actual, nil);
if (!result) {
DWORD lerr = GetLastError();
WMSG3(" ReadBlocksWin2K: ReadFile failed (start=%ld count=%d err=%ld)\n",
startBlock, blockCount, lerr);
return LastErrorToDIError();
}
if ((long) actual != blockCount * kBlockSize)
return kDIErrEOF;
return kDIErrNone;
}
/*
* Write blocks to a Win2K logical or physical volume.
*/
/*static*/ DIError
Win32VolumeAccess::BlockAccess::WriteBlocksWin2K(HANDLE handle,
long startBlock, short blockCount, const void* buf)
{
DWORD posn, actual;
posn = ::SetFilePointer(handle, startBlock * kBlockSize, nil,
FILE_BEGIN);
if (posn == INVALID_SET_FILE_POINTER) {
DWORD lerr = GetLastError();
WMSG2(" GFDWinVolume ReadBlocks: SFP %ld failed (err=%ld)\n",
startBlock * kBlockSize, lerr);
return LastErrorToDIError();
}
BOOL result;
result = ::WriteFile(handle, buf, blockCount * kBlockSize, &actual, nil);
if (!result) {
DWORD lerr = GetLastError();
WMSG1(" GFDWinVolume WriteBlocks: WriteFile failed (err=%ld)\n",
lerr);
return LastErrorToDIError();
}
if ((long) actual != blockCount * kBlockSize)
return kDIErrEOF; // unexpected on a write call?
return kDIErrNone;
}
/*
* ===========================================================================
* LogicalBlockAccess
* ===========================================================================
*/
/*
* Open a logical device. The device name should be of the form "A:\".
*/
DIError
Win32VolumeAccess::LogicalBlockAccess::Open(const char* deviceName, bool readOnly)
{
DIError dierr = kDIErrNone;
const bool kPreferASPI = true;
assert(fHandle == nil);
fIsCDROM = false;
fDriveNum = -1;
if (deviceName[0] < 'A' || deviceName[0] > 'Z' ||
deviceName[1] != ':' || deviceName[2] != '\\' ||
deviceName[3] != '\0')
{
WMSG1(" LogicalBlockAccess: invalid device name '%s'\n", deviceName);
assert(false);
dierr = kDIErrInvalidArg;
goto bail;
}
if (deviceName[0] == 'C') {
if (readOnly == false) {
WMSG0(" REFUSING WRITE ACCESS TO C:\\ \n");
return kDIErrVWAccessForbidden;
}
}
DWORD access;
if (readOnly)
access = GENERIC_READ;
else
access = GENERIC_READ | GENERIC_WRITE;
UINT driveType;
driveType = GetDriveType(deviceName);
if (driveType == DRIVE_CDROM) {
if (!Global::GetHasSPTI() && !Global::GetHasASPI())
return kDIErrCDROMNotSupported;
fIsCDROM = true;
// SPTI needs this -- maybe to enforce exclusive access?
access |= GENERIC_WRITE;
}
if (fIsWin9x) {
if (fIsCDROM)
return kDIErrCDROMNotSupported;
fHandle = CreateFile("\\\\.\\vwin32", 0, 0, NULL,
OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
if (fHandle == INVALID_HANDLE_VALUE) {
DWORD lastError = GetLastError();
WMSG1(" Win32LVOpen: CreateFile(vwin32) failed (err=%ld)\n",
lastError);
dierr = LastErrorToDIError();
goto bail;
}
fDriveNum = deviceName[0] - 'A' +1;
assert(fDriveNum > 0 && fDriveNum <= kNumLogicalVolumes);
#if 0
int int13Unit;
dierr = GetInt13Unit(fHandle, fDriveNum, &int13Unit);
if (dierr != kDIErrNone)
goto bail;
if (int13Unit < 4) {
fFloppyUnit = int13Unit;
WMSG2(" Logical volume #%d looks like floppy unit %d\n",
fDriveNum, fFloppyUnit);
}
#endif
} else {
char device[7] = "\\\\.\\_:";
device[4] = deviceName[0];
WMSG1("Opening '%s'\n", device);
// If we're reading, allow others to write. If we're writing, insist
// upon exclusive access to the volume.
DWORD shareMode = FILE_SHARE_READ;
if (access == GENERIC_READ)
shareMode |= FILE_SHARE_WRITE;
fHandle = CreateFile(device, access, shareMode,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (fHandle == INVALID_HANDLE_VALUE) {
DWORD lastError = GetLastError();
dierr = LastErrorToDIError();
if (lastError == ERROR_INVALID_PARAMETER && shareMode == FILE_SHARE_READ)
{
// Win2K spits this back if the volume is open and we're
// not specifying FILE_SHARE_WRITE. Give it a try, just to
// see if it works, so we can tell the difference between
// an internal library error and an active filesystem.
HANDLE tmpHandle;
tmpHandle = CreateFile(device, access, FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (tmpHandle != INVALID_HANDLE_VALUE) {
dierr = kDIErrNoExclusiveAccess;
::CloseHandle(tmpHandle);
}
}
WMSG2(" LBAccess Open: CreateFile failed (err=%ld dierr=%d)\n",
lastError, dierr);
goto bail;
}
}
assert(fHandle != nil && fHandle != INVALID_HANDLE_VALUE);
#if 0
if (fIsCDROM) {
assert(Global::GetHasSPTI() || Global::GetHasASPI());
if (Global::GetHasASPI() && (!Global::GetHasSPTI() || kPreferASPI)) {
WMSG0(" LBAccess using ASPI\n");
fCDBaggage.fUseASPI = true;
} else {
WMSG0(" LBAccess using SPTI\n");
fCDBaggage.fUseASPI = false;
}
if (fCDBaggage.fUseASPI) {
dierr = FindASPIDriveMapping(deviceName[0]);
if (dierr != kDIErrNone) {
WMSG0("ERROR: couldn't find ASPI drive mapping\n");
dierr = kDIErrNoASPIMapping;
goto bail;
}
}
}
#endif
bail:
if (dierr != kDIErrNone) {
if (fHandle != nil && fHandle != INVALID_HANDLE_VALUE)
::CloseHandle(fHandle);
fHandle = nil;
}
return dierr;
}
/*
* Close the device handle.
*/
DIError
Win32VolumeAccess::LogicalBlockAccess::Close(void)
{
if (fHandle != nil) {
::CloseHandle(fHandle);
fHandle = nil;
}
return kDIErrNone;
}
/*
* Read 512-byte blocks from CD-ROM media using SPTI calls.
*/
DIError
Win32VolumeAccess::LogicalBlockAccess::ReadBlocksCDROM(HANDLE handle,
long startBlock, short blockCount, void* buf)
{
#ifdef HAVE_WINDOWS_CDROM
DIError dierr;
assert(handle != nil);
assert(startBlock >= 0);
assert(blockCount > 0);
assert(buf != nil);
//WMSG2(" CDROM read block %ld (%ld)\n", startBlock, block);
/* alloc sector buffer on first use */
if (fLastSectorCache == nil) {
fLastSectorCache = new unsigned char[kCDROMSectorSize];
if (fLastSectorCache == nil)
return kDIErrMalloc;
assert(fLastSectorNum == -1);
}
/*
* Map a range of 512-byte blocks to a range of 2048-byte blocks.
*/
assert(kCDROMSectorSize % kBlockSize == 0);
const int kFactor = kCDROMSectorSize / kBlockSize;
long sectorIndex = startBlock / kFactor;
int sectorOffset = (int) (startBlock % kFactor); // 0-3
/*
* When possible, do multi-block reads directly into "buf". The first
* and last block may require special handling.
*/
while (blockCount) {
assert(blockCount > 0);
if (sectorOffset != 0 || blockCount < kFactor) {
assert(sectorOffset >= 0 && sectorOffset < kFactor);
/* get from single-block cache or from disc */
if (sectorIndex != fLastSectorNum) {
fLastSectorNum = -1; // invalidate, in case of error
dierr = SPTI::ReadBlocks(handle, sectorIndex, 1,
kCDROMSectorSize, fLastSectorCache);
if (dierr != kDIErrNone)
return dierr;
fLastSectorNum = sectorIndex;
}
int thisNumBlocks;
thisNumBlocks = kFactor - sectorOffset;
if (thisNumBlocks > blockCount)
thisNumBlocks = blockCount;
//WMSG3(" Small copy (sectIdx=%ld off=%d*512 size=%d*512)\n",
// sectorIndex, sectorOffset, thisNumBlocks);
memcpy(buf, fLastSectorCache + sectorOffset * kBlockSize,
thisNumBlocks * kBlockSize);
blockCount -= thisNumBlocks;
buf = (unsigned char*) buf + (thisNumBlocks * kBlockSize);
sectorOffset = 0;
sectorIndex++;
} else {
fLastSectorNum = -1; // invalidate single-block cache
int numSectors;
numSectors = blockCount / kFactor; // rounds down
//WMSG2(" Big read (sectIdx=%ld numSect=%d)\n",
// sectorIndex, numSectors);
dierr = SPTI::ReadBlocks(handle, sectorIndex, numSectors,
kCDROMSectorSize, buf);
if (dierr != kDIErrNone)
return dierr;
blockCount -= numSectors * kFactor;
buf = (unsigned char*) buf + (numSectors * kCDROMSectorSize);
sectorIndex += numSectors;
}
}
return kDIErrNone;
#else
return kDIErrCDROMNotSupported;
#endif
}
/*
* ===========================================================================
* PhysicalBlockAccess
* ===========================================================================
*/
/*
* Open a physical device. The device name should be of the form "80:\".
*/
DIError
Win32VolumeAccess::PhysicalBlockAccess::Open(const char* deviceName, bool readOnly)
{
DIError dierr = kDIErrNone;
// initialize all local state
assert(fHandle == nil);
fInt13Unit = -1;
fFloppyKind = kFloppyUnknown;
memset(&fGeometry, 0, sizeof(fGeometry));
// sanity-check name; not this only works for first 10 devices
if (deviceName[0] < '0' || deviceName[0] > '9' ||
deviceName[1] < '0' || deviceName[1] > '9' ||
deviceName[2] != ':' || deviceName[3] != '\\' ||
deviceName[4] != '\0')
{
WMSG1(" PhysicalBlockAccess: invalid device name '%s'\n", deviceName);
assert(false);
dierr = kDIErrInvalidArg;
goto bail;
}
if (deviceName[0] == '8' && deviceName[1] == '0') {
if (!gAllowWritePhys0 && readOnly == false) {
WMSG0(" REFUSING WRITE ACCESS TO 80:\\ \n");
return kDIErrVWAccessForbidden;
}
}
fInt13Unit = (deviceName[0] - '0') * 16 + deviceName[1] - '0';
if (!fIsWin9x && fInt13Unit < 0x80) {
WMSG0("GLITCH: can't open floppy as physical unit in Win2K\n");
dierr = kDIErrInvalidArg;
goto bail;
}
if (fIsWin9x && fInt13Unit >= 0x80) {
WMSG0("GLITCH: can't access physical HD in Win9x\n");
dierr = kDIErrInvalidArg;
goto bail;
}
if ((fInt13Unit >= 0x00 && fInt13Unit < 0x04) ||
(fInt13Unit >= 0x80 && fInt13Unit < 0x88))
{
WMSG1(" Win32VA/P: opening unit %02xh\n", fInt13Unit);
} else {
WMSG2("GLITCH: converted '%s' to %02xh\n", deviceName, fInt13Unit);
dierr = kDIErrInternal;
goto bail;
}
DWORD access;
if (readOnly)
access = GENERIC_READ;
else
access = GENERIC_READ | GENERIC_WRITE;
if (fIsWin9x) {
fHandle = CreateFile("\\\\.\\vwin32", 0, 0, NULL,
OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
if (fHandle == INVALID_HANDLE_VALUE) {
DWORD lastError = GetLastError();
WMSG1(" Win32VA/PBOpen: CreateFile(vwin32) failed (err=%ld)\n",
lastError);
dierr = LastErrorToDIError();
goto bail;
}
/* figure out the geometry */
dierr = DetectFloppyGeometry();
if (dierr != kDIErrNone)
goto bail;
} else {
char device[19] = "\\\\.\\PhysicalDrive_";
assert(fInt13Unit >= 0x80 && fInt13Unit <= 0x89);
device[17] = fInt13Unit - 0x80 + '0';
WMSG2("Opening '%s' (access=0x%02x)\n", device, access);
// If we're reading, allow others to write. If we're writing, insist
// upon exclusive access to the volume.
DWORD shareMode = FILE_SHARE_READ;
if (access == GENERIC_READ)
shareMode |= FILE_SHARE_WRITE;
fHandle = CreateFile(device, access, shareMode,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (fHandle == INVALID_HANDLE_VALUE) {
DWORD lastError = GetLastError();
dierr = LastErrorToDIError();
WMSG2(" PBAccess Open: CreateFile failed (err=%ld dierr=%d)\n",
lastError, dierr);
goto bail;
}
}
assert(fHandle != nil && fHandle != INVALID_HANDLE_VALUE);
bail:
if (dierr != kDIErrNone) {
if (fHandle != nil && fHandle != INVALID_HANDLE_VALUE)
::CloseHandle(fHandle);
fHandle = nil;
}
return dierr;
}
/*
* Auto-detect the geometry of a floppy drive.
*
* Sets "fFloppyKind" and "fGeometry".
*/
DIError
Win32VolumeAccess::PhysicalBlockAccess::DetectFloppyGeometry(void)
{
DIError dierr = kDIErrNone;
static const struct {
FloppyKind kind;
DiskGeometry geom;
} floppyGeometry[] = {
{ kFloppyUnknown, { -1, -1, -1, -1 } },
{ kFloppy525_360, { 40, 2, 9, 360*2 } },
{ kFloppy525_1200, { 80, 2, 15, 1200*2 } },
{ kFloppy35_720, { 80, 2, 9, 720*2 } },
{ kFloppy35_1440, { 80, 2, 18, 1440*2 } },
{ kFloppy35_2880, { 80, 2, 36, 2880*2 } }
};
unsigned char buf[kBlockSize];
FloppyKind driveKind;
int status;
/* verify that we can directly index the table with the enum */
for (int chk = 0; chk < NELEM(floppyGeometry); chk++) {
assert(floppyGeometry[chk].kind == chk);
}
assert(chk == kFloppyMax);
/*
* Issue a BIOS call to determine the kind of drive we're looking at.
*/
dierr = GetFloppyDriveKind(fHandle, fInt13Unit, &driveKind);
if (dierr != kDIErrNone)
goto bail;
switch (driveKind) {
case kFloppy35_2880:
status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 36, 1, buf);
if (status == 0) {
fFloppyKind = kFloppy35_2880;
break;
}
// else, fall through
case kFloppy35_1440:
status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 18, 1, buf);
if (status == 0) {
fFloppyKind = kFloppy35_1440;
break;
}
// else, fall through
case kFloppy35_720:
status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 9, 1, buf);
if (status == 0) {
fFloppyKind = kFloppy35_720;
break;
}
// else, fail
dierr = kDIErrReadFailed;
goto bail;
case kFloppy525_1200:
status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 15, 1, buf);
if (status == 0) {
fFloppyKind = kFloppy525_1200;
break;
}
// else, fall through
case kFloppy525_360:
status = ReadBlocksInt13h(fHandle, fInt13Unit, 0, 0, 9, 1, buf);
if (status == 0) {
fFloppyKind = kFloppy525_360;
break;
}
// else, fail
dierr = kDIErrReadFailed;
goto bail;
default:
WMSG1(" Unknown driveKind %d\n", driveKind);
assert(false);
dierr = kDIErrInternal;
goto bail;
}
WMSG1("PBA: floppy disk appears to be type=%d\n", fFloppyKind);
fGeometry = floppyGeometry[fFloppyKind].geom;
bail:
return dierr;
}
#if 0
/*
* Flush the system disk cache.
*/
DIError
Win32VolumeAccess::PhysicalBlockAccess::FlushBlockDevice(void)
{
DIError dierr = kDIErrNone;
if (::FlushFileBuffers(fHandle) == FALSE) {
DWORD lastError = GetLastError();
WMSG1(" Win32VA/PBAFlush: FlushFileBuffers failed (err=%ld)\n",
lastError);
dierr = LastErrorToDIError();
}
return dierr;
}
#endif
/*
* Close the device handle.
*/
DIError
Win32VolumeAccess::PhysicalBlockAccess::Close(void)
{
if (fHandle != nil) {
::CloseHandle(fHandle);
fHandle = nil;
}
return kDIErrNone;
}
/*
* ===========================================================================
* ASPIBlockAccess
* ===========================================================================
*/
/*
* Unpack device name and verify that the device is a CD-ROM drive or
* direct-access storage device.
*/
DIError
Win32VolumeAccess::ASPIBlockAccess::Open(const char* deviceName, bool readOnly)
{
DIError dierr = kDIErrNone;
if (fpASPI != nil)
return kDIErrAlreadyOpen;
fpASPI = Global::GetASPI();
if (fpASPI == nil)
return kDIErrASPIFailure;
if (strncmp(deviceName, kASPIDev, strlen(kASPIDev)) != 0) {
assert(false);
dierr = kDIErrInternal;
goto bail;
}
const char* cp;
int adapter, target, lun;
int result;
cp = deviceName + strlen(kASPIDev);
result = 0;
result |= ExtractInt(&cp, &adapter);
result |= ExtractInt(&cp, &target);
result |= ExtractInt(&cp, &lun);
if (result != 0) {
WMSG1(" Win32VA couldn't parse '%s'\n", deviceName);
dierr = kDIErrInternal;
goto bail;
}
fAdapter = adapter;
fTarget = target;
fLun = lun;
unsigned char deviceType;
dierr = fpASPI->GetDeviceType(fAdapter, fTarget, fLun, &deviceType);
if (dierr != kDIErrNone ||
(deviceType != kScsiDevTypeCDROM && deviceType != kScsiDevTypeDASD))
{
WMSG2(" Win32VA bad GetDeviceType err=%d type=%d\n",
dierr, deviceType);
dierr = kDIErrInternal; // should not be here at all
goto bail;
}
if (deviceType == kScsiDevTypeCDROM)
fReadOnly = true;
else
fReadOnly = readOnly;
WMSG4(" Win32VA successful 'open' of '%s' on %d:%d:%d\n",
deviceName, fAdapter, fTarget, fLun);
bail:
if (dierr != kDIErrNone)
fpASPI = nil;
return dierr;
}
/*
* Extract an integer from a string, advancing to the next integer after
* doing so.
*
* Returns 0 on success, -1 on failure.
*/
int
Win32VolumeAccess::ASPIBlockAccess::ExtractInt(const char** pStr, int* pResult)
{
char* end = nil;
if (*pStr == nil) {
assert(false);
return -1;
}
*pResult = (int) strtol(*pStr, &end, 10);
if (end == nil)
*pStr = nil;
else
*pStr = end+1;
return 0;
}
/*
* Return the device capacity in 512-byte blocks.
*
* Sets fChunkSize as a side-effect.
*/
DIError
Win32VolumeAccess::ASPIBlockAccess::DetectCapacity(long* pNumBlocks)
{
DIError dierr = kDIErrNone;
unsigned long lba, blockLen;
dierr = fpASPI->GetDeviceCapacity(fAdapter, fTarget, fLun, &lba,
&blockLen);
if (dierr != kDIErrNone)
goto bail;
WMSG3("READ CAPACITY reports lba=%lu blockLen=%lu (total=%lu)\n",
lba, blockLen, lba*blockLen);
fChunkSize = blockLen;
if ((blockLen % 512) != 0) {
WMSG1("Unacceptable CD-ROM blockLen=%ld, bailing\n", blockLen);
dierr = kDIErrReadFailed;
goto bail;
}
// The LBA is the last valid block on the disk. To get the disk size,
// we need to add one.
*pNumBlocks = (blockLen/512) * (lba+1);
WMSG1(" ASPI returning 512-byte block count %ld\n", *pNumBlocks);
bail:
return dierr;
}
/*
* Read one or more 512-byte blocks from the device.
*
* SCSI doesn't promise it'll be in a chunk size we like, but it's pretty
* safe to assume that it'll be at least 512 bytes, and divisible by 512.
*/
DIError
Win32VolumeAccess::ASPIBlockAccess::ReadBlocks(long startBlock, short blockCount,
void* buf)
{
DIError dierr;
// we're expecting fBlockSize to be 512 or 2048
assert(fChunkSize >= kBlockSize && fChunkSize <= 65536);
assert((fChunkSize % kBlockSize) == 0);
/* alloc chunk buffer on first use */
if (fLastChunkCache == nil) {
fLastChunkCache = new unsigned char[fChunkSize];
if (fLastChunkCache == nil)
return kDIErrMalloc;
assert(fLastChunkNum == -1);
}
/*
* Map a range of N-byte blocks to a range of 2048-byte blocks.
*/
const int kFactor = fChunkSize / kBlockSize;
long chunkIndex = startBlock / kFactor;
int chunkOffset = (int) (startBlock % kFactor); // small integer
/*
* When possible, do multi-block reads directly into "buf". The first
* and last block may require special handling.
*/
while (blockCount) {
assert(blockCount > 0);
if (chunkOffset != 0 || blockCount < kFactor) {
assert(chunkOffset >= 0 && chunkOffset < kFactor);
/* get from single-block cache or from disc */
if (chunkIndex != fLastChunkNum) {
fLastChunkNum = -1; // invalidate, in case of error
dierr = fpASPI->ReadBlocks(fAdapter, fTarget, fLun, chunkIndex,
1, fChunkSize, fLastChunkCache);
if (dierr != kDIErrNone)
return dierr;
fLastChunkNum = chunkIndex;
}
int thisNumBlocks;
thisNumBlocks = kFactor - chunkOffset;
if (thisNumBlocks > blockCount)
thisNumBlocks = blockCount;
//WMSG3(" Small copy (chIdx=%ld off=%d*512 size=%d*512)\n",
// chunkIndex, chunkOffset, thisNumBlocks);
memcpy(buf, fLastChunkCache + chunkOffset * kBlockSize,
thisNumBlocks * kBlockSize);
blockCount -= thisNumBlocks;
buf = (unsigned char*) buf + (thisNumBlocks * kBlockSize);
chunkOffset = 0;
chunkIndex++;
} else {
fLastChunkNum = -1; // invalidate single-block cache
int numChunks;
numChunks = blockCount / kFactor; // rounds down
//WMSG2(" Big read (chIdx=%ld numCh=%d)\n",
// chunkIndex, numChunks);
dierr = fpASPI->ReadBlocks(fAdapter, fTarget, fLun, chunkIndex,
numChunks, fChunkSize, buf);
if (dierr != kDIErrNone)
return dierr;
blockCount -= numChunks * kFactor;
buf = (unsigned char*) buf + (numChunks * fChunkSize);
chunkIndex += numChunks;
}
}
return kDIErrNone;
}
/*
* Write one or more 512-byte blocks to the device.
*
* SCSI doesn't promise it'll be in a chunk size we like, but it's pretty
* safe to assume that it'll be at least 512 bytes, and divisible by 512.
*/
DIError
Win32VolumeAccess::ASPIBlockAccess::WriteBlocks(long startBlock, short blockCount,
const void* buf)
{
DIError dierr;
if (fReadOnly)
return kDIErrAccessDenied;
// we're expecting fBlockSize to be 512 or 2048
assert(fChunkSize >= kBlockSize && fChunkSize <= 65536);
assert((fChunkSize % kBlockSize) == 0);
/* throw out the cache */
fLastChunkNum = -1;
/*
* Map a range of N-byte blocks to a range of 2048-byte blocks.
*/
const int kFactor = fChunkSize / kBlockSize;
long chunkIndex = startBlock / kFactor;
int chunkOffset = (int) (startBlock % kFactor); // small integer
/*
* When possible, do multi-block writes directly from "buf". The first
* and last block may require special handling.
*/
while (blockCount) {
assert(blockCount > 0);
if (chunkOffset != 0 || blockCount < kFactor) {
assert(chunkOffset >= 0 && chunkOffset < kFactor);
/* read the chunk we're writing a part of */
dierr = fpASPI->ReadBlocks(fAdapter, fTarget, fLun, chunkIndex,
1, fChunkSize, fLastChunkCache);
if (dierr != kDIErrNone)
return dierr;
int thisNumBlocks;
thisNumBlocks = kFactor - chunkOffset;
if (thisNumBlocks > blockCount)
thisNumBlocks = blockCount;
WMSG3(" Small copy out (chIdx=%ld off=%d*512 size=%d*512)\n",
chunkIndex, chunkOffset, thisNumBlocks);
memcpy(fLastChunkCache + chunkOffset * kBlockSize, buf,
thisNumBlocks * kBlockSize);
blockCount -= thisNumBlocks;
buf = (const unsigned char*) buf + (thisNumBlocks * kBlockSize);
chunkOffset = 0;
chunkIndex++;
} else {
int numChunks;
numChunks = blockCount / kFactor; // rounds down
WMSG2(" Big write (chIdx=%ld numCh=%d)\n",
chunkIndex, numChunks);
dierr = fpASPI->WriteBlocks(fAdapter, fTarget, fLun, chunkIndex,
numChunks, fChunkSize, buf);
if (dierr != kDIErrNone)
return dierr;
blockCount -= numChunks * kFactor;
buf = (const unsigned char*) buf + (numChunks * fChunkSize);
chunkIndex += numChunks;
}
}
return kDIErrNone;
}
/*
* Not much to do, really, since we're not holding onto any OS structures.
*/
DIError
Win32VolumeAccess::ASPIBlockAccess::Close(void)
{
fpASPI = nil;
return kDIErrNone;
}
/*
* ===========================================================================
* CBCache
* ===========================================================================
*/
/*
* Determine whether we're holding a block in the cache.
*/
bool
CBCache::IsBlockInCache(long blockNum) const
{
if (fFirstBlock == kEmpty)
return false;
assert(fNumBlocks > 0);
if (blockNum >= fFirstBlock && blockNum < fFirstBlock + fNumBlocks)
return true;
else
return false;
}
/*
* Retrieve a single block from the cache.
*/
DIError
CBCache::GetFromCache(long blockNum, void* buf)
{
if (!IsBlockInCache(blockNum)) {
assert(false);
return kDIErrInternal;
}
//WMSG1(" CBCache: getting block %d from cache\n", blockNum);
int offset = (blockNum - fFirstBlock) * kBlockSize;
assert(offset >= 0);
memcpy(buf, fCache + offset, kBlockSize);
return kDIErrNone;
}
/*
* Determine whether a block will "fit" in the cache. There are two
* criteria: (1) there must actually be room at the end, and (2) the
* block in question must be the next consecutive block.
*/
bool
CBCache::IsRoomInCache(long blockNum) const
{
if (fFirstBlock == kEmpty)
return true;
// already in cache?
if (blockNum >= fFirstBlock && blockNum < fFirstBlock + fNumBlocks)
return true;
// running off the end?
if (fNumBlocks == kMaxCachedBlocks)
return false;
// is it the exact next one?
if (fFirstBlock + fNumBlocks != blockNum)
return false;
return true;
}
/*
* Add a block to the cache.
*
* We might be adding it after a read or a write. The "isDirty" flag
* tells us what the deal is. If somebody tries to overwrite a dirty
* block with a new one and doesn't have "isDirty" set, it probably means
* they're trying to overwrite dirty cached data with the result of a new
* read, which is a bug. Trap it here.
*/
DIError
CBCache::PutInCache(long blockNum, const void* buf, bool isDirty)
{
int blockOffset = -1;
if (!IsRoomInCache(blockNum)) {
assert(false);
return kDIErrInternal;
}
if (fFirstBlock == kEmpty) {
//WMSG1(" CBCache: starting anew with block %ld\n", blockNum);
fFirstBlock = blockNum;
fNumBlocks = 1;
blockOffset = 0;
} else if (blockNum == fFirstBlock + fNumBlocks) {
//WMSG1(" CBCache: appending block %ld\n", blockNum);
blockOffset = fNumBlocks;
fNumBlocks++;
} else if (blockNum >= fFirstBlock && blockNum < fFirstBlock + fNumBlocks) {
blockOffset = blockNum - fFirstBlock;
} else {
assert(false);
return kDIErrInternal;
}
assert(blockOffset != -1);
assert(blockOffset < kMaxCachedBlocks);
if (fDirty[blockOffset] && !isDirty) {
WMSG1("BUG: CBCache trying to clear dirty flag for block %ld\n",
blockNum);
assert(false);
return kDIErrInternal;
}
fDirty[blockOffset] = isDirty;
//WMSG2(" CBCache: adding block %d to cache at %d\n", blockNum, blockOffset);
int offset = blockOffset * kBlockSize;
assert(offset >= 0);
memcpy(fCache + offset, buf, kBlockSize);
return kDIErrNone;
}
/*
* Determine whether there are any dirty blocks in the cache.
*/
bool
CBCache::IsDirty(void) const
{
if (fFirstBlock == kEmpty)
return false;
assert(fNumBlocks > 0);
for (int i = 0; i < fNumBlocks; i++) {
if (fDirty[i]) {
//WMSG0(" CBCache: dirty blocks found\n");
return true;
}
}
//WMSG0(" CBCache: no dirty blocks found\n");
return false;
}
/*
* Return a pointer to the cache goodies, so that the object sitting
* on the disk hardware can write our stuff.
*/
void
CBCache::GetCachePointer(long* pFirstBlock, int* pNumBlocks, void** pBuf) const
{
assert(fFirstBlock != kEmpty); // not essential, but why call here if not?
*pFirstBlock = fFirstBlock;
*pNumBlocks = fNumBlocks;
*pBuf = (void*) fCache;
}
/*
* Clear all the dirty flags.
*/
void
CBCache::Scrub(void)
{
if (fFirstBlock == kEmpty)
return;
for (int i = 0; i < fNumBlocks; i++)
fDirty[i] = false;
}
/*
* Trash all of our entries. If any are dirty, scream bloody murder.
*/
void
CBCache::Purge(void)
{
if (fFirstBlock == kEmpty)
return;
if (IsDirty()) {
// Should only happen after a write failure causes us to clean up.
WMSG0("HEY: CBCache purging dirty blocks!\n");
//assert(false);
}
Scrub();
fFirstBlock = kEmpty;
fNumBlocks = 0;
}
#endif /*_WIN32*/