ciderpress/diskimg/FAT.cpp

509 lines
14 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of the Windows FAT filesystem.
*
* Right now we just try to identify that a disk is in a PC format rather
* than Apple II. The trick here is to figure out whether block 0 is a
* Master Boot Record or merely a Boot Sector.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* DiskFSFAT
* ===========================================================================
*/
const int kBlkSize = 512;
const long kBootBlock = 0;
const uint16_t kSignature = 0xaa55; // MBR or boot sector
const int kSignatureOffset = 0x1fe;
const uint8_t kOpcodeMumble = 0x33; // seen on 2nd drive
const uint8_t kOpcodeBranch = 0xeb;
const uint8_t kOpcodeSetInt = 0xfa;
typedef struct PartitionTableEntry {
uint8_t driveNum; // dl (0x80 or 0x00)
uint8_t startHead; // dh
uint8_t startSector; // cl (&0x3f=sector, +two hi bits cyl)
uint8_t startCylinder; // ch (low 8 bits of 10-bit cylinder)
uint8_t type; // partition type
uint8_t endHead; // dh
uint8_t endSector; // cl
uint8_t endCylinder; // ch
uint32_t startLBA; // in blocks
uint32_t size; // in blocks
} PartitionTableEntry;
/*
* Definition of a Master Boot Record, which is block 0 of a physical volume.
*/
typedef struct DiskFSFAT::MasterBootRecord {
/*
* Begins immediately with code, usually 0xfa (set interrupt flag) or
* 0xeb (relative branch).
*/
uint8_t firstByte;
/*
* Partition table starts at 0x1be. Four entries, each 16 bytes.
*/
PartitionTableEntry parTab[4];
} MasterBootRecord;
/*
* Definition of a boot sector, which is block 0 of a logical volume.
*/
typedef struct DiskFSFAT::BootSector {
/*
* The first few bytes of the boot sector is called the BIOS Parameter
* Block, or BPB.
*/
uint8_t jump[3]; // usually EB XX 90
uint8_t oemName[8]; // e.g. "MSWIN4.1" or "MSDOS5.0"
uint16_t bytesPerSector; // usually (always?) 512
uint8_t sectPerCluster;
uint16_t reservedSectors;
uint8_t numFAT;
uint16_t numRootDirEntries;
uint16_t numSectors; // if set, ignore numSectorsHuge
uint8_t mediaType;
uint16_t numFATSectors;
uint16_t sectorsPerTrack;
uint16_t numHeads;
uint32_t numHiddenSectors;
uint32_t numSectorsHuge; // only if numSectors==0
/*
* This next part can start immediately after the above (at 0x24) for
* FAT12/FAT16, or somewhat later (0x42) for FAT32. It doesn't seem
* to exist for NTFS. Probably safest to assume it doesn't exist.
*
* The only way to be sure of what we're dealing with is to know the
* partition type, but if this is our block 0 then we can't know what
* that is.
*/
uint8_t driveNum;
uint8_t reserved;
uint8_t signature; // 0x29
uint32_t volumeID;
uint8_t volumeLabel[11]; // e.g. "FUBAR "
uint8_t fileSysType[8]; // e.g. "FAT12 "
/*
* Code follows. Signature 0xaa55 in the last two bytes.
*/
} BootSector;
// some values for MediaType
enum MediaType {
kMediaTypeLarge = 0xf0, // 1440KB or 2800KB 3.5" disk
kMediaTypeHardDrive = 0xf8,
kMediaTypeMedium = 0xf9, // 720KB 3.5" disk or 1.2MB 5.25" disk
kMediaTypeSmall = 0xfd, // 360KB 5.25" disk
};
/*
* Unpack the MBR.
*
* Returns "true" if this looks like an MBR, "false" otherwise.
*/
/*static*/ bool DiskFSFAT::UnpackMBR(const uint8_t* buf, MasterBootRecord* pOut)
{
const uint8_t* ptr;
int i;
pOut->firstByte = buf[0x00];
ptr = &buf[0x1be];
for (i = 0; i < 4; i++) {
pOut->parTab[i].driveNum = ptr[0x00];
pOut->parTab[i].startHead = ptr[0x01];
pOut->parTab[i].startSector = ptr[0x02];
pOut->parTab[i].startCylinder = ptr[0x03];
pOut->parTab[i].type = ptr[0x04];
pOut->parTab[i].endHead = ptr[0x05];
pOut->parTab[i].endSector = ptr[0x06];
pOut->parTab[i].endCylinder = ptr[0x07];
pOut->parTab[i].startLBA = GetLongLE(&ptr[0x08]);
pOut->parTab[i].size = GetLongLE(&ptr[0x0c]);
ptr += 16;
}
if (pOut->firstByte != kOpcodeBranch &&
pOut->firstByte != kOpcodeSetInt &&
pOut->firstByte != kOpcodeMumble)
return false;
bool foundActive = false;
for (i = 0; i < 4; i++) {
if (pOut->parTab[i].driveNum == 0x80)
foundActive = true;
else if (pOut->parTab[i].driveNum != 0x00)
return false; // must be 0x00 or 0x80
}
// CFFA cards don't seem to set the "active" flag
if (false && !foundActive)
return false;
return true;
}
/*
* Unpack the boot sector.
*
* Returns "true" if this looks like a boot sector, "false" otherwise.
*/
/*static*/ bool DiskFSFAT::UnpackBootSector(const uint8_t* buf, BootSector* pOut)
{
memcpy(pOut->jump, &buf[0x00], sizeof(pOut->jump));
memcpy(pOut->oemName, &buf[0x03], sizeof(pOut->oemName));
pOut->bytesPerSector = GetShortLE(&buf[0x0b]);
pOut->sectPerCluster = buf[0x0d];
pOut->reservedSectors = GetShortLE(&buf[0x0e]);
pOut->numFAT = buf[0x10];
pOut->numRootDirEntries = GetShortLE(&buf[0x11]);
pOut->numSectors = GetShortLE(&buf[0x13]);
pOut->mediaType = buf[0x15];
pOut->numFATSectors = GetShortLE(&buf[0x16]);
pOut->sectorsPerTrack = GetShortLE(&buf[0x18]);
pOut->numHeads = GetShortLE(&buf[0x1a]);
pOut->numHiddenSectors = GetLongLE(&buf[0x1c]);
pOut->numSectorsHuge = GetLongLE(&buf[0x20]);
if (pOut->jump[0] != kOpcodeBranch && pOut->jump[0] != kOpcodeSetInt)
return false;
if (pOut->bytesPerSector != 512)
return false;
return true;
}
/*
* See if this looks like a FAT volume.
*/
/*static*/ DIError DiskFSFAT::TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
{
DIError dierr = kDIErrNone;
uint8_t blkBuf[kBlkSize];
MasterBootRecord mbr;
BootSector bs;
dierr = pImg->ReadBlockSwapped(kBootBlock, blkBuf, imageOrder,
DiskImg::kSectorOrderProDOS);
if (dierr != kDIErrNone)
goto bail;
/*
* Both MBR and boot sectors have the same signature in block 0.
*/
if (GetShortLE(&blkBuf[kSignatureOffset]) != kSignature) {
dierr = kDIErrFilesystemNotFound;
goto bail;
}
/*
* Decode it as an MBR and as a partition table. Figure out which
* one makes sense. If neither make sense, fail.
*/
bool hasMBR, hasBS;
hasMBR = UnpackMBR(blkBuf, &mbr);
hasBS = UnpackBootSector(blkBuf, &bs);
LOGI(" FAT hasMBR=%d hasBS=%d", hasMBR, hasBS);
if (!hasMBR && !hasBS) {
dierr = kDIErrFilesystemNotFound;
goto bail;
}
if (hasMBR) {
LOGI(" FAT partition table found:");
for (int i = 0; i < 4; i++) {
LOGI(" %d: type=0x%02x start LBA=%-9u size=%u",
i, mbr.parTab[i].type,
mbr.parTab[i].startLBA, mbr.parTab[i].size);
}
}
if (hasBS) {
LOGI(" FAT boot sector found:");
LOGI(" OEMName is '%.8s'", bs.oemName);
}
// looks good!
bail:
return dierr;
}
/*
* Test to see if the image is a FAT disk.
*/
/*static*/ DIError DiskFSFAT::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
/* must be block format, should be at least 360K */
if (!pImg->GetHasBlocks() || pImg->GetNumBlocks() < kExpectedMinBlocks)
return kDIErrFilesystemNotFound;
if (pImg->GetIsEmbedded()) // don't look for FAT inside CFFA!
return kDIErrFilesystemNotFound;
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImage(pImg, ordering[i]) == kDIErrNone) {
*pOrder = ordering[i];
*pFormat = DiskImg::kFormatMSDOS;
return kDIErrNone;
}
}
LOGI(" FAT didn't find valid FS");
return kDIErrFilesystemNotFound;
}
/*
* Get things rolling.
*/
DIError DiskFSFAT::Initialize(void)
{
DIError dierr = kDIErrNone;
strcpy(fVolumeName, "[MS-DOS]"); // max 11 chars
strcpy(fVolumeID, "FATxx [MS-DOS]");
// take the easy way out
fTotalBlocks = fpImg->GetNumBlocks();
CreateFakeFile();
SetVolumeUsageMap();
return dierr;
}
/*
* Blank out the volume usage map.
*/
void DiskFSFAT::SetVolumeUsageMap(void)
{
VolumeUsage::ChunkState cstate;
long block;
fVolumeUsage.Create(fpImg->GetNumBlocks());
cstate.isUsed = true;
cstate.isMarkedUsed = true;
cstate.purpose = VolumeUsage::kChunkPurposeUnknown;
for (block = fTotalBlocks-1; block >= 0; block--)
fVolumeUsage.SetChunkState(block, &cstate);
}
/*
* Fill a buffer with some interesting stuff, and add it to the file list.
*/
void DiskFSFAT::CreateFakeFile(void)
{
A2FileFAT* pFile;
char buf[768]; // currently running about 430
static const char* kFormatMsg =
"The FAT12/16/32 and NTFS filesystems are not supported. CiderPress knows\r\n"
"how to recognize MS-DOS and Windows volumes so that it can identify\r\n"
"PC data on removable media, but it does not know how to view or extract\r\n"
"files from them.\r\n"
"\r\n"
"Some information about this FAT volume:\r\n"
"\r\n"
" Volume name : '%s'\r\n"
" Volume size : %ld blocks (%.2fMB)\r\n"
"\r\n"
"(CiderPress limits itself to 8GB, so larger volume sizes may not be shown.)\r\n"
;
long capacity;
capacity = fTotalBlocks;
memset(buf, 0, sizeof(buf));
snprintf(buf, NELEM(buf)-1, kFormatMsg,
fVolumeName,
capacity,
(double) capacity / 2048.0);
buf[sizeof(buf) - 1] = '\0';
pFile = new A2FileFAT(this);
pFile->SetFakeFile(buf, strlen(buf));
strcpy(pFile->fFileName, "(not supported)");
AddFileToList(pFile);
}
/*
* ===========================================================================
* A2FileFAT
* ===========================================================================
*/
/*
* Dump the contents of the A2File structure.
*/
void A2FileFAT::Dump(void) const
{
LOGD("A2FileFAT '%s'", fFileName);
}
/*
* Not a whole lot to do.
*/
DIError A2FileFAT::Open(A2FileDescr** ppOpenFile, bool readOnly,
bool rsrcFork /*=false*/)
{
A2FDFAT* pOpenFile = NULL;
if (fpOpenFile != NULL)
return kDIErrAlreadyOpen;
if (rsrcFork)
return kDIErrForkNotFound;
assert(readOnly == true);
pOpenFile = new A2FDFAT(this);
fpOpenFile = pOpenFile;
*ppOpenFile = pOpenFile;
return kDIErrNone;
}
/*
* ===========================================================================
* A2FDFAT
* ===========================================================================
*/
/*
* Read a chunk of data from the fake file.
*/
DIError A2FDFAT::Read(void* buf, size_t len, size_t* pActual)
{
LOGD(" FAT reading %lu bytes from '%s' (offset=%ld)",
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
A2FileFAT* pFile = (A2FileFAT*) fpFile;
/* don't allow them to read past the end of the file */
if (fOffset + (long)len > pFile->fLength) {
if (pActual == NULL)
return kDIErrDataUnderrun;
len = (size_t) (pFile->fLength - fOffset);
}
if (pActual != NULL)
*pActual = len;
memcpy(buf, pFile->GetFakeFileBuf(), len);
fOffset += len;
return kDIErrNone;
}
/*
* Write data at the current offset.
*/
DIError A2FDFAT::Write(const void* buf, size_t len, size_t* pActual)
{
return kDIErrNotSupported;
}
/*
* Seek to a new offset.
*/
DIError A2FDFAT::Seek(di_off_t offset, DIWhence whence)
{
di_off_t fileLen = ((A2FileFAT*) fpFile)->fLength;
switch (whence) {
case kSeekSet:
if (offset < 0 || offset > fileLen)
return kDIErrInvalidArg;
fOffset = offset;
break;
case kSeekEnd:
if (offset > 0 || offset < -fileLen)
return kDIErrInvalidArg;
fOffset = fileLen + offset;
break;
case kSeekCur:
if (offset < -fOffset ||
offset >= (fileLen - fOffset))
{
return kDIErrInvalidArg;
}
fOffset += offset;
break;
default:
assert(false);
return kDIErrInvalidArg;
}
assert(fOffset >= 0 && fOffset <= fileLen);
return kDIErrNone;
}
/*
* Return current offset.
*/
di_off_t A2FDFAT::Tell(void)
{
return fOffset;
}
/*
* Release file state, and tell our parent to destroy us.
*/
DIError A2FDFAT::Close(void)
{
fpFile->CloseDescr(this);
return kDIErrNone;
}
/*
* Return the #of sectors/blocks in the file.
*/
long A2FDFAT::GetSectorCount(void) const
{
A2FileFAT* pFile = (A2FileFAT*) fpFile;
return (long) ((pFile->fLength+255) / 256);
}
long A2FDFAT::GetBlockCount(void) const
{
A2FileFAT* pFile = (A2FileFAT*) fpFile;
return (long) ((pFile->fLength+511) / 512);
}
/*
* Return the Nth track/sector in this file.
*/
DIError A2FDFAT::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
{
return kDIErrNotSupported;
}
/*
* Return the Nth 512-byte block in this file.
*/
DIError A2FDFAT::GetStorage(long blockIdx, long* pBlock) const
{
return kDIErrNotSupported;
}