ciderpress/diskimg/CFFA.cpp

592 lines
20 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* The "CFFA" DiskFS is a container class for multiple ProDOS and HFS volumes.
*
* The CFFA card doesn't have any RAM, so the author used a fixed partitioning
* scheme. You get 4 or 8 volumes -- depending on which firmware you jumper
* in -- at 32MB each. CF cards usually hold less than you would expect, so
* a 64MB card would have one 32MB volume and one less-than-32MB volume.
*
* With Dave's GS/OS driver, you get an extra drive or two at the end, at up
* to 1GB each. The driver only works in 4-volume mode.
*
* There is no magic CFFA block at the front, so it looks like a plain
* ProDOS or HFS volume. If the size is less than 32MB -- meaning there's
* only one volume -- we don't need to take an interest in the file,
* because the regular filesystem goodies will handle it just fine. If it's
* more than 32MB, we need to create a structure in which multiple volumes
* reside.
*
* The trick is finding all the volumes. The first four are easy. The
* fifth one is either another 32MB volume (if you're in 8-volume mode)
* or a volume whose size is somewhere between the amount of space left
* and 1GB. Not an issue until we get to CF cards > 128MB. We have to
* rely on the CFFA card making volumes as large as it can.
*
* I think it's reasonable to require that the first volume be either ProDOS
* or HFS. That way we don't go digging through large non-CFFA files when
* auto-probing.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* Figure out if this is a CFFA volume, and if so, whether it was formatted
* in 4-partition or 8-partition mode.
*
* The "imageOrder" parameter has no use here, because (in the current
* version) embedded parent volumes are implicitly ProDOS-ordered.
*
* "*pFormatFound" should be either a CFFA format or "unknown" on entry.
* If it's not "unknown", we will look for the specified format first.
* Otherwise, we look for 4-partition then 8-partition. The first one
* we find successfully is returned.
*
* Ideally we'd have some way to express ambiguity here, so that we could
* force the "disk format verification" dialog to come up. No such
* mechanism exists, and for now it doesn't seem worthwhile to add one.
*/
/*static*/ DIError DiskFSCFFA::TestImage(DiskImg* pImg,
DiskImg::SectorOrder imageOrder, DiskImg::FSFormat* pFormatFound)
{
DIError dierr;
long totalBlocks = pImg->GetNumBlocks();
long startBlock, maxBlocks, totalBlocksLeft;
long fsNumBlocks;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
//bool fiveIs32MB;
assert(totalBlocks > kEarlyVolExpectedSize);
// could be "generic" from an earlier override
if (*pFormatFound != DiskImg::kFormatCFFA4 &&
*pFormatFound != DiskImg::kFormatCFFA8)
{
*pFormatFound = DiskImg::kFormatUnknown;
}
LOGI("----- BEGIN CFFA SCAN (fmt=%d) -----", *pFormatFound);
startBlock = 0;
totalBlocksLeft = totalBlocks;
/*
* Look for a 32MB ProDOS or HFS volume in the first slot. If we
* don't find this, it's probably not CFFA. It's possible they just
* didn't format the first one, but that seems unlikely, and it's not
* unreasonable to insist that they format the first partition.
*/
maxBlocks = totalBlocksLeft;
if (maxBlocks > kEarlyVolExpectedSize)
maxBlocks = kEarlyVolExpectedSize;
dierr = OpenSubVolume(pImg, startBlock, maxBlocks, true,
&pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
LOGI(" CFFA failed opening sub-volume #1");
goto bail;
}
fsNumBlocks = pNewFS->GetFSNumBlocks();
delete pNewFS;
delete pNewImg;
if (fsNumBlocks != kEarlyVolExpectedSize &&
fsNumBlocks != kEarlyVolExpectedSize-1)
{
LOGI(" CFFA found fsNumBlocks=%ld in slot #1", fsNumBlocks);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
LOGI(" CFFA found good volume in slot #1");
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
assert(totalBlocksLeft > 0);
/*
* Look for a ProDOS or HFS volume <= 32MB in the second slot. If
* we don't find something here, and this is a 64MB card, then there's
* no advantage to using CFFA (in fact, the single-volume handling may
* be more convenient). If there's at least another 32MB, we continue
* looking.
*/
maxBlocks = totalBlocksLeft;
if (maxBlocks > kEarlyVolExpectedSize)
maxBlocks = kEarlyVolExpectedSize;
dierr = OpenSubVolume(pImg, startBlock, maxBlocks, true,
&pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
LOGI(" CFFA failed opening sub-volume #2");
if (maxBlocks < kEarlyVolExpectedSize)
goto bail;
// otherwise, assume they just didn't format #2, and keep going
} else {
fsNumBlocks = pNewFS->GetFSNumBlocks();
delete pNewFS;
delete pNewImg;
#if 0
if (fsNumBlocks != kEarlyVolExpectedSize &&
fsNumBlocks != kEarlyVolExpectedSize-1)
{
LOGI(" CFFA found fsNumBlocks=%ld in slot #2", fsNumBlocks);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
#endif
LOGI(" CFFA found good volume in slot #2");
}
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
if (totalBlocksLeft == 0) {
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
/*
* Skip #3 and #4.
*/
LOGI(" CFFA skipping over slot #3");
maxBlocks = kEarlyVolExpectedSize*2;
if (maxBlocks > totalBlocksLeft)
maxBlocks = totalBlocksLeft;
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
if (totalBlocksLeft == 0) {
// no more partitions to find; we're done
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
LOGI(" CFFA skipping over slot #4");
/*
* Partition #5. We know where it starts, but not how large it is.
* Could be 32MB, could be 1GB, could be anything between.
*
* CF cards come in power-of-two sizes -- 128MB, 256MB, etc. -- but
* we don't want to make assumptions here. It's possible we're
* looking at an odd-sized image file that some clever person is
* expecting to access with CiderPress.
*/
maxBlocks = totalBlocksLeft;
if (maxBlocks > kOneGB)
maxBlocks = kOneGB;
if (maxBlocks <= kEarlyVolExpectedSize) {
/*
* Only enough room for one <= 32MB volume. Not expected for a
* real CFFA card, unless they come in 160MB sizes.
*
* Treat it like 4-partition; it'll look like somebody slapped a
* 32MB volume into the first 1GB area.
*/
LOGI(" CFFA assuming odd-sized slot #5");
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
/*
* We could be looking at a 32MB ProDOS partition, 32MB HFS partition,
* or an up to 1GB HFS partition. We have to specify the size in
* the OpenSubVolume request, which means trying it both ways and
* finding a match from GetFSNumBlocks(). Complicating matters is
* truncation (ProDOS max 65535, not 65536) and round-off (not sure
* how HFS deals with left-over blocks).
*
* Start with <= 1GB.
*/
dierr = OpenSubVolume(pImg, startBlock, maxBlocks, true,
&pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
LOGI(" CFFA failed opening large sub-volume #5");
// if we can't get #5, don't bother looking for #6
// (we could try anyway, but it's easier to just skip it)
dierr = kDIErrNone;
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
fsNumBlocks = pNewFS->GetFSNumBlocks();
delete pNewFS;
delete pNewImg;
if (fsNumBlocks < 2 || fsNumBlocks > maxBlocks) {
LOGI(" CFFA WARNING: FSNumBlocks #5 reported blocks=%ld",
fsNumBlocks);
}
if (fsNumBlocks == kEarlyVolExpectedSize-1 ||
fsNumBlocks == kEarlyVolExpectedSize)
{
LOGI(" CFFA #5 is a 32MB volume");
// tells us nothing -- could still be 4 or 8, so we keep going
maxBlocks = kEarlyVolExpectedSize;
} else if (fsNumBlocks > kEarlyVolExpectedSize) {
// must be a GS/OS 1GB area
LOGI(" CFFA #5 is larger than 32MB");
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
} else {
LOGI(" CFFA #5 was unexpectedly small (%ld blocks)", fsNumBlocks);
// just stop now
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
if (!totalBlocksLeft) {
LOGI(" CFFA got 5 volumes");
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
/*
* Various possibilities for slots 5 and up:
* A. Card in 4-partition mode. 5th partition isn't formatted. Don't
* bother looking for 6th. [already handled]
* B. Card in 4-partition mode. 5th partition is >32MB HFS. 6th
* partition will be at +1GB. [already handled]
* C. Card in 4-partition mode. 5th partition is 32MB ProDOS. 6th
* partition will be at +1GB.
* D. Card in 8-partition mode. 5th partition is 32MB HFS. 6th
* partition will be at +32MB.
* E. Card in 8-partition mode. 5th partition is 32MB ProDOS. 6th
* partition will be at +32MB.
*
* I'm ignoring D on the off chance somebody could create a 32MB HFS
* partition in the 1GB space. D and E are handled alike.
*
* The difference between C and D/E can *usually* be determined by
* opening up a 6th partition in the two expected locations.
*/
LOGI(" CFFA probing 6th slot for 4-vs-8");
/*
* Look in two different places. If we find something at the
* +32MB mark, assume it's in "8 mode". If we find something at
* the +1GB mark, assume it's in "4 + GS/OS mode". If both exist
* we have a problem.
*/
bool foundSmall, foundGig;
foundSmall = false;
maxBlocks = totalBlocksLeft;
if (maxBlocks > kEarlyVolExpectedSize)
maxBlocks = kEarlyVolExpectedSize;
dierr = OpenSubVolume(pImg, startBlock + kEarlyVolExpectedSize,
maxBlocks, true, &pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
LOGI(" CFFA no vol #6 found at +32MB");
} else {
foundSmall = true;
delete pNewFS;
delete pNewImg;
}
foundGig = false;
// no need to look if we don't have at least 1GB left!
if (totalBlocksLeft >= kOneGB) {
maxBlocks = totalBlocksLeft;
if (maxBlocks > kOneGB)
maxBlocks = kOneGB;
dierr = OpenSubVolume(pImg, startBlock + kOneGB,
maxBlocks, true, &pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
LOGI(" CFFA no vol #6 found at +1GB");
} else {
foundGig = true;
delete pNewFS;
delete pNewImg;
}
}
dierr = kDIErrNone;
if (!foundSmall && !foundGig) {
LOGI(" CFFA no valid filesystem found in 6th position");
*pFormatFound = DiskImg::kFormatCFFA4;
// don't bother looking for 7 and 8
} else if (foundSmall && foundGig) {
LOGI(" CFFA WARNING: found valid volumes at +32MB *and* +1GB");
// default to 4-partition mode
if (*pFormatFound == DiskImg::kFormatUnknown)
*pFormatFound = DiskImg::kFormatCFFA4;
} else if (foundGig) {
LOGI(" CFFA found 6th volume at +1GB, assuming 4-mode w/GSOS");
if (fsNumBlocks < 2 || fsNumBlocks > kOneGB) {
LOGI(" CFFA WARNING: FSNumBlocks #6 reported as %ld",
fsNumBlocks);
}
*pFormatFound = DiskImg::kFormatCFFA4;
} else if (foundSmall) {
LOGI(" CFFA found 6th volume at +32MB, assuming 8-mode");
if (fsNumBlocks < 2 || fsNumBlocks > kEarlyVolExpectedSize) {
LOGI(" CFFA WARNING: FSNumBlocks #6 reported as %ld",
fsNumBlocks);
}
*pFormatFound = DiskImg::kFormatCFFA8;
} else {
assert(false); // how'd we get here??
}
// done!
bail:
LOGI("----- END CFFA SCAN (err=%d format=%d) -----",
dierr, *pFormatFound);
if (dierr == kDIErrNone) {
assert(*pFormatFound != DiskImg::kFormatUnknown);
} else {
*pFormatFound = DiskImg::kFormatUnknown;
}
return dierr;
}
/*
* Open up a sub-volume.
*
* If "scanOnly" is set, the full DiskFS initialization isn't performed.
* We just do enough to get the volume size info.
*/
/*static*/ DIError DiskFSCFFA::OpenSubVolume(DiskImg* pImg, long startBlock,
long numBlocks, bool scanOnly, DiskImg** ppNewImg, DiskFS** ppNewFS)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
pNewImg = new DiskImg;
if (pNewImg == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewImg->OpenImage(pImg, startBlock, numBlocks);
if (dierr != kDIErrNone) {
LOGI(" CFFASub: OpenImage(%ld,%ld) failed (err=%d)",
startBlock, numBlocks, dierr);
goto bail;
}
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" CFFASub: analysis failed (err=%d)", dierr);
goto bail;
}
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
LOGI(" CFFASub: unable to identify filesystem at %ld",
startBlock);
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* open a DiskFS for the sub-image */
LOGI(" CFFASub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
pNewFS = pNewImg->OpenAppropriateDiskFS();
if (pNewFS == NULL) {
LOGI(" CFFASub: OpenAppropriateDiskFS failed");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* we encapsulate arbitrary stuff, so encourage child to scan */
pNewFS->SetScanForSubVolumes(kScanSubEnabled);
/*
* Load the files from the sub-image. When doing our initial tests,
* or when loading data for the volume copier, we don't want to dig
* into our sub-volumes, just figure out what they are and where.
*/
InitMode initMode;
if (scanOnly)
initMode = kInitHeaderOnly;
else
initMode = kInitFull;
dierr = pNewFS->Initialize(pNewImg, initMode);
if (dierr != kDIErrNone) {
LOGI(" CFFASub: error %d reading list of files from disk", dierr);
goto bail;
}
bail:
if (dierr != kDIErrNone) {
delete pNewFS;
delete pNewImg;
} else {
*ppNewImg = pNewImg;
*ppNewFS = pNewFS;
}
return dierr;
}
/*
* Check to see if this is a CFFA volume.
*/
/*static*/ DIError DiskFSCFFA::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
if (pImg->GetNumBlocks() < kMinInterestingBlocks)
return kDIErrFilesystemNotFound;
if (pImg->GetIsEmbedded()) // don't look for CFFA inside CFFA!
return kDIErrFilesystemNotFound;
/* assume ProDOS -- shouldn't matter, since it's embedded */
if (TestImage(pImg, DiskImg::kSectorOrderProDOS, pFormat) == kDIErrNone) {
assert(*pFormat == DiskImg::kFormatCFFA4 ||
*pFormat == DiskImg::kFormatCFFA8);
*pOrder = DiskImg::kSectorOrderProDOS;
return kDIErrNone;
}
// make sure we didn't tamper with it
assert(*pFormat == DiskImg::kFormatUnknown);
LOGI(" FS didn't find valid CFFA");
return kDIErrFilesystemNotFound;
}
/*
* Prep the CFFA "container" for use.
*/
DIError DiskFSCFFA::Initialize(void)
{
DIError dierr = kDIErrNone;
LOGI("CFFA initializing (scanForSub=%d)", fScanForSubVolumes);
/* seems pointless *not* to, but we just do what we're told */
if (fScanForSubVolumes != kScanSubDisabled) {
dierr = FindSubVolumes();
if (dierr != kDIErrNone)
return dierr;
}
/* blank out the volume usage map */
SetVolumeUsageMap();
return dierr;
}
/*
* Find the various sub-volumes and open them.
*
* We don't handle the volume specially unless it's at least 32MB, which
* means there are at least 2 partitions.
*/
DIError DiskFSCFFA::FindSubVolumes(void)
{
DIError dierr;
long startBlock, blocksLeft;
startBlock = 0;
blocksLeft = fpImg->GetNumBlocks();
if (fpImg->GetFSFormat() == DiskImg::kFormatCFFA4) {
LOGI(" CFFA opening 4+2 volumes");
dierr = AddVolumeSeries(0, 4, kEarlyVolExpectedSize, /*ref*/startBlock,
/*ref*/blocksLeft);
if (dierr != kDIErrNone)
goto bail;
LOGI(" CFFA after first 4, startBlock=%ld blocksLeft=%ld",
startBlock, blocksLeft);
if (blocksLeft > 0) {
dierr = AddVolumeSeries(4, 2, kOneGB, /*ref*/startBlock,
/*ref*/blocksLeft);
if (dierr != kDIErrNone)
goto bail;
}
} else if (fpImg->GetFSFormat() == DiskImg::kFormatCFFA8) {
LOGI(" CFFA opening 8 volumes");
dierr = AddVolumeSeries(0, 8, kEarlyVolExpectedSize, /*ref*/startBlock,
/*ref*/blocksLeft);
if (dierr != kDIErrNone)
goto bail;
} else {
assert(false);
return kDIErrInternal;
}
if (blocksLeft != 0) {
LOGI(" CFFA ignoring leftover %ld blocks", blocksLeft);
}
bail:
return dierr;
}
/*
* Add a series of equal-sized volumes.
*
* Updates "startBlock" and "totalBlocksLeft".
*/
DIError DiskFSCFFA::AddVolumeSeries(int start, int count, long blocksPerVolume,
long& startBlock, long& totalBlocksLeft)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
long maxBlocks, fsNumBlocks;
bool scanOnly = false;
/* used by volume copier, to avoid deep scan */
if (GetScanForSubVolumes() == kScanSubContainerOnly)
scanOnly = true;
for (int i = start; i < start+count; i++) {
maxBlocks = blocksPerVolume;
if (maxBlocks > totalBlocksLeft)
maxBlocks = totalBlocksLeft;
dierr = OpenSubVolume(fpImg, startBlock, maxBlocks, scanOnly,
&pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
LOGI(" CFFA failed opening sub-volume %d (not formatted?)", i);
/* create a fake one to represent the partition */
dierr = CreatePlaceholder(startBlock, maxBlocks, NULL, NULL,
&pNewImg, &pNewFS);
if (dierr == kDIErrNone) {
AddSubVolumeToList(pNewImg, pNewFS);
} else {
LOGI(" CFFA unable to create placeholder (%ld, %ld) (err=%d)",
startBlock, maxBlocks, dierr);
goto bail;
}
} else {
fsNumBlocks = pNewFS->GetFSNumBlocks();
if (fsNumBlocks < 2 || fsNumBlocks > blocksPerVolume) {
LOGI(" CFFA WARNING: FSNumBlocks #%d reported as %ld",
i, fsNumBlocks);
}
AddSubVolumeToList(pNewImg, pNewFS);
}
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
if (!totalBlocksLeft)
break; // all done
}
bail:
return dierr;
}