ciderpress/diskimg/DiskImg.cpp
Andy McFadden edb7c13120 Fix diskimg OpenImage
The OpenImage method had an overload that took void*.  This turns out
to be a bad idea, because void* matches any pointer type that didn't
match something else.  So the WCHAR* filenames were going to the "open
from buffer" method rather than the "open from file" variant.

A less important issue is whether open-from-buffer should take a const
or non-const pointer.  If the "readOnly" boolean flag is not set, then
the contents can be altered and const is inappropriate.  The best course
seems to be to drop the boolean flag as an argument, and just have two
different methods.
2014-11-23 10:34:57 -08:00

3534 lines
112 KiB
C++

/*
* CiderPress
* Copyright (C) 2009 by CiderPress authors. All Rights Reserved.
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of the DiskImg class.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
#include "TwoImg.h"
/*
* ===========================================================================
* DiskImg
* ===========================================================================
*/
/*
* Standard NibbleDescr profiles.
*
* These will be tried in the order in which they appear here.
*
* IMPORTANT: if you add or remove an entry, update the StdNibbleDescr enum
* in DiskImg.h.
*
* Formats that allow the data checksum to be ignored should NOT be written.
* It's possible that the DOS on the disk is ignoring the checksums, but
* it's more likely that they're using a non-standard seed, and the newly-
* written sectors will have the wrong checksum value.
*
* Non-standard headers are usually okay, because we don't rewrite the
* headers, just the sector contents.
*/
/*static*/ const DiskImg::NibbleDescr DiskImg::kStdNibbleDescrs[] = {
{
"DOS 3.3 Standard",
16,
{ 0xd5, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb },
0x00, // checksum seed
true, // verify checksum
true, // verify track
2, // epilog verify count
{ 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb },
0x00, // checksum seed
true, // verify checksum
2, // epilog verify count
kNibbleEnc62,
kNibbleSpecialNone,
},
{
"DOS 3.3 Patched",
16,
{ 0xd5, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb },
0x00, // checksum seed
false, // verify checksum
false, // verify track
0, // epilog verify count
{ 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb },
0x00, // checksum seed
true, // verify checksum
0, // epilog verify count
kNibbleEnc62,
kNibbleSpecialNone,
},
{
"DOS 3.3 Ignore Checksum",
16,
{ 0xd5, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb },
0x00, // checksum seed
false, // verify checksum
false, // verify track
0, // epilog verify count
{ 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb },
0x00, // checksum seed
false, // verify checksum
0, // epilog verify count
kNibbleEnc62,
kNibbleSpecialNone,
},
{
"DOS 3.2 Standard",
13,
{ 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb },
0x00,
true,
true,
2,
{ 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb },
0x00,
true,
2,
kNibbleEnc53,
kNibbleSpecialNone,
},
{
"DOS 3.2 Patched",
13,
{ 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb },
0x00,
false,
false,
0,
{ 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb },
0x00,
true,
0,
kNibbleEnc53,
kNibbleSpecialNone,
},
{
"Muse DOS 3.2", // standard DOS 3.2 with doubled sectors
13,
{ 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb },
0x00,
true,
true,
2,
{ 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb },
0x00,
true,
2,
kNibbleEnc53,
kNibbleSpecialMuse,
},
{
"RDOS 3.3", // SSI 16-sector RDOS, with altered headers
16,
{ 0xd4, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb },
0x00,
true,
true,
0, // epilog verify count
{ 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb },
0x00,
true,
2,
kNibbleEnc62,
kNibbleSpecialSkipFirstAddrByte,
/* odd tracks use d4aa96, even tracks use d5aa96 */
},
{
"RDOS 3.2", // SSI 13-sector RDOS, with altered headers
13,
{ 0xd4, 0xaa, 0xb7 }, { 0xde, 0xaa, 0xeb },
0x00,
true,
true,
2,
{ 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb },
0x00,
true,
2,
kNibbleEnc53,
kNibbleSpecialNone,
},
{
"Custom", // reserve space for empty slot
0,
},
};
/*static*/ const DiskImg::NibbleDescr*
DiskImg::GetStdNibbleDescr(StdNibbleDescr idx)
{
if ((int)idx < 0 || (int)idx >= (int) NELEM(kStdNibbleDescrs))
return NULL;
return &kStdNibbleDescrs[(int)idx];
}
/*
* Initialize the members during construction.
*/
DiskImg::DiskImg(void)
{
assert(Global::GetAppInitCalled());
fOuterFormat = kOuterFormatUnknown;
fFileFormat = kFileFormatUnknown;
fPhysical = kPhysicalFormatUnknown;
fpNibbleDescr = NULL;
fOrder = kSectorOrderUnknown;
fFormat = kFormatUnknown;
fFileSysOrder = kSectorOrderUnknown;
fSectorPairing = false;
fSectorPairOffset = -1;
fpOuterGFD = NULL;
fpWrapperGFD = NULL;
fpDataGFD = NULL;
fpOuterWrapper = NULL;
fpImageWrapper = NULL;
fpParentImg = NULL;
fDOSVolumeNum = kVolumeNumNotSet;
fOuterLength = -1;
fWrappedLength = -1;
fLength = -1;
fExpandable = false;
fReadOnly = true;
fDirty = false;
fHasSectors = false;
fHasBlocks = false;
fHasNibbles = false;
fNumTracks = -1;
fNumSectPerTrack = -1;
fNumBlocks = -1;
fpScanProgressCallback = NULL;
/*
* Create a working copy of the nibble descr table. We want to leave
* open the possibility of applications editing or discarding entries,
* so we work off of a copy.
*
* Ideally we'd allow these to be set per-track, so that certain odd
* formats could be handled transparently (e.g. Muse tweaked DOS 3.2)
* for formatting as well as reading.
*/
assert(kStdNibbleDescrs[kNibbleDescrCustom].numSectors == 0);
assert(kNibbleDescrCustom == NELEM(kStdNibbleDescrs)-1);
fpNibbleDescrTable = new NibbleDescr[NELEM(kStdNibbleDescrs)];
fNumNibbleDescrEntries = NELEM(kStdNibbleDescrs);
memcpy(fpNibbleDescrTable, kStdNibbleDescrs, sizeof(kStdNibbleDescrs));
fNibbleTrackBuf = NULL;
fNibbleTrackLoaded = -1;
fNuFXCompressType = kNuThreadFormatLZW2;
fNotes = NULL;
fpBadBlockMap = NULL;
fDiskFSRefCnt = 0;
}
/*
* Throw away local storage.
*/
DiskImg::~DiskImg(void)
{
if (fpDataGFD != NULL) {
LOGI("~DiskImg closing GenericFD(s)");
}
(void) CloseImage();
delete[] fpNibbleDescrTable;
delete[] fNibbleTrackBuf;
delete[] fNotes;
delete fpBadBlockMap;
/* normally these will be closed, but perhaps not if something failed */
if (fpOuterGFD != NULL)
delete fpOuterGFD;
if (fpWrapperGFD != NULL)
delete fpWrapperGFD;
if (fpDataGFD != NULL)
delete fpDataGFD;
if (fpOuterWrapper != NULL)
delete fpOuterWrapper;
if (fpImageWrapper != NULL)
delete fpImageWrapper;
fDiskFSRefCnt = 100; // flag as freed
}
/*
* Set the nibble descr pointer.
*/
void
DiskImg::SetNibbleDescr(int idx)
{
assert(idx >= 0 && idx < kNibbleDescrMAX);
fpNibbleDescr = &fpNibbleDescrTable[idx];
}
/*
* Set up a custom nibble descriptor.
*/
void
DiskImg::SetCustomNibbleDescr(const NibbleDescr* pDescr)
{
if (pDescr == NULL) {
fpNibbleDescr = NULL;
} else {
assert(fpNibbleDescrTable != NULL);
//LOGI("Overwriting entry %d with new value (special=%d)",
// kNibbleDescrCustom, pDescr->special);
fpNibbleDescrTable[kNibbleDescrCustom] = *pDescr;
fpNibbleDescr = &fpNibbleDescrTable[kNibbleDescrCustom];
}
}
/*
* Open a volume or a file on disk.
*
* For Windows, we need to handle logical/physical volumes specially. If
* the filename matches the appropriate pattern, use a different GFD.
*/
DIError
DiskImg::OpenImage(const char* pathName, char fssep, bool readOnly)
{
DIError dierr = kDIErrNone;
bool isWinDevice = false;
if (fpDataGFD != NULL) {
LOGI(" DI already open!");
return kDIErrAlreadyOpen;
}
LOGI(" DI OpenImage '%s' '%.1s' ro=%d", pathName, &fssep, readOnly);
fReadOnly = readOnly;
#ifdef _WIN32
if ((fssep == '\0' || fssep == '\\') &&
pathName[0] >= 'A' && pathName[0] <= 'Z' &&
pathName[1] == ':' && pathName[2] == '\\' &&
pathName[3] == '\0')
{
isWinDevice = true; // logical volume ("A:\")
}
if ((fssep == '\0' || fssep == '\\') &&
isdigit(pathName[0]) && isdigit(pathName[1]) &&
pathName[2] == ':' && pathName[3] == '\\' &&
pathName[4] == '\0')
{
isWinDevice = true; // physical volume ("80:\")
}
if ((fssep == '\0' || fssep == '\\') &&
strncmp(pathName, kASPIDev, strlen(kASPIDev)) == 0 &&
pathName[strlen(pathName)-1] == '\\')
{
isWinDevice = true; // ASPI volume ("ASPI:x:y:z\")
}
#endif
if (isWinDevice) {
#ifdef _WIN32
GFDWinVolume* pGFDWinVolume = new GFDWinVolume;
dierr = pGFDWinVolume->Open(pathName, fReadOnly);
if (dierr != kDIErrNone) {
delete pGFDWinVolume;
goto bail;
}
fpWrapperGFD = pGFDWinVolume;
// Use a unique extension to skip some of the probing.
dierr = AnalyzeImageFile("CPDevice.cp-win-vol", '\0');
if (dierr != kDIErrNone)
goto bail;
#endif
} else {
GFDFile* pGFDFile = new GFDFile;
dierr = pGFDFile->Open(pathName, fReadOnly);
if (dierr != kDIErrNone) {
delete pGFDFile;
goto bail;
}
//fImageFileName = new char[strlen(pathName) + 1];
//strcpy(fImageFileName, pathName);
fpWrapperGFD = pGFDFile;
pGFDFile = NULL;
dierr = AnalyzeImageFile(pathName, fssep);
if (dierr != kDIErrNone)
goto bail;
}
assert(fpDataGFD != NULL);
bail:
return dierr;
}
DIError DiskImg::OpenImageFromBufferRO(const uint8_t* buffer, long length) {
return OpenImageFromBuffer(const_cast<uint8_t*>(buffer), length, true);
}
DIError DiskImg::OpenImageFromBufferRW(uint8_t* buffer, long length) {
return OpenImageFromBuffer(buffer, length, false);
}
/*
* Open from a buffer, which could point to unadorned ready-to-go content
* or to a preloaded image file.
*/
DIError
DiskImg::OpenImageFromBuffer(uint8_t* buffer, long length, bool readOnly)
{
if (fpDataGFD != NULL) {
LOGI(" DI already open!");
return kDIErrAlreadyOpen;
}
LOGI(" DI OpenImage %08lx %ld ro=%d", (long) buffer, length, readOnly);
DIError dierr;
GFDBuffer* pGFDBuffer;
fReadOnly = readOnly;
pGFDBuffer = new GFDBuffer;
dierr = pGFDBuffer->Open(buffer, length, false, false, readOnly);
if (dierr != kDIErrNone) {
delete pGFDBuffer;
return dierr;
}
fpWrapperGFD = pGFDBuffer;
pGFDBuffer = NULL;
dierr = AnalyzeImageFile("", '\0');
if (dierr != kDIErrNone)
return dierr;
assert(fpDataGFD != NULL);
return kDIErrNone;
}
/*
* Open a range of blocks from an already-open disk image. This is only
* useful for things like UNIDOS volumes, which don't have an associated
* file in the image and are linear.
*
* The "read only" flag is inherited from the parent.
*
* For embedded images with visible file structure, we should be using
* an EmbeddedFD instead. [Note these were never implemented.]
*
* NOTE: there is an implicit ProDOS block ordering imposed on the parent
* image. It turns out that all of our current embedded parents use
* ProDOS-ordered blocks, so it works out okay, but the "linear" requirement
* above goes beyond just having contiguous blocks.
*/
DIError
DiskImg::OpenImage(DiskImg* pParent, long firstBlock, long numBlocks)
{
LOGI(" DI OpenImage parent=0x%08lx %ld %ld", (long) pParent, firstBlock,
numBlocks);
if (fpDataGFD != NULL) {
LOGI(" DI already open!");
return kDIErrAlreadyOpen;
}
if (pParent == NULL || firstBlock < 0 || numBlocks <= 0 ||
firstBlock + numBlocks > pParent->GetNumBlocks())
{
assert(false);
return kDIErrInvalidArg;
}
fReadOnly = pParent->GetReadOnly(); // very important
DIError dierr;
GFDGFD* pGFDGFD;
pGFDGFD = new GFDGFD;
dierr = pGFDGFD->Open(pParent->fpDataGFD, firstBlock * kBlockSize, fReadOnly);
if (dierr != kDIErrNone) {
delete pGFDGFD;
return dierr;
}
fpDataGFD = pGFDGFD;
assert(fpWrapperGFD == NULL);
/*
* This replaces the call to "analyze image file" because we know we
* already have an open file with specific characteristics.
*/
//fOffset = pParent->fOffset + kBlockSize * firstBlock;
fLength = numBlocks * kBlockSize;
fOuterLength = fWrappedLength = fLength;
fFileFormat = kFileFormatUnadorned;
fPhysical = pParent->fPhysical;
fOrder = pParent->fOrder;
fpParentImg = pParent;
return dierr;
}
DIError
DiskImg::OpenImage(DiskImg* pParent, long firstTrack, long firstSector,
long numSectors)
{
LOGI(" DI OpenImage parent=0x%08lx %ld %ld %ld", (long) pParent,
firstTrack, firstSector, numSectors);
if (fpDataGFD != NULL) {
LOGI(" DI already open!");
return kDIErrAlreadyOpen;
}
if (pParent == NULL)
return kDIErrInvalidArg;
int prntSectPerTrack = pParent->GetNumSectPerTrack();
int lastTrack = firstTrack +
(numSectors + prntSectPerTrack-1) / prntSectPerTrack;
if (firstTrack < 0 || numSectors <= 0 ||
lastTrack > pParent->GetNumTracks())
{
return kDIErrInvalidArg;
}
fReadOnly = pParent->GetReadOnly(); // very important
DIError dierr;
GFDGFD* pGFDGFD;
pGFDGFD = new GFDGFD;
dierr = pGFDGFD->Open(pParent->fpDataGFD,
kSectorSize * firstTrack * prntSectPerTrack, fReadOnly);
if (dierr != kDIErrNone) {
delete pGFDGFD;
return dierr;
}
fpDataGFD = pGFDGFD;
assert(fpWrapperGFD == NULL);
/*
* This replaces the call to "analyze image file" because we know we
* already have an open file with specific characteristics.
*/
assert(firstSector == 0); // else fOffset calculation breaks
//fOffset = pParent->fOffset + kSectorSize * firstTrack * prntSectPerTrack;
fLength = numSectors * kSectorSize;
fOuterLength = fWrappedLength = fLength;
fFileFormat = kFileFormatUnadorned;
fPhysical = pParent->fPhysical;
fOrder = pParent->fOrder;
fpParentImg = pParent;
return dierr;
}
/*
* Enable sector pairing. Useful for OzDOS.
*/
void
DiskImg::SetPairedSectors(bool enable, int idx)
{
fSectorPairing = enable;
fSectorPairOffset = idx;
if (enable) {
assert(idx == 0 || idx == 1);
}
}
/*
* Close the image, freeing resources.
*
* If we write to a child DiskImg, it's responsible for setting the "dirty"
* flag in its parent (and so on up the chain). That's necessary so that,
* when we close the file, changes made to a child DiskImg cause the parent
* to do any necessary recompression.
*
* [ This is getting called even when image creation failed with an error.
* This is probably the correct behavior, but we may want to be aborting the
* image creation instead of completing it. That's a higher-level decision
* though. ++ATM 20040506 ]
*/
DIError
DiskImg::CloseImage(void)
{
DIError dierr;
LOGI("CloseImage 0x%p", this);
/* check for DiskFS objects that still point to us */
if (fDiskFSRefCnt != 0) {
LOGI("ERROR: CloseImage: fDiskFSRefCnt=%d", fDiskFSRefCnt);
assert(false); //DebugBreak();
}
/*
* Flush any changes.
*/
dierr = FlushImage(kFlushAll);
if (dierr != kDIErrNone)
return dierr;
/*
* Clean up. Close GFD, OrigGFD, and OuterGFD. Delete ImageWrapper
* and OuterWrapper.
*
* In some cases we will have the file open more than once (e.g. a
* NuFX archive, which must be opened on disk).
*/
if (fpDataGFD != NULL) {
fpDataGFD->Close();
delete fpDataGFD;
fpDataGFD = NULL;
}
if (fpWrapperGFD != NULL) {
fpWrapperGFD->Close();
delete fpWrapperGFD;
fpWrapperGFD = NULL;
}
if (fpOuterGFD != NULL) {
fpOuterGFD->Close();
delete fpOuterGFD;
fpOuterGFD = NULL;
}
delete fpImageWrapper;
fpImageWrapper = NULL;
delete fpOuterWrapper;
fpOuterWrapper = NULL;
return dierr;
}
/*
* Flush data to disk.
*
* The only time this really needs to do anything on a disk image file is
* when we have compressed data (NuFX, DDD, .gz, .zip). The uncompressed
* wrappers either don't do anything ("unadorned") or just update some
* header fields (DiskCopy42).
*
* If "mode" is kFlushFastOnly, we only flush the formats that don't really
* need flushing. This is part of a scheme to keep the disk contents in a
* reasonable state on the off chance we crash with a modified file open.
* It also helps the user understand when changes are being made immediately
* vs. when they're written to memory and compressed later. We could just
* refuse to raise the "dirty" flag when modifying "simple" file formats,
* but that would change the meaning of the flag from "something has been
* changed" to "what's in the file and what's in memory differ". I want it
* to be a "dirty" flag.
*/
DIError
DiskImg::FlushImage(FlushMode mode)
{
DIError dierr = kDIErrNone;
LOGI(" DI FlushImage (dirty=%d mode=%d)", fDirty, mode);
if (!fDirty)
return kDIErrNone;
if (fpDataGFD == NULL) {
/*
* This can happen if we tried to create a disk image but failed, e.g.
* couldn't create the output file because of access denied on the
* directory. There's no data, therefore nothing to flush, but the
* "dirty" flag is set because CreateImageCommon sets it almost
* immediately.
*/
LOGI(" (disk must've failed during creation)");
fDirty = false;
return kDIErrNone;
}
if (mode == kFlushFastOnly &&
((fpImageWrapper != NULL && !fpImageWrapper->HasFastFlush()) ||
(fpOuterWrapper != NULL && !fpOuterWrapper->HasFastFlush()) ))
{
LOGI("DI fast flush requested, but one or both wrappers are slow");
return kDIErrNone;
}
/*
* Step 1: make sure any local caches have been flushed.
*/
/* (none) */
/*
* Step 2: push changes from fpDataGFD to fpWrapperGFD. This will
* cause ImageWrapper to rebuild itself (SHK, DDD, whatever). In
* some cases this amounts to copying the data on top of itself,
* which we can avoid easily.
*
* Embedded volumes don't have wrappers; when you write to an
* embedded volume, it passes straight through to the parent.
*
* (Note to self: formats like NuFX that write to a temp file and then
* rename over the old will close fpWrapperGFD and just access it
* directly. This is bad, because it doesn't allow them to have an
* "outer" format, but it's the way life is. The point is that it's
* okay for fpWrapperGFD to be non-NULL but represent a closed file,
* so long as the "Flush" function has it figured out.)
*/
if (fpWrapperGFD != NULL) {
LOGI(" DI flushing data changes to wrapper (fLen=%ld fWrapLen=%ld)",
(long) fLength, (long) fWrappedLength);
dierr = fpImageWrapper->Flush(fpWrapperGFD, fpDataGFD, fLength,
&fWrappedLength);
if (dierr != kDIErrNone) {
LOGI(" ERROR: wrapper flush failed (err=%d)", dierr);
return dierr;
}
/* flush the GFD in case it's a Win32 volume with block caching */
dierr = fpWrapperGFD->Flush();
} else {
assert(fpParentImg != NULL);
}
/*
* Step 3: if we have an fpOuterGFD, rebuild the file with the data
* in fpWrapperGFD.
*/
if (fpOuterWrapper != NULL) {
LOGI(" DI saving wrapper to outer, fWrapLen=%ld",
(long) fWrappedLength);
assert(fpOuterGFD != NULL);
dierr = fpOuterWrapper->Save(fpOuterGFD, fpWrapperGFD,
fWrappedLength);
if (dierr != kDIErrNone) {
LOGI(" ERROR: outer save failed (err=%d)", dierr);
return dierr;
}
}
fDirty = false;
return kDIErrNone;
}
/*
* Given the filename extension and a GFD, figure out what's inside.
*
* The filename extension should give us some idea what to expect:
* SHK, SDK, BXY - ShrinkIt compressed disk image
* GZ - gzip-compressed file (with something else inside)
* ZIP - ZIP archive with a single disk image inside
* DDD - DDD, DDD Pro, or DDD5.0 compressed image
* DSK - DiskCopy 4.2 or DO/PO
* DC - DiskCopy 4.2 (or 6?)
* DC6 - DiskCopy 6 (usually just raw sectors)
* DO, PO, D13, RAW? - DOS-order or ProDOS-order uncompressed
* IMG - Copy ][+ image (unadorned, physical sector order)
* HDV - virtual hard drive image
* NIB, RAW? - nibblized image
* (no extension) uncompressed
* cp-win-vol - our "magic" extension to indicate a Windows logical volume
*
* We can also examine the file length to see if it's a standard size
* (140K, 800K) and look for magic values in the header.
*
* If we can access the contents directly from disk, we do so. It's
* possibly more efficient to load the whole thing into memory, but if
* we have that much memory then the OS should cache it for us. (I have
* some 20MB disk images from my hard drive that shouldn't be loaded
* in their entirety. Certainly don't want to load a 512MB CFFA image.)
*
* On input, the following fields must be set:
* fpWrapperGFD - GenericFD for the file pointed to by "pathname" (or for a
* memory buffer if this is a sub-volume)
*
* On success, the following fields will be set:
* fWrappedLength, fOuterLength - set appropriately
* fpDataGFD - GFD for the raw data, possibly just a GFDGFD with an offset
* fLength - length of unadorned data in the file, or the length of
* data stored in fBuffer (test for fBuffer!=NULL)
* fFileFormat - set to the overall file format, mostly interesting
* for identification of the file "wrapper"
* fPhysicalFormat - set to the type of data this holds
* (maybe) fOrder - set when the file format or extension dictates, e.g.
* 2MG or *.po; not always reliable
* (maybe) fDOSVolumeNum - set to DOS volume number from wrapper
*
* This may set fReadOnly if one of the wrappers looks okay but is reporting
* a bad checksum.
*/
DIError
DiskImg::AnalyzeImageFile(const char* pathName, char fssep)
{
DIError dierr = kDIErrNone;
FileFormat probableFormat;
bool reliableExt;
const char* ext = FindExtension(pathName, fssep);
char* extBuf = NULL; // uses malloc/free
bool needExtFromOuter = false;
if (ext != NULL) {
assert(*ext == '.');
ext++;
} else
ext = "";
LOGI(" DI AnalyzeImageFile '%s' '%c' ext='%s'",
pathName, fssep, ext);
/* sanity check: nobody should have configured these yet */
assert(fOuterFormat == kOuterFormatUnknown);
assert(fFileFormat == kFileFormatUnknown);
assert(fOrder == kSectorOrderUnknown);
assert(fFormat == kFormatUnknown);
fLength = -1;
dierr = fpWrapperGFD->Seek(0, kSeekEnd);
if (dierr != kDIErrNone) {
LOGI(" DI Couldn't seek to end of wrapperGFD");
goto bail;
}
fWrappedLength = fOuterLength = fpWrapperGFD->Tell();
/* quick test for zero-length files */
if (fWrappedLength == 0)
return kDIErrUnrecognizedFileFmt;
/*
* Start by checking for a zip/gzip "wrapper wrapper". We want to strip
* that away before we do anything else. Because web sites tend to
* gzip everything in sight whether it needs it or not, we treat this
* as a special case and assume that anything could be inside.
*
* Some cases are difficult to handle, e.g. ".SDK", since NufxLib
* doesn't let us open an archive that is sitting in memory.
*
* We could also handle disk images stored as ordinary files stored
* inside SHK. Not much point in handling multiple files down at
* this level though.
*/
if (strcasecmp(ext, "gz") == 0 &&
OuterGzip::Test(fpWrapperGFD, fOuterLength) == kDIErrNone)
{
LOGI(" DI found gz outer wrapper");
fpOuterWrapper = new OuterGzip();
if (fpOuterWrapper == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
fOuterFormat = kOuterFormatGzip;
/* drop the ".gz" and get down to the next extension */
ext = "";
extBuf = strdup(pathName);
if (extBuf != NULL) {
char* localExt;
localExt = (char*) FindExtension(extBuf, fssep);
if (localExt != NULL)
*localExt = '\0';
localExt = (char*) FindExtension(extBuf, fssep);
if (localExt != NULL) {
ext = localExt;
assert(*ext == '.');
ext++;
}
}
LOGI(" DI after gz, ext='%s'", ext == NULL ? "(NULL)" : ext);
} else if (strcasecmp(ext, "zip") == 0) {
dierr = OuterZip::Test(fpWrapperGFD, fOuterLength);
if (dierr != kDIErrNone)
goto bail;
LOGI(" DI found ZIP outer wrapper");
fpOuterWrapper = new OuterZip();
if (fpOuterWrapper == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
fOuterFormat = kOuterFormatZip;
needExtFromOuter = true;
} else {
fOuterFormat = kOuterFormatNone;
}
/* finish up outer wrapper stuff */
if (fOuterFormat != kOuterFormatNone) {
GenericFD* pNewGFD = NULL;
dierr = fpOuterWrapper->Load(fpWrapperGFD, fOuterLength, fReadOnly,
&fWrappedLength, &pNewGFD);
if (dierr != kDIErrNone) {
LOGI(" DI outer prep failed");
/* extensions are "reliable", so failure is unavoidable */
goto bail;
}
/* Load() sets this */
if (fpOuterWrapper->IsDamaged()) {
AddNote(kNoteWarning, "The zip/gzip wrapper appears to be damaged.");
fReadOnly = true;
}
/* shift GFDs */
fpOuterGFD = fpWrapperGFD;
fpWrapperGFD = pNewGFD;
if (needExtFromOuter) {
ext = fpOuterWrapper->GetExtension();
if (ext == NULL)
ext = "";
}
}
/*
* Try to figure out what format the file is in.
*
* First pass, try only what the filename says it is. This way, if
* two file formats look alike, we have a good chance of getting it
* right.
*
* The "Test" functions have the complete file at their disposal. The
* file's length is stored in "fWrappedLength" for convenience.
*/
reliableExt = false;
probableFormat = kFileFormatUnknown;
if (strcasecmp(ext, "2mg") == 0 || strcasecmp(ext, "2img") == 0) {
reliableExt = true;
if (Wrapper2MG::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
probableFormat = kFileFormat2MG;
} else if (strcasecmp(ext, "shk") == 0 || strcasecmp(ext, "sdk") == 0 ||
strcasecmp(ext, "bxy") == 0)
{
DIError dierr2;
reliableExt = true;
dierr2 = WrapperNuFX::Test(fpWrapperGFD, fWrappedLength);
if (dierr2 == kDIErrNone)
probableFormat = kFileFormatNuFX;
else if (dierr2 == kDIErrFileArchive) {
LOGI(" AnalyzeImageFile thinks it found a NuFX file archive");
dierr = dierr2;
goto bail;
}
} else if (strcasecmp(ext, "hdv") == 0) {
/* usually just a "raw" disk, but check for Sim //e */
if (WrapperSim2eHDV::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
probableFormat = kFileFormatSim2eHDV;
/* ProDOS .hdv volumes can expand */
fExpandable = true;
} else if (strcasecmp(ext, "dsk") == 0 || strcasecmp(ext, "dc") == 0) {
/* might be DiskCopy */
if (WrapperDiskCopy42::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
probableFormat = kFileFormatDiskCopy42;
} else if (strcasecmp(ext, "ddd") == 0) {
/* do this after compressed formats but before unadorned */
reliableExt = true;
if (WrapperDDD::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
probableFormat = kFileFormatDDD;
} else if (strcasecmp(ext, "app") == 0) {
reliableExt = true;
if (WrapperTrackStar::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
probableFormat = kFileFormatTrackStar;
} else if (strcasecmp(ext, "fdi") == 0) {
reliableExt = true;
if (WrapperFDI::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
probableFormat = kFileFormatFDI;
} else if (strcasecmp(ext, "img") == 0) {
if (WrapperUnadornedSector::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
{
probableFormat = kFileFormatUnadorned;
fPhysical = kPhysicalFormatSectors;
fOrder = kSectorOrderPhysical;
}
} else if (strcasecmp(ext, "nib") == 0 || strcasecmp(ext, "raw") == 0) {
if (WrapperUnadornedNibble::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
{
probableFormat = kFileFormatUnadorned;
fPhysical = kPhysicalFormatNib525_6656;
/* figure out NibbleFormat later */
}
} else if (strcasecmp(ext, "do") == 0 || strcasecmp(ext, "po") == 0 ||
strcasecmp(ext, "d13") == 0 || strcasecmp(ext, "dc6") == 0)
{
if (WrapperUnadornedSector::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
{
probableFormat = kFileFormatUnadorned;
fPhysical = kPhysicalFormatSectors;
if (strcasecmp(ext, "do") == 0 || strcasecmp(ext, "d13") == 0)
fOrder = kSectorOrderDOS;
else
fOrder = kSectorOrderProDOS; // po, dc6
LOGI(" DI guessing order is %d by extension", fOrder);
}
} else if (strcasecmp(ext, "cp-win-vol") == 0) {
/* this is a Windows logical volume */
reliableExt = true;
probableFormat = kFileFormatUnadorned;
fPhysical = kPhysicalFormatSectors;
fOrder = kSectorOrderProDOS;
} else {
/* no match on the filename extension; start guessing */
}
if (probableFormat != kFileFormatUnknown) {
/*
* Found a match. Use "probableFormat" to open the file.
*/
LOGI(" DI scored hit on extension '%s'", ext);
} else {
/*
* Didn't work. If the file extension was marked "reliable", then
* either we have the wrong extension on the file, or the contents
* are damaged.
*
* If the extension isn't reliable, or simply absent, then we have
* to probe through the formats we know and just hope for the best.
*
* If the "test" function returns with a checksum failure, we take
* it to mean that the format was positively identified, but the
* data inside is corrupted. This results in an immediate return
* with the checksum failure noted. Only a few wrapper formats
* have checksums embedded. (The "test" functions should only
* be looking at header checksums.)
*/
if (reliableExt) {
LOGI(" DI file extension '%s' did not match contents", ext);
dierr = kDIErrBadFileFormat;
goto bail;
} else {
LOGI(" DI extension '%s' not useful, probing formats", ext);
dierr = WrapperNuFX::Test(fpWrapperGFD, fWrappedLength);
if (dierr == kDIErrNone) {
probableFormat = kFileFormatNuFX;
goto gotit;
} else if (dierr == kDIErrFileArchive)
goto bail; // we know it's NuFX, we know we can't use it
else if (dierr == kDIErrBadChecksum)
goto bail; // right file type, bad data
dierr = WrapperDiskCopy42::Test(fpWrapperGFD, fWrappedLength);
if (dierr == kDIErrNone) {
probableFormat = kFileFormatDiskCopy42;
goto gotit;
} else if (dierr == kDIErrBadChecksum)
goto bail; // right file type, bad data
if (Wrapper2MG::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) {
probableFormat = kFileFormat2MG;
} else if (WrapperDDD::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) {
probableFormat = kFileFormatDDD;
} else if (WrapperSim2eHDV::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
{
probableFormat = kFileFormatSim2eHDV;
} else if (WrapperTrackStar::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
{
probableFormat = kFileFormatTrackStar;
} else if (WrapperFDI::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone)
{
probableFormat = kFileFormatFDI;
} else if (WrapperUnadornedNibble::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) {
probableFormat = kFileFormatUnadorned;
fPhysical = kPhysicalFormatNib525_6656; // placeholder
} else if (WrapperUnadornedSector::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) {
probableFormat = kFileFormatUnadorned;
fPhysical = kPhysicalFormatSectors;
}
gotit: ;
}
}
/*
* Either we recognize it or we don't. Finish opening the file by
* setting up "fLength" and "fPhysical" values, extracting data
* into a memory buffer if necessary. fpDataGFD is set up by the
* "prep" function.
*
* If we're lucky, this will also configure "fOrder" for us, which is
* important when we can't recognize the filesystem format (for correct
* operation of disk tools).
*/
switch (probableFormat) {
case kFileFormat2MG:
fpImageWrapper = new Wrapper2MG();
break;
case kFileFormatDiskCopy42:
fpImageWrapper = new WrapperDiskCopy42();
break;
case kFileFormatSim2eHDV:
fpImageWrapper = new WrapperSim2eHDV();
break;
case kFileFormatTrackStar:
fpImageWrapper = new WrapperTrackStar();
break;
case kFileFormatFDI:
fpImageWrapper = new WrapperFDI();
fReadOnly = true; // writing to FDI not yet supported
break;
case kFileFormatNuFX:
fpImageWrapper = new WrapperNuFX();
((WrapperNuFX*)fpImageWrapper)->SetCompressType(
(NuThreadFormat) fNuFXCompressType);
break;
case kFileFormatDDD:
fpImageWrapper = new WrapperDDD();
break;
case kFileFormatUnadorned:
if (IsSectorFormat(fPhysical))
fpImageWrapper = new WrapperUnadornedSector();
else if (IsNibbleFormat(fPhysical))
fpImageWrapper = new WrapperUnadornedNibble();
else {
assert(false);
}
break;
default:
LOGI(" DI couldn't figure out the file format");
dierr = kDIErrUnrecognizedFileFmt;
break;
}
if (fpImageWrapper != NULL) {
assert(fpDataGFD == NULL);
dierr = fpImageWrapper->Prep(fpWrapperGFD, fWrappedLength, fReadOnly,
&fLength, &fPhysical, &fOrder, &fDOSVolumeNum,
&fpBadBlockMap, &fpDataGFD);
} else {
/* could be a mem alloc failure that didn't set dierr */
if (dierr == kDIErrNone)
dierr = kDIErrGeneric;
}
if (dierr != kDIErrNone) {
LOGI(" DI wrapper prep failed (err=%d)", dierr);
goto bail;
}
/* check for non-fatal checksum failures, e.g. DiskCopy42 */
if (fpImageWrapper->IsDamaged()) {
AddNote(kNoteWarning, "File checksum didn't match.");
fReadOnly = true;
}
fFileFormat = probableFormat;
assert(fLength >= 0);
assert(fpDataGFD != NULL);
assert(fOuterFormat != kOuterFormatUnknown);
assert(fFileFormat != kFileFormatUnknown);
assert(fPhysical != kPhysicalFormatUnknown);
bail:
free(extBuf);
return dierr;
}
/*
* Try to figure out what we're looking at.
*
* Returns an error if we don't think this is even a disk image. If we
* just can't figure it out, we return success but with the format value
* set to "unknown". This gives the caller a chance to use "override"
* to help us find our way.
*
* On entry:
* fpDataGFD, fLength, and fFileFormat are defined
* fSectorPairing is specified
* fOrder has a semi-reliable guess at sector ordering
* On exit:
* fOrder and fFormat are set to the best of our ability
* fNumTracks, fNumSectPerTrack, and fNumBlocks are set
* fHasSectors, fHasTracks, and fHasNibbles are set
* fFileSysOrder is set
* fpNibbleDescr will be set for nibble images
*/
DIError
DiskImg::AnalyzeImage(void)
{
assert(fLength >= 0);
assert(fpDataGFD != NULL);
assert(fFileFormat != kFileFormatUnknown);
assert(fPhysical != kPhysicalFormatUnknown);
assert(fFormat == kFormatUnknown);
assert(fFileSysOrder == kSectorOrderUnknown);
assert(fNumTracks == -1);
assert(fNumSectPerTrack == -1);
assert(fNumBlocks == -1);
if (fpDataGFD == NULL)
return kDIErrInternal;
/*
* Figure out how many tracks and sectors the image has.
*
* For an odd-sized ProDOS image, there will be no tracks and sectors.
*/
if (IsSectorFormat(fPhysical)) {
if (!fLength) {
LOGI(" DI zero-length disk images not allowed");
return kDIErrOddLength;
}
if (fLength == kD13Length) {
/* 13-sector .d13 image */
fHasSectors = true;
fNumSectPerTrack = 13;
fNumTracks = kTrackCount525;
assert(!fHasBlocks);
} else if (fLength % (16 * kSectorSize) == 0) {
/* looks like a collection of 16-sector tracks */
fHasSectors = true;
fNumSectPerTrack = 16;
fNumTracks = (int) (fLength / (fNumSectPerTrack * kSectorSize));
/* sector pairing effectively cuts #of tracks in half */
if (fSectorPairing) {
if ((fNumTracks & 0x01) != 0) {
LOGI(" DI error: bad attempt at sector pairing");
assert(false);
fSectorPairing = false;
}
}
if (fSectorPairing)
fNumTracks /= 2;
} else {
if (fSectorPairing) {
LOGI("GLITCH: sector pairing enabled, but fLength=%ld",
(long) fLength);
return kDIErrOddLength;
}
assert(fNumTracks == -1);
assert(fNumSectPerTrack == -1);
assert((fLength % kBlockSize) == 0);
fHasBlocks = true;
fNumBlocks = (long) (fLength / kBlockSize);
}
} else if (IsNibbleFormat(fPhysical)) {
fHasNibbles = fHasSectors = true;
/*
* Figure out if it's 13-sector or 16-sector (or garbage). We
* have to make an assessment of the entire disk so we can declare
* it to be 13-sector or 16-sector, which is useful for DiskFS
* which will want to scan for DOS VTOCs and other goodies. We
* also want to provide a default NibbleDescr.
*
* Failing that, we still allow it to be opened for raw track access.
*
* This also sets fNumTracks, which could be more than 35 if we're
* working with a TrackStar or FDI image.
*/
DIError dierr;
dierr = AnalyzeNibbleData(); // sets nibbleDescr and DOS vol num
if (dierr == kDIErrNone) {
assert(fpNibbleDescr != NULL);
fNumSectPerTrack = fpNibbleDescr->numSectors;
fOrder = kSectorOrderPhysical;
if (!fReadOnly && !fpNibbleDescr->dataVerifyChecksum) {
LOGI("DI nibbleDescr does not verify data checksum, disabling writes");
AddNote(kNoteInfo,
"Sectors use non-standard data checksums; writing disabled.");
fReadOnly = true;
}
} else {
//assert(fpNibbleDescr == NULL);
fNumSectPerTrack = -1;
fOrder = kSectorOrderPhysical;
fHasSectors = false;
}
} else {
LOGI("Unsupported physical %d", fPhysical);
assert(false);
return kDIErrGeneric;
}
/*
* Compute the number of blocks. For a 13-sector disk, block access
* is not possible.
*
* For nibble formats, we have to base the block count on the number
* of sectors rather than the file length.
*/
if (fHasSectors) {
assert(fNumSectPerTrack > 0);
if ((fNumSectPerTrack & 0x01) == 0) {
/* not a 13-sector disk, so define blocks in terms of sectors */
/* (effects of sector pairing are already taken into account) */
fHasBlocks = true;
fNumBlocks = (fNumTracks * fNumSectPerTrack) / 2;
}
} else if (fHasBlocks) {
if ((fLength % kBlockSize) == 0) {
/* not sector-oriented, so define blocks based on length */
fHasBlocks = true;
fNumBlocks = (long) (fLength / kBlockSize);
if (fSectorPairing) {
if ((fNumBlocks & 0x01) != 0) {
LOGI(" DI error: bad attempt at sector pairing (blk)");
assert(false);
fSectorPairing = false;
} else
fNumBlocks /= 2;
}
} else {
assert(false);
return kDIErrGeneric;
}
} else if (fHasNibbles) {
assert(fNumBlocks == -1);
} else {
LOGI(" DI none of fHasSectors/fHasBlocks/fHasNibbles are set");
assert(false);
return kDIErrInternal;
}
/*
* We've got the track/sector/block layout sorted out; now figure out
* what kind of filesystem we're dealing with.
*/
AnalyzeImageFS();
LOGI(" DI AnalyzeImage tracks=%ld sectors=%d blocks=%ld fileSysOrder=%d",
fNumTracks, fNumSectPerTrack, fNumBlocks, fFileSysOrder);
LOGI(" hasBlocks=%d hasSectors=%d hasNibbles=%d",
fHasBlocks, fHasSectors, fHasNibbles);
return kDIErrNone;
}
/*
* Try to figure out what filesystem exists on this disk image.
*
* We want to test for DOS before ProDOS, because sometimes they overlap (e.g.
* 800K ProDOS disk with five 160K DOS volumes on it).
*
* Sets fFormat, fOrder, and fFileSysOrder.
*/
void
DiskImg::AnalyzeImageFS(void)
{
/*
* In some circumstances it would be useful to have a set describing
* what filesystems we might expect to find, e.g. we're not likely to
* encounter RDOS embedded in a CF card.
*/
if (DiskFSMacPart::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatMacPart);
LOGI(" DI found MacPart, order=%d", fOrder);
} else if (DiskFSMicroDrive::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatMicroDrive);
LOGI(" DI found MicroDrive, order=%d", fOrder);
} else if (DiskFSFocusDrive::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatFocusDrive);
LOGI(" DI found FocusDrive, order=%d", fOrder);
} else if (DiskFSCFFA::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
// The CFFA format doesn't have a partition map, but we do insist
// on finding multiple volumes. It needs to come after MicroDrive,
// because a disk formatted for CFFA then subsequently partitioned
// for MicroDrive will still look like valid CFFA unless you zero
// out the blocks.
assert(fFormat == kFormatCFFA4 || fFormat == kFormatCFFA8);
LOGI(" DI found CFFA, order=%d", fOrder);
} else if (DiskFSFAT::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
// This is really just a trap to catch CFFA cards that were formatted
// for ProDOS and then re-formatted for MSDOS. As such it needs to
// come before the ProDOS test. It only works on larger volumes,
// and can be overridden, so it's pretty safe.
assert(fFormat == kFormatMSDOS);
LOGI(" DI found MSDOS, order=%d", fOrder);
} else if (DiskFSDOS33::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatDOS32 || fFormat == kFormatDOS33);
LOGI(" DI found DOS3.x, order=%d", fOrder);
if (fNumSectPerTrack == 13)
fFormat = kFormatDOS32;
} else if (DiskFSUNIDOS::TestWideFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
// Should only succeed on 400K embedded chunks.
assert(fFormat == kFormatDOS33);
fNumSectPerTrack = 32;
fNumTracks /= 2;
LOGI(" DI found 'wide' DOS3.3, order=%d", fOrder);
} else if (DiskFSUNIDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatUNIDOS);
fNumSectPerTrack = 32;
fNumTracks /= 2;
LOGI(" DI found UNIDOS, order=%d", fOrder);
} else if (DiskFSOzDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatOzDOS);
fNumSectPerTrack = 32;
fNumTracks /= 2;
LOGI(" DI found OzDOS, order=%d", fOrder);
} else if (DiskFSProDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatProDOS);
LOGI(" DI found ProDOS, order=%d", fOrder);
} else if (DiskFSPascal::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatPascal);
LOGI(" DI found Pascal, order=%d", fOrder);
} else if (DiskFSCPM::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatCPM);
LOGI(" DI found CP/M, order=%d", fOrder);
} else if (DiskFSRDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatRDOS33 ||
fFormat == kFormatRDOS32 ||
fFormat == kFormatRDOS3);
LOGI(" DI found RDOS 3.3, order=%d", fOrder);
} else if (DiskFSHFS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatMacHFS);
LOGI(" DI found HFS, order=%d", fOrder);
} else if (DiskFSGutenberg::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone)
{
assert(fFormat == kFormatGutenberg);
LOGI(" DI found Gutenberg, order=%d", fOrder);
} else {
fFormat = kFormatUnknown;
LOGI(" DI no recognizeable filesystem found (fOrder=%d)",
fOrder);
}
fFileSysOrder = CalcFSSectorOrder();
}
/*
* Override the format determined by the analyzer.
*
* If they insist on the presence of a valid filesystem, check to make sure
* that filesystem actually exists.
*
* Note that this does not allow overriding the file structure, which must
* be clearly identifiable to be at all useful. If the file has no "wrapper"
* structure, the "unadorned" format should be specified, and the contents
* identified by the PhysicalFormat.
*/
DIError
DiskImg::OverrideFormat(PhysicalFormat physical, FSFormat format,
SectorOrder order)
{
DIError dierr = kDIErrNone;
SectorOrder newOrder;
FSFormat newFormat;
LOGI(" DI override: physical=%d format=%d order=%d",
physical, format, order);
if (!IsSectorFormat(physical) && !IsNibbleFormat(physical))
return kDIErrUnsupportedPhysicalFmt;
/* don't allow forcing physical format change */
if (physical != fPhysical)
return kDIErrInvalidArg;
/* optimization */
if (physical == fPhysical && format == fFormat && order == fOrder) {
LOGI(" DI override matches existing, ignoring");
return kDIErrNone;
}
newOrder = order;
newFormat = format;
switch (format) {
case kFormatDOS33:
case kFormatDOS32:
dierr = DiskFSDOS33::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
// Go ahead and allow the override even if the DOS version is wrong.
// So long as the sector count is correct, it's okay.
break;
case kFormatProDOS:
dierr = DiskFSProDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatPascal:
dierr = DiskFSPascal::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatMacHFS:
dierr = DiskFSHFS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatUNIDOS:
dierr = DiskFSUNIDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatOzDOS:
dierr = DiskFSOzDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatCFFA4:
case kFormatCFFA8:
dierr = DiskFSCFFA::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
// So long as it's CFFA, we allow the user to force it to be 4-mode
// or 8-mode. Don't require newFormat==format.
break;
case kFormatMacPart:
dierr = DiskFSMacPart::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatMicroDrive:
dierr = DiskFSMicroDrive::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatFocusDrive:
dierr = DiskFSFocusDrive::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatCPM:
dierr = DiskFSCPM::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatMSDOS:
dierr = DiskFSFAT::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
break;
case kFormatRDOS33:
case kFormatRDOS32:
case kFormatRDOS3:
dierr = DiskFSRDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery);
if (newFormat != format)
dierr = kDIErrFilesystemNotFound; // found RDOS, but wrong flavor
break;
case kFormatGenericPhysicalOrd:
case kFormatGenericProDOSOrd:
case kFormatGenericDOSOrd:
case kFormatGenericCPMOrd:
/* no discussion possible, since there's no FS to validate */
newFormat = format;
newOrder = order;
break;
case kFormatUnknown:
/* only valid in rare situations, e.g. CFFA CreatePlaceholder */
newFormat = format;
newOrder = order;
break;
default:
dierr = kDIErrUnsupportedFSFmt;
break;
}
if (dierr != kDIErrNone) {
LOGI(" DI override failed");
goto bail;
}
/*
* We passed in "order" to TestFS. If it came back with something
* different, it means that it didn't like the new order value even
* when "leniency" was granted.
*/
if (newOrder != order) {
dierr = kDIErrBadOrdering;
goto bail;
}
fFormat = format;
fOrder = newOrder;
fFileSysOrder = CalcFSSectorOrder();
LOGI(" DI override accepted");
bail:
return dierr;
}
/*
* Figure out the sector ordering for this filesystem, so we can decide
* how the sectors need to be re-arranged when we're reading them.
*
* If the value returned by this function matches fOrder, then no swapping
* will be done.
*
* NOTE: this table is redundant with some knowledge embedded in the
* individual "TestFS" functions.
*/
DiskImg::SectorOrder
DiskImg::CalcFSSectorOrder(void) const
{
/* in the absence of information, just leave it alone */
if (fFormat == kFormatUnknown || fOrder == kSectorOrderUnknown) {
LOGI(" DI WARNING: FindSectorOrder but format not known");
return fOrder;
}
assert(fOrder == kSectorOrderPhysical || fOrder == kSectorOrderCPM ||
fOrder == kSectorOrderProDOS || fOrder == kSectorOrderDOS);
switch (fFormat) {
case kFormatGenericPhysicalOrd:
case kFormatRDOS32:
case kFormatRDOS3:
return kSectorOrderPhysical;
case kFormatGenericDOSOrd:
case kFormatDOS33:
case kFormatDOS32:
case kFormatUNIDOS:
case kFormatOzDOS:
case kFormatGutenberg:
return kSectorOrderDOS;
case kFormatGenericCPMOrd:
case kFormatCPM:
return kSectorOrderCPM;
case kFormatGenericProDOSOrd:
case kFormatProDOS:
case kFormatRDOS33:
case kFormatPascal:
case kFormatMacHFS:
case kFormatMacMFS:
case kFormatLisa:
case kFormatMSDOS:
case kFormatISO9660:
case kFormatCFFA4:
case kFormatCFFA8:
case kFormatMacPart:
case kFormatMicroDrive:
case kFormatFocusDrive:
return kSectorOrderProDOS;
default:
assert(false);
return fOrder;
}
}
/*
* Based on the disk format, figure out if we should prefer blocks or
* sectors when examining disk contents.
*/
bool
DiskImg::ShowAsBlocks(void) const
{
if (!fHasBlocks)
return false;
/* in the absence of information, assume sectors */
if (fFormat == kFormatUnknown) {
if (fOrder == kSectorOrderProDOS)
return true;
else
return false;
}
switch (fFormat) {
case kFormatGenericPhysicalOrd:
case kFormatGenericDOSOrd:
case kFormatDOS33:
case kFormatDOS32:
case kFormatRDOS3:
case kFormatRDOS33:
case kFormatUNIDOS:
case kFormatOzDOS:
case kFormatGutenberg:
return false;
case kFormatGenericProDOSOrd:
case kFormatGenericCPMOrd:
case kFormatProDOS:
case kFormatPascal:
case kFormatMacHFS:
case kFormatMacMFS:
case kFormatLisa:
case kFormatCPM:
case kFormatMSDOS:
case kFormatISO9660:
case kFormatCFFA4:
case kFormatCFFA8:
case kFormatMacPart:
case kFormatMicroDrive:
case kFormatFocusDrive:
return true;
default:
assert(false);
return false;
}
}
/*
* Format an image with the requested fileystem format. This only works if
* the matching DiskFS supports formatting of disks.
*/
DIError
DiskImg::FormatImage(FSFormat format, const char* volName)
{
DIError dierr = kDIErrNone;
DiskFS* pDiskFS = NULL;
FSFormat savedFormat;
LOGI(" DI FormatImage '%s'", volName);
/*
* Open a temporary DiskFS for the requested format. We do this via the
* standard OpenAppropriate call, so we temporarily switch our format
* out. (We will eventually replace it, but we want to make sure that
* local error handling works correctly, so we restore it for now.)
*/
savedFormat = fFormat;
fFormat = format;
pDiskFS = OpenAppropriateDiskFS(false);
fFormat = savedFormat;
if (pDiskFS == NULL) {
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
dierr = pDiskFS->Format(this, volName);
if (dierr != kDIErrNone)
goto bail;
LOGI("DI format successful");
fFormat = format;
bail:
delete pDiskFS;
return dierr;
}
/*
* Clear an image to zeros, usually done as a prelude to a higher-level format.
*
* BUG: this should also handle the track/sector case.
*
* HEY: this is awfully slow on large disks... should have some sort of
* optimized path that just writes to the GFD or something. Maybe even just
* a "ZeroBlock" instead of "WriteBlock" so we can memset instead of memcpy?
*/
DIError
DiskImg::ZeroImage(void)
{
DIError dierr = kDIErrNone;
unsigned char blkBuf[kBlockSize];
long block;
LOGI(" DI ZeroImage (%ld blocks)", GetNumBlocks());
memset(blkBuf, 0, sizeof(blkBuf));
for (block = 0; block < GetNumBlocks(); block++) {
dierr = WriteBlock(block, blkBuf);
if (dierr != kDIErrNone)
break;
}
return dierr;
}
/*
* Set the "scan progress" function.
*
* We want to use the same function for our sub-volumes too.
*/
void
DiskImg::SetScanProgressCallback(ScanProgressCallback func, void* cookie)
{
if (fpParentImg != NULL) {
/* unexpected, but perfectly okay */
DebugBreak();
}
fpScanProgressCallback = func;
fScanProgressCookie = cookie;
fScanCount = 0;
fScanMsg[0] = '\0';
fScanLastMsgWhen = time(NULL);
}
/*
* Update the progress. Call with a string at the start of a volume, then
* call with a NULL pointer every time we add a file.
*/
bool
DiskImg::UpdateScanProgress(const char* newStr)
{
ScanProgressCallback func = fpScanProgressCallback;
DiskImg* pImg = this;
bool result = true;
/* search up the tree to find a progress updater */
while (func == NULL) {
pImg = pImg->fpParentImg;
if (pImg == NULL)
return result; // none defined, bail out
func = pImg->fpScanProgressCallback;
}
time_t now = time(NULL);
if (newStr == NULL) {
fScanCount++;
//if ((fScanCount % 100) == 0)
if (fScanLastMsgWhen != now) {
result = (*func)(fScanProgressCookie,
fScanMsg, fScanCount);
fScanLastMsgWhen = now;
}
} else {
fScanCount = 0;
strncpy(fScanMsg, newStr, sizeof(fScanMsg));
fScanMsg[sizeof(fScanMsg)-1] = '\0';
result = (*func)(fScanProgressCookie, fScanMsg,
fScanCount);
fScanLastMsgWhen = now;
}
return result;
}
/*
* ==========================================================================
* Block/track/sector I/O
* ==========================================================================
*/
/*
* Handle sector order conversions.
*/
DIError
DiskImg::CalcSectorAndOffset(long track, int sector, SectorOrder imageOrder,
SectorOrder fsOrder, di_off_t* pOffset, int* pNewSector)
{
if (!fHasSectors)
return kDIErrUnsupportedAccess;
/*
* Sector order conversions. No table is needed for Copy ][+ format,
* which is equivalent to "physical".
*/
static const int raw2dos[16] = {
0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15
};
static const int dos2raw[16] = {
0, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 15
};
static const int raw2prodos[16] = {
0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15
};
static const int prodos2raw[16] = {
0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15
};
static const int raw2cpm[16] = {
0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5
};
static const int cpm2raw[16] = {
0, 3, 6, 9, 12, 15, 2, 5, 8, 11, 14, 1, 4, 7, 10, 13
};
if (track < 0 || track >= fNumTracks) {
LOGI(" DI read invalid track %ld", track);
return kDIErrInvalidTrack;
}
if (sector < 0 || sector >= fNumSectPerTrack) {
LOGI(" DI read invalid sector %d", sector);
return kDIErrInvalidSector;
}
di_off_t offset;
int newSector = -1;
/*
* 16-sector disks write sectors in ascending order and then remap
* them with a translation table.
*/
if (fNumSectPerTrack == 16 || fNumSectPerTrack == 32) {
if (fSectorPairing) {
assert(fSectorPairOffset == 0 || fSectorPairOffset == 1);
// this pushes "track" beyond fNumTracks
track *= 2;
if (sector >= 16) {
track++;
sector -= 16;
}
offset = track * fNumSectPerTrack * kSectorSize;
sector = sector * 2 + fSectorPairOffset;
if (sector >= 16) {
offset += 16*kSectorSize;
sector -= 16;
}
} else {
offset = track * fNumSectPerTrack * kSectorSize;
if (sector >= 16) {
offset += 16*kSectorSize;
sector -= 16;
}
}
assert(sector >= 0 && sector < 16);
/* convert request to "raw" sector number */
switch (fsOrder) {
case kSectorOrderProDOS:
newSector = prodos2raw[sector];
break;
case kSectorOrderDOS:
newSector = dos2raw[sector];
break;
case kSectorOrderCPM:
newSector = cpm2raw[sector];
break;
case kSectorOrderPhysical: // used for Copy ][+
newSector = sector;
break;
case kSectorOrderUnknown:
// should never happen; fall through to "default"
default:
assert(false);
newSector = sector;
break;
}
/* convert "raw" request to the image's ordering */
switch (imageOrder) {
case kSectorOrderProDOS:
newSector = raw2prodos[newSector];
break;
case kSectorOrderDOS:
newSector = raw2dos[newSector];
break;
case kSectorOrderCPM:
newSector = raw2cpm[newSector];
break;
case kSectorOrderPhysical:
//newSector = newSector;
break;
case kSectorOrderUnknown:
// should never happen; fall through to "default"
default:
assert(false);
//newSector = newSector;
break;
}
if (imageOrder == fsOrder) {
assert(sector == newSector);
}
offset += newSector * kSectorSize;
} else if (fNumSectPerTrack == 13) {
/* sector skew has no meaning, so assume no translation */
offset = track * fNumSectPerTrack * kSectorSize;
newSector = sector;
offset += newSector * kSectorSize;
if (imageOrder != fsOrder) {
/* translation expected */
LOGI("NOTE: CalcSectorAndOffset for nspt=13 with img=%d fs=%d",
imageOrder, fsOrder);
}
} else {
assert(false); // should not be here
/* try to do something reasonable */
assert(imageOrder == fsOrder);
offset = (di_off_t)track * fNumSectPerTrack * kSectorSize;
offset += sector * kSectorSize;
}
*pOffset = offset;
*pNewSector = newSector;
return kDIErrNone;
}
/*
* Determine whether an image uses a linear mapping. This allows us to
* optimize block reads & writes, very useful when dealing with logical
* volumes under Windows (which also use 512-byte blocks).
*
* The "imageOrder" argument usually comes from fOrder, and "fsOrder"
* comes from "fFileSysOrder".
*/
inline bool
DiskImg::IsLinearBlocks(SectorOrder imageOrder, SectorOrder fsOrder)
{
/*
* Any time fOrder==fFileSysOrder, we know that we have a linear
* mapping. This holds true for reading ProDOS blocks from a ".po"
* file or reading DOS sectors from a ".do" file.
*/
return (IsSectorFormat(fPhysical) && fHasBlocks &&
imageOrder == fsOrder);
}
/*
* Read the specified track and sector, adjusting for sector ordering as
* appropriate.
*
* Copies 256 bytes into "*buf".
*
* Returns 0 on success, nonzero on failure.
*/
DIError
DiskImg::ReadTrackSectorSwapped(long track, int sector, void* buf,
SectorOrder imageOrder, SectorOrder fsOrder)
{
DIError dierr;
di_off_t offset;
int newSector = -1;
if (buf == NULL)
return kDIErrInvalidArg;
#if 0 // Pre-d13
if (fNumSectPerTrack == 13) {
/* no sector skewing possible for 13-sector disks */
assert(fHasNibbles);
return ReadNibbleSector(track, sector, buf, fpNibbleDescr);
}
#endif
dierr = CalcSectorAndOffset(track, sector, imageOrder, fsOrder,
&offset, &newSector);
if (dierr != kDIErrNone)
return dierr;
if (IsSectorFormat(fPhysical)) {
assert(offset+kSectorSize <= fLength);
//LOGI(" DI t=%d s=%d", track,
// (offset - track * fNumSectPerTrack * kSectorSize) / kSectorSize);
dierr = CopyBytesOut(buf, offset, kSectorSize);
} else if (IsNibbleFormat(fPhysical)) {
if (imageOrder != kSectorOrderPhysical) {
LOGI(" NOTE: nibble imageOrder is %d (expected %d)",
imageOrder, kSectorOrderPhysical);
}
dierr = ReadNibbleSector(track, newSector, buf, fpNibbleDescr);
} else {
assert(false);
dierr = kDIErrInternal;
}
return dierr;
}
/*
* Write the specified track and sector, adjusting for sector ordering as
* appropriate.
*
* Copies 256 bytes out of "buf".
*
* Returns 0 on success, nonzero on failure.
*/
DIError
DiskImg::WriteTrackSector(long track, int sector, const void* buf)
{
DIError dierr;
di_off_t offset;
int newSector = -1;
if (buf == NULL)
return kDIErrInvalidArg;
if (fReadOnly)
return kDIErrAccessDenied;
#if 0 // Pre-d13
if (fNumSectPerTrack == 13) {
/* no sector skewing possible for 13-sector disks */
assert(fHasNibbles);
return WriteNibbleSector(track, sector, buf, fpNibbleDescr);
}
#endif
dierr = CalcSectorAndOffset(track, sector, fOrder, fFileSysOrder,
&offset, &newSector);
if (dierr != kDIErrNone)
return dierr;
if (IsSectorFormat(fPhysical)) {
assert(offset+kSectorSize <= fLength);
//LOGI(" DI t=%d s=%d", track,
// (offset - track * fNumSectPerTrack * kSectorSize) / kSectorSize);
dierr = CopyBytesIn(buf, offset, kSectorSize);
} else if (IsNibbleFormat(fPhysical)) {
if (fOrder != kSectorOrderPhysical) {
LOGI(" NOTE: nibble fOrder is %d (expected %d)",
fOrder, kSectorOrderPhysical);
}
dierr = WriteNibbleSector(track, newSector, buf, fpNibbleDescr);
} else {
assert(false);
dierr = kDIErrInternal;
}
return dierr;
}
/*
* Read a 512-byte block.
*
* Copies 512 bytes into "*buf".
*/
DIError
DiskImg::ReadBlockSwapped(long block, void* buf, SectorOrder imageOrder,
SectorOrder fsOrder)
{
if (!fHasBlocks)
return kDIErrUnsupportedAccess;
if (block < 0 || block >= fNumBlocks)
return kDIErrInvalidBlock;
if (buf == NULL)
return kDIErrInvalidArg;
DIError dierr;
long track, blkInTrk;
/* if we have a bad block map, check it */
if (CheckForBadBlocks(block, 1)) {
dierr = kDIErrReadFailed;
goto bail;
}
if (fHasSectors && !IsLinearBlocks(imageOrder, fsOrder)) {
/* run it through the t/s call so we handle DOS ordering */
track = block / (fNumSectPerTrack/2);
blkInTrk = block - (track * (fNumSectPerTrack/2));
dierr = ReadTrackSectorSwapped(track, blkInTrk*2, buf,
imageOrder, fsOrder);
if (dierr != kDIErrNone)
return dierr;
dierr = ReadTrackSectorSwapped(track, blkInTrk*2+1,
(char*)buf+kSectorSize, imageOrder, fsOrder);
} else if (fHasBlocks) {
/* no sectors, so no swapping; must be linear blocks */
if (imageOrder != fsOrder) {
LOGI(" DI NOTE: ReadBlockSwapped on non-sector (%d/%d)",
imageOrder, fsOrder);
}
dierr = CopyBytesOut(buf, (di_off_t) block * kBlockSize, kBlockSize);
} else {
assert(false);
dierr = kDIErrInternal;
}
bail:
return dierr;
}
/*
* Read multiple blocks.
*
* IMPORTANT: this returns immediately when a read fails. The buffer will
* probably not contain data from all readable sectors. The application is
* expected to retry the blocks individually.
*/
DIError
DiskImg::ReadBlocks(long startBlock, int numBlocks, void* buf)
{
DIError dierr = kDIErrNone;
assert(fHasBlocks);
assert(startBlock >= 0);
assert(numBlocks > 0);
assert(buf != NULL);
if (startBlock < 0 || numBlocks + startBlock > GetNumBlocks()) {
assert(false);
return kDIErrInvalidArg;
}
/* if we have a bad block map, check it */
if (CheckForBadBlocks(startBlock, numBlocks)) {
dierr = kDIErrReadFailed;
goto bail;
}
if (!IsLinearBlocks(fOrder, fFileSysOrder)) {
/*
* This isn't a collection of linear blocks, so we need to read it one
* block at a time with sector swapping. This almost certainly means
* that we're not reading from physical media, so performance shouldn't
* be an issue.
*/
if (startBlock == 0) {
LOGI(" ReadBlocks: nonlinear, not trying");
}
while (numBlocks--) {
dierr = ReadBlock(startBlock, buf);
if (dierr != kDIErrNone)
goto bail;
startBlock++;
buf = (unsigned char*)buf + kBlockSize;
}
} else {
if (startBlock == 0) {
LOGI(" ReadBlocks: doing big linear reads");
}
dierr = CopyBytesOut(buf,
(di_off_t) startBlock * kBlockSize, numBlocks * kBlockSize);
}
bail:
return dierr;
}
/*
* Check to see if any blocks in a range of blocks show up in the bad
* block map. This is primarily useful for 3.5" disk images converted
* from nibble images, because we convert them directly to "cooked"
* 512-byte blocks.
*
* Returns "true" if we found bad blocks, "false" if not.
*/
bool
DiskImg::CheckForBadBlocks(long startBlock, int numBlocks)
{
int i;
if (fpBadBlockMap == NULL)
return false;
for (i = startBlock; i < startBlock+numBlocks; i++) {
if (fpBadBlockMap->IsSet(i))
return true;
}
return false;
}
/*
* Write a block of data to a DiskImg.
*
* Returns immediately when a block write fails. Does not try to write all
* blocks before returning failure.
*/
DIError
DiskImg::WriteBlock(long block, const void* buf)
{
if (!fHasBlocks)
return kDIErrUnsupportedAccess;
if (block < 0 || block >= fNumBlocks)
return kDIErrInvalidBlock;
if (buf == NULL)
return kDIErrInvalidArg;
if (fReadOnly)
return kDIErrAccessDenied;
DIError dierr;
long track, blkInTrk;
if (fHasSectors && !IsLinearBlocks(fOrder, fFileSysOrder)) {
/* run it through the t/s call so we handle DOS ordering */
track = block / (fNumSectPerTrack/2);
blkInTrk = block - (track * (fNumSectPerTrack/2));
dierr = WriteTrackSector(track, blkInTrk*2, buf);
if (dierr != kDIErrNone)
return dierr;
dierr = WriteTrackSector(track, blkInTrk*2+1, (char*)buf+kSectorSize);
} else if (fHasBlocks) {
/* no sectors, so no swapping; must be linear blocks */
if (fOrder != fFileSysOrder) {
LOGI(" DI NOTE: WriteBlock on non-sector (%d/%d)",
fOrder, fFileSysOrder);
}
dierr = CopyBytesIn(buf, (di_off_t)block * kBlockSize, kBlockSize);
} else {
assert(false);
dierr = kDIErrInternal;
}
return dierr;
}
/*
* Write multiple blocks.
*/
DIError
DiskImg::WriteBlocks(long startBlock, int numBlocks, const void* buf)
{
DIError dierr = kDIErrNone;
assert(fHasBlocks);
assert(startBlock >= 0);
assert(numBlocks > 0);
assert(buf != NULL);
if (startBlock < 0 || numBlocks + startBlock > GetNumBlocks()) {
assert(false);
return kDIErrInvalidArg;
}
if (!IsLinearBlocks(fOrder, fFileSysOrder)) {
/*
* This isn't a collection of linear blocks, so we need to write it
* one block at a time with sector swapping. This almost certainly
* means that we're not reading from physical media, so performance
* shouldn't be an issue.
*/
if (startBlock == 0) {
LOGI(" WriteBlocks: nonlinear, not trying");
}
while (numBlocks--) {
dierr = WriteBlock(startBlock, buf);
if (dierr != kDIErrNone)
goto bail;
startBlock++;
buf = (unsigned char*)buf + kBlockSize;
}
} else {
if (startBlock == 0) {
LOGI(" WriteBlocks: doing big linear writes");
}
dierr = CopyBytesIn(buf,
(di_off_t) startBlock * kBlockSize, numBlocks * kBlockSize);
}
bail:
return dierr;
}
/*
* Copy a chunk of bytes out of the disk image.
*
* (This is the lowest-level read routine in this class.)
*/
DIError
DiskImg::CopyBytesOut(void* buf, di_off_t offset, int size) const
{
DIError dierr;
dierr = fpDataGFD->Seek(offset, kSeekSet);
if (dierr != kDIErrNone) {
LOGI(" DI seek off=%ld failed (err=%d)", (long) offset, dierr);
return dierr;
}
dierr = fpDataGFD->Read(buf, size);
if (dierr != kDIErrNone) {
LOGI(" DI read off=%ld size=%d failed (err=%d)",
(long) offset, size, dierr);
return dierr;
}
return kDIErrNone;
}
/*
* Copy a chunk of bytes into the disk image.
*
* Sets the "dirty" flag.
*
* (This is the lowest-level write routine in DiskImg.)
*/
DIError
DiskImg::CopyBytesIn(const void* buf, di_off_t offset, int size)
{
DIError dierr;
if (fReadOnly) {
DebugBreak();
return kDIErrAccessDenied;
}
assert(fpDataGFD != NULL); // somebody closed the image?
dierr = fpDataGFD->Seek(offset, kSeekSet);
if (dierr != kDIErrNone) {
LOGI(" DI seek off=%ld failed (err=%d)", (long) offset, dierr);
return dierr;
}
dierr = fpDataGFD->Write(buf, size);
if (dierr != kDIErrNone) {
LOGI(" DI write off=%ld size=%d failed (err=%d)",
(long) offset, size, dierr);
return dierr;
}
/* set the dirty flag here and everywhere above */
DiskImg* pImg = this;
while (pImg != NULL) {
pImg->fDirty = true;
pImg = pImg->fpParentImg;
}
return kDIErrNone;
}
/*
* ===========================================================================
* Image creation
* ===========================================================================
*/
/*
* Create a disk image with the specified parameters.
*
* "storageName" and "pNibbleDescr" may be NULL.
*/
DIError
DiskImg::CreateImage(const char* pathName, const char* storageName,
OuterFormat outerFormat, FileFormat fileFormat, PhysicalFormat physical,
const NibbleDescr* pNibbleDescr, SectorOrder order,
FSFormat format, long numBlocks, bool skipFormat)
{
assert(fpDataGFD == NULL); // should not be open already!
if (numBlocks <= 0) {
LOGI("ERROR: bad numBlocks %ld", numBlocks);
assert(false);
return kDIErrInvalidCreateReq;
}
fOuterFormat = outerFormat;
fFileFormat = fileFormat;
fPhysical = physical;
SetCustomNibbleDescr(pNibbleDescr);
fOrder = order;
fFormat = format;
fNumBlocks = numBlocks;
fHasBlocks = true;
return CreateImageCommon(pathName, storageName, skipFormat);
}
DIError
DiskImg::CreateImage(const char* pathName, const char* storageName,
OuterFormat outerFormat, FileFormat fileFormat, PhysicalFormat physical,
const NibbleDescr* pNibbleDescr, SectorOrder order,
FSFormat format, long numTracks, long numSectPerTrack, bool skipFormat)
{
assert(fpDataGFD == NULL); // should not be open already!
if (numTracks <= 0 || numSectPerTrack == 0) {
LOGI("ERROR: bad tracks/sectors %ld/%ld", numTracks, numSectPerTrack);
assert(false);
return kDIErrInvalidCreateReq;
}
fOuterFormat = outerFormat;
fFileFormat = fileFormat;
fPhysical = physical;
SetCustomNibbleDescr(pNibbleDescr);
fOrder = order;
fFormat = format;
fNumTracks = numTracks;
fNumSectPerTrack = numSectPerTrack;
fHasSectors = true;
if (numSectPerTrack < 0) {
/* nibble image with non-standard formatting */
if (!IsNibbleFormat(fPhysical)) {
LOGI("Whoa: expected nibble format here");
assert(false);
return kDIErrInvalidCreateReq;
}
LOGI("Sector image w/o sectors, switching to nibble mode");
fHasNibbles = true;
fHasSectors = false;
fpNibbleDescr = NULL;
}
return CreateImageCommon(pathName, storageName, skipFormat);
}
/*
* Do the actual disk image creation.
*/
DIError
DiskImg::CreateImageCommon(const char* pathName, const char* storageName,
bool skipFormat)
{
DIError dierr;
/*
* Step 1: figure out fHasBlocks/fHasSectors/fHasNibbles and any
* other misc fields.
*
* If the disk is a nibble image expected to have a particular
* volume number, it should have already been set by the application.
*/
if (fHasBlocks) {
if ((fNumBlocks % 8) == 0) {
fHasSectors = true;
fNumSectPerTrack = 16;
fNumTracks = fNumBlocks / 8;
} else {
LOGI("NOTE: sector access to new image not possible");
}
} else if (fHasSectors) {
if ((fNumSectPerTrack & 0x01) == 0) {
fHasBlocks = true;
fNumBlocks = (fNumTracks * fNumSectPerTrack) / 2;
} else {
LOGI("NOTE: block access to new image not possible");
}
}
if (fHasSectors && fPhysical != kPhysicalFormatSectors)
fHasNibbles = true;
assert(fHasBlocks || fHasSectors || fHasNibbles);
fFileSysOrder = CalcFSSectorOrder();
fReadOnly = false;
fDirty = true;
/*
* Step 2: check for invalid arguments and bad combinations.
*/
dierr = ValidateCreateFormat();
if (dierr != kDIErrNone) {
LOGI("ERROR: CIC arg validation failed, bailing");
goto bail;
}
/*
* Step 3: create the destination file. Put this into fpWrapperGFD
* or fpOuterGFD.
*
* The file must not already exist.
*
* THOUGHT: should allow creation of an in-memory disk image. This won't
* work for NuFX, but will work for pretty much everything else.
*/
LOGI(" CIC: creating '%s'", pathName);
int fd;
fd = open(pathName, O_CREAT | O_EXCL, 0644);
if (fd < 0) {
dierr = (DIError) errno;
LOGI("ERROR: unable to create file '%s' (errno=%d)",
pathName, dierr);
goto bail;
}
close(fd);
GFDFile* pGFDFile;
pGFDFile = new GFDFile;
dierr = pGFDFile->Open(pathName, false);
if (dierr != kDIErrNone) {
delete pGFDFile;
goto bail;
}
if (fOuterFormat == kOuterFormatNone)
fpWrapperGFD = pGFDFile;
else
fpOuterGFD = pGFDFile;
pGFDFile = NULL;
/*
* Step 4: if we have an outer GFD and therefore don't currently have
* an fpWrapperGFD, create an expandable memory buffer to use.
*
* We want to take a guess at how big the image will be, so compute
* fLength now.
*
* Create an OuterWrapper as needed.
*/
if (IsSectorFormat(fPhysical)) {
if (fHasBlocks)
fLength = (di_off_t) GetNumBlocks() * kBlockSize;
else
fLength = (di_off_t) GetNumTracks() * GetNumSectPerTrack() * kSectorSize;
} else {
assert(IsNibbleFormat(fPhysical));
fLength = GetNumTracks() * GetNibbleTrackAllocLength();
}
assert(fLength > 0);
if (fpWrapperGFD == NULL) {
/* shift GFDs and create a new memory GFD, pre-sized */
GFDBuffer* pGFDBuffer = new GFDBuffer;
/* use fLength as a starting point for buffer size; this may expand */
dierr = pGFDBuffer->Open(NULL, fLength, true, true, false);
if (dierr != kDIErrNone) {
delete pGFDBuffer;
goto bail;
}
fpWrapperGFD = pGFDBuffer;
pGFDBuffer = NULL;
}
/* create an fpOuterWrapper struct */
switch (fOuterFormat) {
case kOuterFormatNone:
break;
case kOuterFormatGzip:
fpOuterWrapper = new OuterGzip;
if (fpOuterWrapper == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
break;
case kOuterFormatZip:
fpOuterWrapper = new OuterZip;
if (fpOuterWrapper == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
break;
default:
assert(false);
dierr = kDIErrInternal;
goto bail;
}
/*
* Step 5: tell the ImageWrapper to write itself into the GFD, passing
* in the blank memory buffer.
*
* - Unadorned formats copy from memory buffer to fpWrapperGFD on disk.
* (With gz, fpWrapperGFD is actually a memory buffer.) fpDataGFD
* becomes an offset into the file.
* - 2MG writes header into GFD and follows it with all data; DC42
* and Sim2e do similar things.
* - NuFX reopens pathName as SHK file (fpWrapperGFD must point to a
* file) and accesses the archive through an fpArchive. fpDataGFD
* is created as a memory buffer and the blank image is copied in.
* - DDD leaves fpWrapperGFD alone and copies the blank image into a
* new buffer for fpDataGFD.
*
* Sets fWrappedLength when possible, determined from fPhysical and
* either fNumBlocks or fNumTracks. Creates fpDataGFD, often as a
* GFDGFD offset into fpWrapperGFD.
*/
switch (fFileFormat) {
case kFileFormat2MG:
fpImageWrapper = new Wrapper2MG();
break;
case kFileFormatDiskCopy42:
fpImageWrapper = new WrapperDiskCopy42();
fpImageWrapper->SetStorageName(storageName);
break;
case kFileFormatSim2eHDV:
fpImageWrapper = new WrapperSim2eHDV();
break;
case kFileFormatTrackStar:
fpImageWrapper = new WrapperTrackStar();
fpImageWrapper->SetStorageName(storageName);
break;
case kFileFormatFDI:
fpImageWrapper = new WrapperFDI();
break;
case kFileFormatNuFX:
fpImageWrapper = new WrapperNuFX();
fpImageWrapper->SetStorageName(storageName);
((WrapperNuFX*)fpImageWrapper)->SetCompressType(
(NuThreadFormat) fNuFXCompressType);
break;
case kFileFormatDDD:
fpImageWrapper = new WrapperDDD();
break;
case kFileFormatUnadorned:
if (IsSectorFormat(fPhysical))
fpImageWrapper = new WrapperUnadornedSector();
else if (IsNibbleFormat(fPhysical))
fpImageWrapper = new WrapperUnadornedNibble();
else {
assert(false);
}
break;
default:
assert(fpImageWrapper == NULL);
break;
}
if (fpImageWrapper == NULL) {
LOGI(" DI couldn't figure out the file format");
dierr = kDIErrUnrecognizedFileFmt;
goto bail;
}
/* create the wrapper, write the header, and create fpDataGFD */
assert(fpDataGFD == NULL);
dierr = fpImageWrapper->Create(fLength, fPhysical, fOrder,
fDOSVolumeNum, fpWrapperGFD, &fWrappedLength, &fpDataGFD);
if (dierr != kDIErrNone) {
LOGI("ImageWrapper Create failed, err=%d", dierr);
goto bail;
}
assert(fpDataGFD != NULL);
/*
* Step 6: "format" fpDataGFD.
*
* Note we don't specify an ordering to the "create blank" functions.
* Either it's sectors, in which case it's all zeroes, or it's nibbles,
* in which case it's always in physical order.
*
* If we're formatting for nibbles, and the application hasn't specified
* a disk volume number, use the default (254).
*/
if (fPhysical == kPhysicalFormatSectors)
dierr = FormatSectors(fpDataGFD, skipFormat); // zero out the image
else {
assert(!skipFormat); // don't skip low-level nibble formatting!
if (fDOSVolumeNum == kVolumeNumNotSet) {
fDOSVolumeNum = kDefaultNibbleVolumeNum;
LOGI(" Using default nibble volume num");
}
dierr = FormatNibbles(fpDataGFD); // write basic nibble stuff
}
/*
* We're done!
*
* Quick sanity check...
*/
if (fOuterFormat != kOuterFormatNone) {
assert(fpOuterGFD != NULL);
assert(fpWrapperGFD != NULL);
assert(fpDataGFD != NULL);
}
bail:
return dierr;
}
/*
* Check that the requested format is one we can create.
*
* We don't allow .SDK.GZ or 6384-byte nibble 2MG. 2MG sector images
* must be in DOS or ProDOS order.
*
* Only "generic" FS formats may be used. The application may choose
* to call AnalyzeImage later on to set the actual FS once data has
* been written.
*/
DIError
DiskImg::ValidateCreateFormat(void) const
{
/*
* Check for invalid arguments.
*/
if (fHasBlocks && fNumBlocks >= 4194304) { // 2GB or larger?
if (fFileFormat != kFileFormatUnadorned) {
LOGI("CreateImage: images >= 2GB can only be unadorned");
return kDIErrInvalidCreateReq;
}
}
if (fOuterFormat == kOuterFormatUnknown ||
fFileFormat == kFileFormatUnknown ||
fPhysical == kPhysicalFormatUnknown ||
fOrder == kSectorOrderUnknown ||
fFormat == kFormatUnknown)
{
LOGI("CreateImage: ambiguous format");
return kDIErrInvalidCreateReq;
}
if (fOuterFormat != kOuterFormatNone &&
fOuterFormat != kOuterFormatGzip &&
fOuterFormat != kOuterFormatZip)
{
LOGI("CreateImage: unsupported outer format %d", fOuterFormat);
return kDIErrInvalidCreateReq;
}
if (fFileFormat != kFileFormatUnadorned &&
fFileFormat != kFileFormat2MG &&
fFileFormat != kFileFormatDiskCopy42 &&
fFileFormat != kFileFormatSim2eHDV &&
fFileFormat != kFileFormatTrackStar &&
fFileFormat != kFileFormatFDI &&
fFileFormat != kFileFormatNuFX &&
fFileFormat != kFileFormatDDD)
{
LOGI("CreateImage: unsupported file format %d", fFileFormat);
return kDIErrInvalidCreateReq;
}
if (fFormat != kFormatGenericPhysicalOrd &&
fFormat != kFormatGenericProDOSOrd &&
fFormat != kFormatGenericDOSOrd &&
fFormat != kFormatGenericCPMOrd)
{
LOGI("CreateImage: may only use 'generic' formats");
return kDIErrInvalidCreateReq;
}
/*
* Check for invalid combinations.
*/
if (fPhysical != kPhysicalFormatSectors) {
if (fOrder != kSectorOrderPhysical) {
LOGI("CreateImage: nibble images are always 'physical' order");
return kDIErrInvalidCreateReq;
}
if (GetHasSectors() == false && GetHasNibbles() == false) {
LOGI("CreateImage: must set hasSectors(%d) or hasNibbles(%d)",
GetHasSectors(), GetHasNibbles());
return kDIErrInvalidCreateReq;
}
if (fpNibbleDescr == NULL && GetNumSectPerTrack() > 0) {
LOGI("CreateImage: must provide NibbleDescr for non-sector");
return kDIErrInvalidCreateReq;
}
if (fpNibbleDescr != NULL &&
fpNibbleDescr->numSectors != GetNumSectPerTrack())
{
LOGI("CreateImage: ?? nd->numSectors=%d, GetNumSectPerTrack=%d",
fpNibbleDescr->numSectors, GetNumSectPerTrack());
return kDIErrInvalidCreateReq;
}
if (fpNibbleDescr != NULL && (
(fpNibbleDescr->numSectors == 13 &&
fpNibbleDescr->encoding != kNibbleEnc53) ||
(fpNibbleDescr->numSectors == 16 &&
fpNibbleDescr->encoding != kNibbleEnc62))
)
{
LOGI("CreateImage: sector count/encoding mismatch");
return kDIErrInvalidCreateReq;
}
if (GetNumTracks() != kTrackCount525 &&
!(GetNumTracks() == 40 && fFileFormat == kFileFormatTrackStar))
{
LOGI("CreateImage: unexpected track count %ld", GetNumTracks());
return kDIErrInvalidCreateReq;
}
}
if (fFileFormat == kFileFormat2MG) {
if (fPhysical != kPhysicalFormatSectors &&
fPhysical != kPhysicalFormatNib525_6656)
{
LOGI("CreateImage: 2MG can't handle physical %d", fPhysical);
return kDIErrInvalidCreateReq;
}
if (fPhysical == kPhysicalFormatSectors &&
(fOrder != kSectorOrderProDOS &&
fOrder != kSectorOrderDOS))
{
LOGI("CreateImage: 2MG requires DOS or ProDOS ordering");
return kDIErrInvalidCreateReq;
}
}
if (fFileFormat == kFileFormatNuFX) {
if (fOuterFormat != kOuterFormatNone) {
LOGI("CreateImage: can't mix NuFX and outer wrapper");
return kDIErrInvalidCreateReq;
}
if (fPhysical != kPhysicalFormatSectors) {
LOGI("CreateImage: NuFX physical must be sectors");
return kDIErrInvalidCreateReq;
}
if (fOrder != kSectorOrderProDOS) {
LOGI("CreateImage: NuFX is always ProDOS-order");
return kDIErrInvalidCreateReq;
}
}
if (fFileFormat == kFileFormatDiskCopy42) {
if (fPhysical != kPhysicalFormatSectors) {
LOGI("CreateImage: DC42 physical must be sectors");
return kDIErrInvalidCreateReq;
}
if ((GetHasBlocks() && GetNumBlocks() != 1600) ||
GetHasSectors() &&
(GetNumTracks() != 200 || GetNumSectPerTrack() != 16))
{
LOGI("CreateImage: DC42 only for 800K disks");
return kDIErrInvalidCreateReq;
}
if (fOrder != kSectorOrderProDOS &&
fOrder != kSectorOrderDOS) // used for UNIDOS disks??
{
LOGI("CreateImage: DC42 is always ProDOS or DOS");
return kDIErrInvalidCreateReq;
}
}
if (fFileFormat == kFileFormatSim2eHDV) {
if (fPhysical != kPhysicalFormatSectors) {
LOGI("CreateImage: Sim2eHDV physical must be sectors");
return kDIErrInvalidCreateReq;
}
if (fOrder != kSectorOrderProDOS) {
LOGI("CreateImage: Sim2eHDV is always ProDOS-order");
return kDIErrInvalidCreateReq;
}
}
if (fFileFormat == kFileFormatTrackStar) {
if (fPhysical != kPhysicalFormatNib525_Var) {
LOGI("CreateImage: TrackStar physical must be var-nibbles");
return kDIErrInvalidCreateReq;
}
}
if (fFileFormat == kFileFormatFDI) {
if (fPhysical != kPhysicalFormatNib525_Var) {
LOGI("CreateImage: FDI physical must be var-nibbles");
return kDIErrInvalidCreateReq;
}
}
if (fFileFormat == kFileFormatDDD) {
if (fPhysical != kPhysicalFormatSectors) {
LOGI("CreateImage: DDD physical must be sectors");
return kDIErrInvalidCreateReq;
}
if (fOrder != kSectorOrderDOS) {
LOGI("CreateImage: DDD is always DOS-order");
return kDIErrInvalidCreateReq;
}
if (!GetHasSectors() || GetNumTracks() != 35 ||
GetNumSectPerTrack() != 16)
{
LOGI("CreateImage: DDD is only for 16-sector 35-track disks");
return kDIErrInvalidCreateReq;
}
}
return kDIErrNone;
}
/*
* Create a blank image for physical=="sectors".
*
* fLength must be a multiple of 256.
*
* If "quickFormat" is set, only the very last sector is written (to set
* the EOF on the file).
*/
DIError
DiskImg::FormatSectors(GenericFD* pGFD, bool quickFormat) const
{
DIError dierr = kDIErrNone;
char sctBuf[kSectorSize];
di_off_t length;
assert(fLength > 0 && (fLength & 0xff) == 0);
//if (!(fLength & 0x01))
// return FormatBlocks(pGFD);
memset(sctBuf, 0, sizeof(sctBuf));
pGFD->Rewind();
if (quickFormat) {
dierr = pGFD->Seek(fLength - sizeof(sctBuf), kSeekSet);
if (dierr != kDIErrNone) {
LOGI(" FormatSectors: GFD seek %ld failed (err=%d)",
(long) fLength - sizeof(sctBuf), dierr);
goto bail;
}
dierr = pGFD->Write(sctBuf, sizeof(sctBuf), NULL);
if (dierr != kDIErrNone) {
LOGI(" FormatSectors: GFD quick write failed (err=%d)", dierr);
goto bail;
}
} else {
for (length = fLength ; length > 0; length -= sizeof(sctBuf)) {
dierr = pGFD->Write(sctBuf, sizeof(sctBuf), NULL);
if (dierr != kDIErrNone) {
LOGI(" FormatSectors: GFD write failed (err=%d)", dierr);
goto bail;
}
}
assert(length == 0);
}
bail:
return dierr;
}
#if 0 // didn't help
/*
* Create a blank image for physical=="sectors". This is called from
* FormatSectors when it looks like we're formatting entire blocks.
*/
DIError
DiskImg::FormatBlocks(GenericFD* pGFD) const
{
DIError dierr;
char blkBuf[kBlockSize];
long length;
time_t start, end;
assert(fLength > 0 && (fLength & 0x1ff) == 0);
start = time(NULL);
memset(blkBuf, 0, sizeof(blkBuf));
pGFD->Rewind();
for (length = fLength ; length > 0; length -= sizeof(blkBuf)) {
dierr = pGFD->Write(blkBuf, sizeof(blkBuf), NULL);
if (dierr != kDIErrNone) {
LOGI(" FormatBlocks: GFD write failed (err=%d)", dierr);
return dierr;
}
}
assert(length == 0);
end = time(NULL);
LOGI("FormatBlocks complete, time=%ld", end - start);
return kDIErrNone;
}
#endif
/*
* ===========================================================================
* Utility functions
* ===========================================================================
*/
/*
* Add a note to this disk image.
*
* This is how we communicate cautions and warnings to the user. Use
* linefeeds ('\n') to indicate line breaks.
*
* The maximum length of a single note is set by the size of "buf".
*/
void
DiskImg::AddNote(NoteType type, const char* fmt, ...)
{
char buf[512];
char* cp = buf;
int maxLen = sizeof(buf);
va_list args;
int len;
/*
* Prepend a string that highlights the note.
*/
switch (type) {
case kNoteWarning:
strcpy(cp, "- WARNING: ");
break;
default:
strcpy(cp, "- ");
break;
}
len = strlen(cp);
cp += len;
maxLen -= len;
/*
* Add the note.
*/
va_start(args, fmt);
#if defined(HAVE_VSNPRINTF)
(void) vsnprintf(cp, maxLen, fmt, args);
#elif defined(HAVE__VSNPRINTF)
(void) _vsnprintf(cp, maxLen, fmt, args);
#else
# error "hosed"
#endif
va_end(args);
buf[sizeof(buf)-2] = '\0'; // leave room for additional '\n'
len = strlen(buf);
if (len > 0 && buf[len-1] != '\n') {
buf[len] = '\n';
buf[len+1] = '\0';
len++;
}
LOGI("+++ adding note '%s'", buf);
if (fNotes == NULL) {
fNotes = new char[len +1];
if (fNotes == NULL) {
LOGI("Unable to create notes[%d]", len+1);
assert(false);
return;
}
strcpy(fNotes, buf);
} else {
int existingLen = strlen(fNotes);
char* newNotes = new char[existingLen + len +1];
if (newNotes == NULL) {
LOGI("Unable to create newNotes[%d]", existingLen+len+1);
assert(false);
return;
}
strcpy(newNotes, fNotes);
strcpy(newNotes + existingLen, buf);
delete[] fNotes;
fNotes = newNotes;
}
}
/*
* Return a string with the notes in it.
*/
const char*
DiskImg::GetNotes(void) const
{
if (fNotes == NULL)
return "";
else
return fNotes;
}
/*
* Get length and offset of tracks in a nibble image. This is necessary
* because of formats with variable-length tracks (e.g. TrackStar).
*/
int
DiskImg::GetNibbleTrackLength(long track) const
{
assert(fpImageWrapper != NULL);
return fpImageWrapper->GetNibbleTrackLength(fPhysical, track);
}
int
DiskImg::GetNibbleTrackOffset(long track) const
{
assert(fpImageWrapper != NULL);
return fpImageWrapper->GetNibbleTrackOffset(fPhysical, track);
}
/*
* Return a new object with the appropriate DiskFS sub-class.
*
* If the image hasn't been analyzed, or was analyzed to no avail, "NULL"
* is returned unless "allowUnknown" is set to "true". In that case, a
* DiskFSUnknown is returned.
*
* This doesn't inspire the DiskFS to do any processing, just creates the
* new object.
*/
DiskFS*
DiskImg::OpenAppropriateDiskFS(bool allowUnknown)
{
DiskFS* pDiskFS = NULL;
/*
* Create an appropriate DiskFS object.
*/
switch (GetFSFormat()) {
case DiskImg::kFormatDOS33:
case DiskImg::kFormatDOS32:
pDiskFS = new DiskFSDOS33();
break;
case DiskImg::kFormatProDOS:
pDiskFS = new DiskFSProDOS();
break;
case DiskImg::kFormatPascal:
pDiskFS = new DiskFSPascal();
break;
case DiskImg::kFormatMacHFS:
pDiskFS = new DiskFSHFS();
break;
case DiskImg::kFormatUNIDOS:
pDiskFS = new DiskFSUNIDOS();
break;
case DiskImg::kFormatOzDOS:
pDiskFS = new DiskFSOzDOS();
break;
case DiskImg::kFormatCFFA4:
case DiskImg::kFormatCFFA8:
pDiskFS = new DiskFSCFFA();
break;
case DiskImg::kFormatMacPart:
pDiskFS = new DiskFSMacPart();
break;
case DiskImg::kFormatMicroDrive:
pDiskFS = new DiskFSMicroDrive();
break;
case DiskImg::kFormatFocusDrive:
pDiskFS = new DiskFSFocusDrive();
break;
case DiskImg::kFormatCPM:
pDiskFS = new DiskFSCPM();
break;
case DiskImg::kFormatMSDOS:
pDiskFS = new DiskFSFAT();
break;
case DiskImg::kFormatRDOS33:
case DiskImg::kFormatRDOS32:
case DiskImg::kFormatRDOS3:
pDiskFS = new DiskFSRDOS();
break;
case DiskImg::kFormatGutenberg:
pDiskFS = new DiskFSGutenberg();
break;
default:
LOGI("WARNING: unhandled DiskFS case %d", GetFSFormat());
assert(false);
/* fall through */
case DiskImg::kFormatGenericPhysicalOrd:
case DiskImg::kFormatGenericProDOSOrd:
case DiskImg::kFormatGenericDOSOrd:
case DiskImg::kFormatGenericCPMOrd:
case DiskImg::kFormatUnknown:
if (allowUnknown) {
pDiskFS = new DiskFSUnknown();
break;
}
}
return pDiskFS;
}
/*
* Fill an array with SectorOrder values. The ordering specified by "first"
* will come first. Unused entries will be set to "unknown" and should be
* ignored.
*
* "orderArray" must have kSectorOrderMax elements.
*/
/*static*/ void
DiskImg::GetSectorOrderArray(SectorOrder* orderArray, SectorOrder first)
{
// init array
for (int i = 0; i < kSectorOrderMax; i++)
orderArray[i] = (SectorOrder) i;
// pull the best-guess ordering to the front
assert(orderArray[0] == kSectorOrderUnknown);
orderArray[0] = first;
orderArray[(int) first] = kSectorOrderUnknown;
// don't bother checking CP/M sector order
orderArray[kSectorOrderCPM] = kSectorOrderUnknown;
}
/*
* Return a short string describing "format".
*
* These are semi-duplicated in ImageFormatDialog.cpp in CiderPress.
*/
/*static*/ const char*
DiskImg::ToStringCommon(int format, const ToStringLookup* pTable,
int tableSize)
{
for (int i = 0; i < tableSize; i++) {
if (pTable[i].format == format)
return pTable[i].str;
}
assert(false);
return "(unknown)";
}
/*static*/ const char*
DiskImg::ToString(OuterFormat format)
{
static const ToStringLookup kOuterFormats[] = {
{ DiskImg::kOuterFormatUnknown, "Unknown format" },
{ DiskImg::kOuterFormatNone, "(none)" },
{ DiskImg::kOuterFormatCompress, "UNIX compress" },
{ DiskImg::kOuterFormatGzip, "gzip" },
{ DiskImg::kOuterFormatBzip2, "bzip2" },
{ DiskImg::kOuterFormatZip, "Zip archive" },
};
return ToStringCommon(format, kOuterFormats, NELEM(kOuterFormats));
}
/*static*/ const char*
DiskImg::ToString(FileFormat format)
{
static const ToStringLookup kFileFormats[] = {
{ DiskImg::kFileFormatUnknown, "Unknown format" },
{ DiskImg::kFileFormatUnadorned, "Unadorned raw data" },
{ DiskImg::kFileFormat2MG, "2MG" },
{ DiskImg::kFileFormatNuFX, "NuFX (ShrinkIt)" },
{ DiskImg::kFileFormatDiskCopy42, "DiskCopy 4.2" },
{ DiskImg::kFileFormatDiskCopy60, "DiskCopy 6.0" },
{ DiskImg::kFileFormatDavex, "Davex volume image" },
{ DiskImg::kFileFormatSim2eHDV, "Sim //e HDV" },
{ DiskImg::kFileFormatTrackStar, "TrackStar image" },
{ DiskImg::kFileFormatFDI, "FDI image" },
{ DiskImg::kFileFormatDDD, "DDD" },
{ DiskImg::kFileFormatDDDDeluxe, "DDDDeluxe" },
};
return ToStringCommon(format, kFileFormats, NELEM(kFileFormats));
};
/*static*/ const char*
DiskImg::ToString(PhysicalFormat format)
{
static const ToStringLookup kPhysicalFormats[] = {
{ DiskImg::kPhysicalFormatUnknown, "Unknown format" },
{ DiskImg::kPhysicalFormatSectors, "Sectors" },
{ DiskImg::kPhysicalFormatNib525_6656, "Raw nibbles (6656-byte)" },
{ DiskImg::kPhysicalFormatNib525_6384, "Raw nibbles (6384-byte)" },
{ DiskImg::kPhysicalFormatNib525_Var, "Raw nibbles (variable len)" },
};
return ToStringCommon(format, kPhysicalFormats, NELEM(kPhysicalFormats));
};
/*static*/ const char*
DiskImg::ToString(SectorOrder format)
{
static const ToStringLookup kSectorOrders[] = {
{ DiskImg::kSectorOrderUnknown, "Unknown ordering" },
{ DiskImg::kSectorOrderProDOS, "ProDOS block ordering" },
{ DiskImg::kSectorOrderDOS, "DOS sector ordering" },
{ DiskImg::kSectorOrderCPM, "CP/M block ordering" },
{ DiskImg::kSectorOrderPhysical, "Physical sector ordering" },
};
return ToStringCommon(format, kSectorOrders, NELEM(kSectorOrders));
};
/*static*/ const char*
DiskImg::ToString(FSFormat format)
{
static const ToStringLookup kFSFormats[] = {
{ DiskImg::kFormatUnknown, "Unknown" },
{ DiskImg::kFormatProDOS, "ProDOS" },
{ DiskImg::kFormatDOS33, "DOS 3.3" },
{ DiskImg::kFormatDOS32, "DOS 3.2" },
{ DiskImg::kFormatPascal, "Pascal" },
{ DiskImg::kFormatMacHFS, "HFS" },
{ DiskImg::kFormatMacMFS, "MFS" },
{ DiskImg::kFormatLisa, "Lisa" },
{ DiskImg::kFormatCPM, "CP/M" },
{ DiskImg::kFormatMSDOS, "MS-DOS FAT" },
{ DiskImg::kFormatISO9660, "ISO-9660" },
{ DiskImg::kFormatRDOS33, "RDOS 3.3 (16-sector)" },
{ DiskImg::kFormatRDOS32, "RDOS 3.2 (13-sector)" },
{ DiskImg::kFormatRDOS3, "RDOS 3 (cracked 13-sector)" },
{ DiskImg::kFormatGenericDOSOrd, "Generic DOS sectors" },
{ DiskImg::kFormatGenericProDOSOrd, "Generic ProDOS blocks" },
{ DiskImg::kFormatGenericPhysicalOrd, "Generic raw sectors" },
{ DiskImg::kFormatGenericCPMOrd, "Generic CP/M blocks" },
{ DiskImg::kFormatUNIDOS, "UNIDOS (400K DOS x2)" },
{ DiskImg::kFormatOzDOS, "OzDOS (400K DOS x2)" },
{ DiskImg::kFormatCFFA4, "CFFA (4 or 6 partitions)" },
{ DiskImg::kFormatCFFA8, "CFFA (8 partitions)" },
{ DiskImg::kFormatMacPart, "Macintosh partitioned disk" },
{ DiskImg::kFormatMicroDrive, "MicroDrive partitioned disk" },
{ DiskImg::kFormatFocusDrive, "FocusDrive partitioned disk" },
};
return ToStringCommon(format, kFSFormats, NELEM(kFSFormats));
};
/*
* strerror() equivalent for DiskImg errors.
*/
const char*
DiskImgLib::DIStrError(DIError dierr)
{
if (dierr > 0) {
const char* msg;
msg = strerror(dierr);
if (msg != NULL)
return msg;
}
/*
* BUG: this should be set up as per-thread storage in an MT environment.
* I would be more inclined to worry about this if I was expecting
* to hit "default:". So long as valid values are passed in, and the
* switch statement is kept up to date, we should never have cause
* to return this.
*
* An easier solution, should this present a problem for someone, would
* be to have the function return NULL or "unknown error" when the
* error value isn't recognized. I'd recommend leaving it as-is for
* debug builds, though, as it's helpful to know *which* error is not
* recognized.
*/
static char defaultMsg[32];
switch (dierr) {
case kDIErrNone:
return "(no error)";
case kDIErrAccessDenied:
return "access denied";
case kDIErrVWAccessForbidden:
return "for safety, write access to this volume is forbidden";
case kDIErrSharingViolation:
return "file is already open and cannot be shared";
case kDIErrNoExclusiveAccess:
return "couldn't get exclusive access";
case kDIErrWriteProtected:
return "write protected";
case kDIErrCDROMNotSupported:
return "access to CD-ROM drives is not supported";
case kDIErrASPIFailure:
return "an ASPI request failed";
case kDIErrSPTIFailure:
return "an SPTI request failed";
case kDIErrSCSIFailure:
return "a SCSI request failed";
case kDIErrDeviceNotReady:
return "device not ready";
case kDIErrFileNotFound:
return "file not found";
case kDIErrForkNotFound:
return "fork not found";
case kDIErrAlreadyOpen:
return "an image is already open";
case kDIErrFileOpen:
return "file is open";
case kDIErrNotReady:
return "object not ready";
case kDIErrFileExists:
return "file already exists";
case kDIErrDirectoryExists:
return "directory already exists";
case kDIErrEOF:
return "end of file reached";
case kDIErrReadFailed:
return "read failed";
case kDIErrWriteFailed:
return "write failed";
case kDIErrDataUnderrun:
return "tried to read past end of file";
case kDIErrDataOverrun:
return "tried to write past end of file";
case kDIErrGenericIO:
return "I/O error";
case kDIErrOddLength:
return "image size is wrong";
case kDIErrUnrecognizedFileFmt:
return "not a recognized disk image format";
case kDIErrBadFileFormat:
return "image file contents aren't in expected format";
case kDIErrUnsupportedFileFmt:
return "file format not supported";
case kDIErrUnsupportedPhysicalFmt:
return "physical format not supported";
case kDIErrUnsupportedFSFmt:
return "filesystem type not supported";
case kDIErrBadOrdering:
return "bad sector ordering";
case kDIErrFilesystemNotFound:
return "specified filesystem not found";
case kDIErrUnsupportedAccess:
return "the method of access used isn't supported for this image";
case kDIErrUnsupportedImageFeature:
return "image file uses features that CiderPress doesn't support";
case kDIErrInvalidTrack:
return "invalid track number";
case kDIErrInvalidSector:
return "invalid sector number";
case kDIErrInvalidBlock:
return "invalid block number";
case kDIErrInvalidIndex:
return "invalid index number";
case kDIErrDirectoryLoop:
return "disk directory structure has an infinite loop";
case kDIErrFileLoop:
return "file structure has an infinite loop";
case kDIErrBadDiskImage:
return "the filesystem on this image appears damaged";
case kDIErrBadFile:
return "file structure appears damaged";
case kDIErrBadDirectory:
return "a directory appears damaged";
case kDIErrBadPartition:
return "bad partition";
case kDIErrFileArchive:
return "this looks like a file archive, not a disk archive";
case kDIErrUnsupportedCompression:
return "compression method not supported";
case kDIErrBadChecksum:
return "checksum doesn't match, data may be corrupted";
case kDIErrBadCompressedData:
return "the compressed data is corrupted";
case kDIErrBadArchiveStruct:
return "archive may be damaged";
case kDIErrBadNibbleSectors:
return "couldn't read sectors from this image";
case kDIErrSectorUnreadable:
return "sector not readable";
case kDIErrInvalidDiskByte:
return "found invalid nibble image disk byte";
case kDIErrBadRawData:
return "couldn't convert raw data to nibble data";
case kDIErrInvalidFileName:
return "invalid file name";
case kDIErrDiskFull:
return "disk full";
case kDIErrVolumeDirFull:
return "volume directory is full";
case kDIErrInvalidCreateReq:
return "invalid disk image create request";
case kDIErrTooBig:
return "size is larger than we can handle";
case kDIErrGeneric:
return "DiskImg generic error";
case kDIErrInternal:
return "DiskImg internal error";
case kDIErrMalloc:
return "memory allocation failure";
case kDIErrInvalidArg:
return "invalid argument";
case kDIErrNotSupported:
return "feature not supported";
case kDIErrCancelled:
return "cancelled by user";
case kDIErrNufxLibInitFailed:
return "NufxLib initialization failed";
default:
sprintf(defaultMsg, "(error=%d)", dierr);
return defaultMsg;
}
}
/*
* High ASCII conversion table, from Technical Note PT515,
* "Apple File Exchange Q&As". The table is available in a hopelessly
* blurry PDF or a pair of GIFs created with small fonts, but I think I
* have mostly captured it.
*/
/*static*/ const unsigned char DiskImg::kMacHighASCII[128+1] =
"AACENOUaaaaaaceeeeiiiinooooouuuu" // 0x80 - 0x9f
"tocL$oPBrct'.=AO%+<>YudsPpSaoOao" // 0xa0 - 0xbf
"?!-vf=d<>. AAOOo--\"\"''/oyY/o<> f" // 0xc0 - 0xdf
"|*,,%AEAEEIIIIOOaOUUUi^~-,**,\"? "; // 0xe0 - 0xff
/*
* Hack for Win32 systems. See Win32BlockIO.cpp for commentary.
*/
bool DiskImgLib::gAllowWritePhys0 = false;
/*static*/ void DiskImg::SetAllowWritePhys0(bool val) {
DiskImgLib::gAllowWritePhys0 = val;
}