/* * 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 WCHAR* deviceName, bool readOnly) { DIError dierr = kDIErrNone; assert(deviceName != NULL); if (fpBlockAccess != NULL) { assert(false); return kDIErrAlreadyOpen; } #ifdef WANT_ASPI if (strncmp(deviceName, kASPIDev, strlen(kASPIDev)) == 0) { fpBlockAccess = new ASPIBlockAccess; if (fpBlockAccess == NULL) { dierr = kDIErrMalloc; goto bail; } dierr = fpBlockAccess->Open(deviceName, readOnly); if (dierr != kDIErrNone) goto bail; } else #endif if (deviceName[0] >= 'A' && deviceName[0] <= 'Z') { fpBlockAccess = new LogicalBlockAccess; if (fpBlockAccess == NULL) { 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 == NULL) { dierr = kDIErrMalloc; goto bail; } dierr = fpBlockAccess->Open(deviceName, readOnly); if (dierr != kDIErrNone) goto bail; } else { LOGI(" Win32VA: '%s' isn't the name of a device", 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 = NULL; } return dierr; } /* * Close the device. */ void Win32VolumeAccess::Close(void) { if (fpBlockAccess != NULL) { DIError dierr; LOGI(" Win32VolumeAccess closing"); dierr = FlushCache(true); if (dierr != kDIErrNone) { LOGI("WARNING: Win32VA Close: FlushCache failed (err=%ld)", dierr); // not much we can do } dierr = fpBlockAccess->Close(); if (dierr != kDIErrNone) { LOGI("WARNING: Win32VolumeAccess BlockAccess Close failed (err=%ld)", dierr); } delete fpBlockAccess; fpBlockAccess = NULL; } } /* * 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 != NULL); assert(fpBlockAccess != NULL); 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 != NULL); 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; //LOGI(" Win32VA: FlushCache (%d)", purge); if (fBlockCache.IsDirty()) { long firstBlock; int numBlocks; void* buf; fBlockCache.GetCachePointer(&firstBlock, &numBlocks, &buf); LOGI("FlushCache writing first=%d num=%d (purge=%d)", firstBlock, numBlocks, purge); dierr = DoWriteBlocks(firstBlock, numBlocks, buf); if (dierr != kDIErrNone) { LOGI(" Win32VA: FlushCache write blocks failed (err=%d)", 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; uint32_t lba, blockLen; dierr = SPTI::GetDeviceCapacity(handle, &lba, &blockLen); if (dierr != kDIErrNone) goto bail; LOGI("READ CAPACITY reports lba=%u blockLen=%u (total=%u)", lba, blockLen, lba*blockLen); if (isCDROM && blockLen != kCDROMSectorSize) { LOGW("Unacceptable CD-ROM blockLen=%ld, bailing", 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); LOGI(" SPTI returning 512-byte block count %ld", *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)) { LOGI(" Win32VolumeAccess: can't read block 0"); 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 */ LOGI("OVERSHOT at %ld", 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 */ LOGI(" Win32VolumeAccess unable to determine size"); dierr = kDIErrReadFailed; goto bail; } } if (totalBlocks > (3 * 1024 * 1024) / BlockAccess::kBlockSize) { LOGI(" GFDWinVolume: size is %ld (%.2fMB)", totalBlocks, (float) totalBlocks / (2048.0)); } else { LOGI(" GFDWinVolume: size is %ld (%.2fKB)", 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; uint8_t buf[BlockAccess::kBlockSize]; dierr = pThis->ReadBlocks(blockNum, 1, buf); if (dierr == kDIErrNone) { LOGI(" +++ Checked %ld (good)", blockNum); return true; } else { LOGI(" +++ Checked %ld (bad)", 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 != NULL); assert(driveNum > 0 && driveNum <= kNumLogicalVolumes); assert(pInt13Unit != NULL); *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) { LOGI(" DeviceIoControl(Int21h, 6fh) FAILED (err=%ld)", GetLastError()); dierr = LastErrorToDIError(); goto bail; } if (reg.reg_Flags & CARRY_FLAG) { LOGI(" --- retrying GDMI with alternate device category (ax=%ld)", 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) { LOGI(" DeviceIoControl(Int21h, 6fh)(retry) FAILED (err=%ld)", GetLastError()); dierr = LastErrorToDIError(); goto bail; } if (reg.reg_Flags & CARRY_FLAG) { LOGI(" --- GetDriveMapInfo call (retry) failed (ax=%ld)", reg.reg_EAX); dierr = kDIErrReadFailed; // close enough goto bail; } LOGI(" +++ DriveMapInfo len=%d flags=%d unit=%d", driveMapInfo.dmiInfoLength, driveMapInfo.dmiFlags, driveMapInfo.dmiInt13Unit); LOGI(" +++ driveMap=0x%08lx RBA=0x%08lx 0x%08lx", 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-NULL. */ /*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)) { LOGI( "GetFloppyGeometry: no match for blocks=%ld", 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 != NULL) *pCylinder = cylinder; if (pHead != NULL) *pHead = head; if (pSector != NULL) *pSector = sector+1; if (pLastBlockOnTrack != NULL) *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)) { LOGI(" GetFloppyKind failed: result=%d flags=0x%04x AH=%d", 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 { LOGI(" GetFloppyKind: unrecognized kind %d", 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); //LOGI(" DIOC Int13h read c=%d h=%d s=%d rc=%d", // 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; } LOGI(" DIOC soft read failure, ax=0x%08lx", reg.reg_EAX); } if (!result || (reg.reg_Flags & CARRY_FLAG)) { int ah = HIBYTE(reg.reg_EAX); LOGI(" DIOC read failed, result=%d ah=%ld", 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) { LOGI(" ReadInt13h: invalid request for start=%ld count=%d", 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; //LOGI("R runCount=%d lastBlkOnT=%d start=%d", // runCount, lastBlockOnTrack, startBlock); assert(runCount > 0); status = ReadBlocksInt13h(handle, unitNum, cylinder, head, sector, runCount, buf); if (status != 0) { LOGI(" DIOC read failed (status=%d)", 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); LOGI(" DIOC Int13h write c=%d h=%d s=%d rc=%d", 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 LOGI(" DIOC soft write failure, ax=0x%08lx", reg.reg_EAX); } if (!result || (reg.reg_Flags & CARRY_FLAG)) { int ah = HIBYTE(reg.reg_EAX); LOGI(" DIOC write failed, result=%d ah=%ld", 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) { LOGI(" WriteInt13h: invalid request for start=%ld count=%d", 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; //LOGI("W runCount=%d lastBlkOnT=%d start=%d", // runCount, lastBlockOnTrack, startBlock); assert(runCount > 0); status = WriteBlocksInt13h(handle, unitNum, cylinder, head, sector, runCount, buf); if (status != 0) { LOGI(" DIOC write failed (status=%d)", 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 LOGI(" Int25 read start=%d count=%d", 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); LOGI(" +++ read from block %ld (result=%d)", 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 LOGI(" Int21/7305h read start=%d count=%d", 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); LOGI(" +++ RB21h %ld %d (result=%d lastError=%ld)", 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) //LOGI(" Int21/7305h write start=%d count=%d", // 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); LOGI(" +++ WB21h %ld %d (result=%d lastError=%ld)", 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(); LOGI(" GFDWinVolume ReadBlock: SFP failed (err=%ld)", lerr); return LastErrorToDIError(); } //LOGI(" ReadFile block start=%d count=%d", startBlock, blockCount); BOOL result; result = ::ReadFile(handle, buf, blockCount * kBlockSize, &actual, NULL); if (!result) { DWORD lerr = GetLastError(); LOGI(" ReadBlocksWin2K: ReadFile failed (start=%ld count=%d err=%ld)", 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, NULL, FILE_BEGIN); if (posn == INVALID_SET_FILE_POINTER) { DWORD lerr = GetLastError(); LOGI(" GFDWinVolume ReadBlocks: SFP %ld failed (err=%ld)", startBlock * kBlockSize, lerr); return LastErrorToDIError(); } BOOL result; result = ::WriteFile(handle, buf, blockCount * kBlockSize, &actual, NULL); if (!result) { DWORD lerr = GetLastError(); LOGI(" GFDWinVolume WriteBlocks: WriteFile failed (err=%ld)", 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 WCHAR* deviceName, bool readOnly) { DIError dierr = kDIErrNone; const bool kPreferASPI = true; assert(fHandle == NULL); fIsCDROM = false; fDriveNum = -1; if (deviceName[0] < 'A' || deviceName[0] > 'Z' || deviceName[1] != ':' || deviceName[2] != '\\' || deviceName[3] != '\0') { LOGI(" LogicalBlockAccess: invalid device name '%s'", deviceName); assert(false); dierr = kDIErrInvalidArg; goto bail; } if (deviceName[0] == 'C') { if (readOnly == false) { LOGI(" REFUSING WRITE ACCESS TO C:\\ "); 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(_T("\\\\.\\vwin32"), 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); if (fHandle == INVALID_HANDLE_VALUE) { DWORD lastError = GetLastError(); LOGI(" Win32LVOpen: CreateFile(vwin32) failed (err=%ld)", 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; LOGI(" Logical volume #%d looks like floppy unit %d", fDriveNum, fFloppyUnit); } #endif } else { WCHAR device[7] = _T("\\\\.\\_:"); device[4] = deviceName[0]; LOGI("Opening '%s'", 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); } } LOGI(" LBAccess Open: CreateFile failed (err=%ld dierr=%d)", lastError, dierr); goto bail; } } assert(fHandle != NULL && fHandle != INVALID_HANDLE_VALUE); #if 0 if (fIsCDROM) { assert(Global::GetHasSPTI() || Global::GetHasASPI()); if (Global::GetHasASPI() && (!Global::GetHasSPTI() || kPreferASPI)) { LOGI(" LBAccess using ASPI"); fCDBaggage.fUseASPI = true; } else { LOGI(" LBAccess using SPTI"); fCDBaggage.fUseASPI = false; } if (fCDBaggage.fUseASPI) { dierr = FindASPIDriveMapping(deviceName[0]); if (dierr != kDIErrNone) { LOGI("ERROR: couldn't find ASPI drive mapping"); dierr = kDIErrNoASPIMapping; goto bail; } } } #endif bail: if (dierr != kDIErrNone) { if (fHandle != NULL && fHandle != INVALID_HANDLE_VALUE) ::CloseHandle(fHandle); fHandle = NULL; } return dierr; } /* * Close the device handle. */ DIError Win32VolumeAccess::LogicalBlockAccess::Close(void) { if (fHandle != NULL) { ::CloseHandle(fHandle); fHandle = NULL; } 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 != NULL); assert(startBlock >= 0); assert(blockCount > 0); assert(buf != NULL); //LOGI(" CDROM read block %ld (%ld)", startBlock, block); /* alloc sector buffer on first use */ if (fLastSectorCache == NULL) { fLastSectorCache = new uint8_t[kCDROMSectorSize]; if (fLastSectorCache == NULL) 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; //LOGI(" Small copy (sectIdx=%ld off=%d*512 size=%d*512)", // sectorIndex, sectorOffset, thisNumBlocks); memcpy(buf, fLastSectorCache + sectorOffset * kBlockSize, thisNumBlocks * kBlockSize); blockCount -= thisNumBlocks; buf = (uint8_t*) buf + (thisNumBlocks * kBlockSize); sectorOffset = 0; sectorIndex++; } else { fLastSectorNum = -1; // invalidate single-block cache int numSectors; numSectors = blockCount / kFactor; // rounds down //LOGI(" Big read (sectIdx=%ld numSect=%d)", // sectorIndex, numSectors); dierr = SPTI::ReadBlocks(handle, sectorIndex, numSectors, kCDROMSectorSize, buf); if (dierr != kDIErrNone) return dierr; blockCount -= numSectors * kFactor; buf = (uint8_t*) 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 WCHAR* deviceName, bool readOnly) { DIError dierr = kDIErrNone; // initialize all local state assert(fHandle == NULL); 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') { LOGI(" PhysicalBlockAccess: invalid device name '%s'", deviceName); assert(false); dierr = kDIErrInvalidArg; goto bail; } if (deviceName[0] == '8' && deviceName[1] == '0') { if (!gAllowWritePhys0 && readOnly == false) { LOGI(" REFUSING WRITE ACCESS TO 80:\\ "); return kDIErrVWAccessForbidden; } } fInt13Unit = (deviceName[0] - '0') * 16 + deviceName[1] - '0'; if (!fIsWin9x && fInt13Unit < 0x80) { LOGI("GLITCH: can't open floppy as physical unit in Win2K"); dierr = kDIErrInvalidArg; goto bail; } if (fIsWin9x && fInt13Unit >= 0x80) { LOGI("GLITCH: can't access physical HD in Win9x"); dierr = kDIErrInvalidArg; goto bail; } if ((fInt13Unit >= 0x00 && fInt13Unit < 0x04) || (fInt13Unit >= 0x80 && fInt13Unit < 0x88)) { LOGI(" Win32VA/P: opening unit %02xh", fInt13Unit); } else { LOGI("GLITCH: converted '%s' to %02xh", deviceName, fInt13Unit); dierr = kDIErrInternal; goto bail; } DWORD access; if (readOnly) access = GENERIC_READ; else access = GENERIC_READ | GENERIC_WRITE; if (fIsWin9x) { fHandle = CreateFile(_T("\\\\.\\vwin32"), 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); if (fHandle == INVALID_HANDLE_VALUE) { DWORD lastError = GetLastError(); LOGI(" Win32VA/PBOpen: CreateFile(vwin32) failed (err=%ld)", lastError); dierr = LastErrorToDIError(); goto bail; } /* figure out the geometry */ dierr = DetectFloppyGeometry(); if (dierr != kDIErrNone) goto bail; } else { WCHAR device[19] = _T("\\\\.\\PhysicalDrive_"); assert(fInt13Unit >= 0x80 && fInt13Unit <= 0x89); device[17] = fInt13Unit - 0x80 + '0'; LOGI("Opening '%s' (access=0x%02x)", 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(); LOGI(" PBAccess Open: CreateFile failed (err=%ld dierr=%d)", lastError, dierr); goto bail; } } assert(fHandle != NULL && fHandle != INVALID_HANDLE_VALUE); bail: if (dierr != kDIErrNone) { if (fHandle != NULL && fHandle != INVALID_HANDLE_VALUE) ::CloseHandle(fHandle); fHandle = NULL; } 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 } } }; uint8_t buf[kBlockSize]; FloppyKind driveKind; int status; /* verify that we can directly index the table with the enum */ int chk; for (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: LOGI(" Unknown driveKind %d", driveKind); assert(false); dierr = kDIErrInternal; goto bail; } LOGI("PBA: floppy disk appears to be type=%d", 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(); LOGI(" Win32VA/PBAFlush: FlushFileBuffers failed (err=%ld)", lastError); dierr = LastErrorToDIError(); } return dierr; } #endif /* * Close the device handle. */ DIError Win32VolumeAccess::PhysicalBlockAccess::Close(void) { if (fHandle != NULL) { ::CloseHandle(fHandle); fHandle = NULL; } return kDIErrNone; } /* * =========================================================================== * ASPIBlockAccess * =========================================================================== */ #ifdef WANT_ASPI /* * 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 != NULL) return kDIErrAlreadyOpen; fpASPI = Global::GetASPI(); if (fpASPI == NULL) 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) { LOGI(" Win32VA couldn't parse '%s'", deviceName); dierr = kDIErrInternal; goto bail; } fAdapter = adapter; fTarget = target; fLun = lun; uint8_t deviceType; dierr = fpASPI->GetDeviceType(fAdapter, fTarget, fLun, &deviceType); if (dierr != kDIErrNone || (deviceType != kScsiDevTypeCDROM && deviceType != kScsiDevTypeDASD)) { LOGI(" Win32VA bad GetDeviceType err=%d type=%d", dierr, deviceType); dierr = kDIErrInternal; // should not be here at all goto bail; } if (deviceType == kScsiDevTypeCDROM) fReadOnly = true; else fReadOnly = readOnly; LOGI(" Win32VA successful 'open' of '%s' on %d:%d:%d", deviceName, fAdapter, fTarget, fLun); bail: if (dierr != kDIErrNone) fpASPI = NULL; 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 = NULL; if (*pStr == NULL) { assert(false); return -1; } *pResult = (int) strtol(*pStr, &end, 10); if (end == NULL) *pStr = NULL; 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; LOGI("READ CAPACITY reports lba=%lu blockLen=%lu (total=%lu)", lba, blockLen, lba*blockLen); fChunkSize = blockLen; if ((blockLen % 512) != 0) { LOGI("Unacceptable CD-ROM blockLen=%ld, bailing", 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); LOGI(" ASPI returning 512-byte block count %ld", *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 == NULL) { fLastChunkCache = new uint8_t[fChunkSize]; if (fLastChunkCache == NULL) 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; //LOGI(" Small copy (chIdx=%ld off=%d*512 size=%d*512)", // chunkIndex, chunkOffset, thisNumBlocks); memcpy(buf, fLastChunkCache + chunkOffset * kBlockSize, thisNumBlocks * kBlockSize); blockCount -= thisNumBlocks; buf = (uint8_t*) buf + (thisNumBlocks * kBlockSize); chunkOffset = 0; chunkIndex++; } else { fLastChunkNum = -1; // invalidate single-block cache int numChunks; numChunks = blockCount / kFactor; // rounds down //LOGI(" Big read (chIdx=%ld numCh=%d)", // chunkIndex, numChunks); dierr = fpASPI->ReadBlocks(fAdapter, fTarget, fLun, chunkIndex, numChunks, fChunkSize, buf); if (dierr != kDIErrNone) return dierr; blockCount -= numChunks * kFactor; buf = (uint8_t*) 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; LOGI(" Small copy out (chIdx=%ld off=%d*512 size=%d*512)", chunkIndex, chunkOffset, thisNumBlocks); memcpy(fLastChunkCache + chunkOffset * kBlockSize, buf, thisNumBlocks * kBlockSize); blockCount -= thisNumBlocks; buf = (const uint8_t*) buf + (thisNumBlocks * kBlockSize); chunkOffset = 0; chunkIndex++; } else { int numChunks; numChunks = blockCount / kFactor; // rounds down LOGI(" Big write (chIdx=%ld numCh=%d)", chunkIndex, numChunks); dierr = fpASPI->WriteBlocks(fAdapter, fTarget, fLun, chunkIndex, numChunks, fChunkSize, buf); if (dierr != kDIErrNone) return dierr; blockCount -= numChunks * kFactor; buf = (const uint8_t*) 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 = NULL; return kDIErrNone; } #endif /* * =========================================================================== * 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; } //LOGI(" CBCache: getting block %d from cache", 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) { //LOGI(" CBCache: starting anew with block %ld", blockNum); fFirstBlock = blockNum; fNumBlocks = 1; blockOffset = 0; } else if (blockNum == fFirstBlock + fNumBlocks) { //LOGI(" CBCache: appending block %ld", 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) { LOGI("BUG: CBCache trying to clear dirty flag for block %ld", blockNum); assert(false); return kDIErrInternal; } fDirty[blockOffset] = isDirty; //LOGI(" CBCache: adding block %d to cache at %d", 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]) { //LOGI(" CBCache: dirty blocks found"); return true; } } //LOGI(" CBCache: no dirty blocks found"); 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. LOGE("HEY: CBCache purging dirty blocks!"); //assert(false); } Scrub(); fFirstBlock = kEmpty; fNumBlocks = 0; } #endif /*_WIN32*/