/* * 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; }