mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-26 17:49:21 +00:00
2165 lines
58 KiB
C++
2165 lines
58 KiB
C++
/*
|
|
* 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,
|
|
®, sizeof(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,
|
|
®, sizeof(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, ®,
|
|
sizeof(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, ®,
|
|
sizeof(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, §or, &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, ®,
|
|
sizeof(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, §or, &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,
|
|
®, sizeof(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,
|
|
®, sizeof(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,
|
|
®, sizeof(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*/
|