mirror of
https://github.com/fadden/ciderpress.git
synced 2024-10-13 07:23:48 +00:00
b42cc8efe9
gcc is justifiably annoyed by variable initialization crossed by a goto. (issue #51)
5184 lines
173 KiB
C++
5184 lines
173 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
/*
|
|
* Implementation of DiskFSProDOS class.
|
|
*
|
|
* We currently only allow one fork to be open at a time, and each file may
|
|
* only be opened once.
|
|
*
|
|
* BUG: does not keep VolumeUsage up to date.
|
|
*/
|
|
#include "StdAfx.h"
|
|
#include "DiskImgPriv.h"
|
|
|
|
// disable Y2K+ dates when testing w/ProSel-16 vol rep (newer ProSel is OK)
|
|
//#define OLD_PRODOS_DATES
|
|
|
|
#if defined(OLD_PRODOS_DATES) && !(defined(_DEBUG))
|
|
# error "don't set OLD_PRODOS_DATES for production"
|
|
#endif
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* DiskFSProDOS
|
|
* ===========================================================================
|
|
*/
|
|
|
|
const int kBlkSize = 512;
|
|
const int kVolHeaderBlock = 2; // block where Volume Header resides
|
|
const int kFormatVolDirNumBlocks = 4; // #of volume header blocks for new volumes
|
|
const int kMinReasonableBlocks = 16; // min size for ProDOS volume
|
|
const int kExpectedBitmapStart = 6; // block# where vol bitmap should start
|
|
const int kMaxCatalogIterations = 1024; // theoretical max is 32768?
|
|
const int kMaxDirectoryDepth = 64; // not sure what ProDOS limit is
|
|
const int kEntriesPerBlock = 0x0d; // expected value for entries per blk
|
|
const int kEntryLength = 0x27; // expected value for dir entry len
|
|
const int kTypeDIR = 0x0f;
|
|
|
|
|
|
/*
|
|
* Directory header. All fields not marked as "only for subdirs" also apply
|
|
* to the volume directory header.
|
|
*/
|
|
typedef struct DiskFSProDOS::DirHeader {
|
|
uint8_t storageType;
|
|
char dirName[A2FileProDOS::kMaxFileName+1];
|
|
DiskFSProDOS::ProDate createWhen;
|
|
uint8_t version;
|
|
uint8_t minVersion;
|
|
uint8_t access;
|
|
uint8_t entryLength;
|
|
uint8_t entriesPerBlock;
|
|
uint16_t fileCount;
|
|
/* the rest are only for subdirs */
|
|
uint16_t parentPointer;
|
|
uint8_t parentEntry;
|
|
uint8_t parentEntryLength;
|
|
} DirHeader;
|
|
|
|
|
|
/*
|
|
* See if this looks like a ProDOS volume.
|
|
*
|
|
* We test a few fields in the volume directory header for validity.
|
|
*/
|
|
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
uint8_t blkBuf[kBlkSize];
|
|
int volDirEntryLength;
|
|
int volDirEntriesPerBlock;
|
|
|
|
dierr = pImg->ReadBlockSwapped(kVolHeaderBlock, blkBuf, imageOrder,
|
|
DiskImg::kSectorOrderProDOS);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
volDirEntryLength = blkBuf[0x23];
|
|
volDirEntriesPerBlock = blkBuf[0x24];
|
|
|
|
|
|
if (!(blkBuf[0x00] == 0 && blkBuf[0x01] == 0) ||
|
|
!((blkBuf[0x04] & 0xf0) == 0xf0) ||
|
|
!((blkBuf[0x04] & 0x0f) != 0) ||
|
|
!(volDirEntryLength * volDirEntriesPerBlock <= kBlkSize) ||
|
|
!(blkBuf[0x05] >= 'A' && blkBuf[0x05] <= 'Z') ||
|
|
0)
|
|
{
|
|
dierr = kDIErrFilesystemNotFound;
|
|
goto bail;
|
|
}
|
|
|
|
bail:
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Test to see if the image is a ProDOS disk.
|
|
*/
|
|
/*static*/ DIError DiskFSProDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
|
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
|
{
|
|
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
|
|
|
|
DiskImg::GetSectorOrderArray(ordering, *pOrder);
|
|
|
|
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
|
|
if (ordering[i] == DiskImg::kSectorOrderUnknown)
|
|
continue;
|
|
if (TestImage(pImg, ordering[i]) == kDIErrNone) {
|
|
*pOrder = ordering[i];
|
|
*pFormat = DiskImg::kFormatProDOS;
|
|
return kDIErrNone;
|
|
}
|
|
}
|
|
|
|
LOGI(" ProDOS didn't find valid FS");
|
|
return kDIErrFilesystemNotFound;
|
|
}
|
|
|
|
/*
|
|
* Get things rolling.
|
|
*
|
|
* Since we're assured that this is a valid disk, errors encountered from here
|
|
* on out must be handled somehow, possibly by claiming that the disk has
|
|
* no files on it.
|
|
*/
|
|
DIError DiskFSProDOS::Initialize(InitMode initMode)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
char msg[kMaxVolumeName + 32];
|
|
|
|
fDiskIsGood = false; // hosed until proven innocent
|
|
fEarlyDamage = false;
|
|
|
|
/*
|
|
* NOTE: we'd probably be better off with fTotalBlocks, since that's how
|
|
* big the disk *thinks* it is, especially on a CFFA or MacPart subvol.
|
|
* However, we know that the image block count is the absolute maximum,
|
|
* so while it may not be a tight bound it is an upper bound.
|
|
*/
|
|
fVolumeUsage.Create(fpImg->GetNumBlocks());
|
|
|
|
dierr = LoadVolHeader();
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
DumpVolHeader();
|
|
|
|
dierr = ScanVolBitmap();
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
if (initMode == kInitHeaderOnly) {
|
|
LOGI(" ProDOS - headerOnly set, skipping file load");
|
|
goto bail;
|
|
}
|
|
|
|
sprintf(msg, "Scanning %s", fVolumeName);
|
|
if (!fpImg->UpdateScanProgress(msg)) {
|
|
LOGI(" ProDOS cancelled by user");
|
|
dierr = kDIErrCancelled;
|
|
goto bail;
|
|
}
|
|
|
|
/* volume dir is guaranteed to come first; if not, we need a lookup func */
|
|
A2FileProDOS* pVolumeDir;
|
|
pVolumeDir = (A2FileProDOS*) GetNextFile(NULL);
|
|
|
|
dierr = RecursiveDirAdd(pVolumeDir, kVolHeaderBlock, "", 0);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" ProDOS RecursiveDirAdd failed");
|
|
goto bail;
|
|
}
|
|
|
|
sprintf(msg, "Processing %s", fVolumeName);
|
|
if (!fpImg->UpdateScanProgress(msg)) {
|
|
LOGI(" ProDOS cancelled by user");
|
|
dierr = kDIErrCancelled;
|
|
goto bail;
|
|
}
|
|
|
|
dierr = ScanFileUsage();
|
|
if (dierr != kDIErrNone) {
|
|
if (dierr == kDIErrCancelled)
|
|
goto bail;
|
|
|
|
/* this might not be fatal; just means that *some* files are bad */
|
|
LOGI("WARNING: ScanFileUsage returned err=%d", dierr);
|
|
dierr = kDIErrNone;
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"Some errors were encountered while scanning files.");
|
|
fEarlyDamage = true; // make sure we know it's damaged
|
|
}
|
|
|
|
fDiskIsGood = CheckDiskIsGood();
|
|
|
|
if (fScanForSubVolumes != kScanSubDisabled)
|
|
(void) ScanForSubVolumes();
|
|
|
|
if (fpImg->GetNumBlocks() <= 1600)
|
|
fVolumeUsage.Dump();
|
|
|
|
// A2File* pFile;
|
|
// pFile = GetNextFile(NULL);
|
|
// while (pFile != NULL) {
|
|
// pFile->Dump();
|
|
// pFile = GetNextFile(pFile);
|
|
// }
|
|
|
|
bail:
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Read some interesting fields from the volume header.
|
|
*
|
|
* The "test" function verified certain things, e.g. the storage type
|
|
* is $f and the volume name length is nonzero.
|
|
*/
|
|
DIError DiskFSProDOS::LoadVolHeader(void)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
uint8_t blkBuf[kBlkSize];
|
|
int nameLen;
|
|
|
|
dierr = fpImg->ReadBlock(kVolHeaderBlock, blkBuf);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
//fPrevBlock = GetShortLE(&blkBuf[0x00]);
|
|
//fNextBlock = GetShortLE(&blkBuf[0x02]);
|
|
nameLen = blkBuf[0x04] & 0x0f;
|
|
memcpy(fVolumeName, &blkBuf[0x05], nameLen);
|
|
fVolumeName[nameLen] = '\0';
|
|
// 0x14-15 reserved
|
|
// undocumented: GS/OS writes the modification date to 0x16-19
|
|
fModWhen = GetLongLE(&blkBuf[0x16]);
|
|
// undocumented: GS/OS uses 0x1a-1b for lower-case handling (see below)
|
|
fCreateWhen = GetLongLE(&blkBuf[0x1c]);
|
|
//fVersion = blkBuf[0x20];
|
|
if (blkBuf[0x21] != 0) {
|
|
/*
|
|
* We don't care about the MIN_VERSION field, but it looks like GS/OS
|
|
* rejects anything with a nonzero value here. We want to add a note
|
|
* about it.
|
|
*/
|
|
fpImg->AddNote(DiskImg::kNoteInfo,
|
|
"Volume header has nonzero min_version; could confuse GS/OS.");
|
|
}
|
|
fAccess = blkBuf[0x22];
|
|
//fEntryLength = blkBuf[0x23];
|
|
//fEntriesPerBlock = blkBuf[0x24];
|
|
fVolDirFileCount = GetShortLE(&blkBuf[0x25]);
|
|
fBitMapPointer = GetShortLE(&blkBuf[0x27]);
|
|
fTotalBlocks = GetShortLE(&blkBuf[0x29]);
|
|
|
|
if (blkBuf[0x1b] & 0x80) {
|
|
/*
|
|
* Handle lower-case conversion; see GS/OS tech note #8. Unlike
|
|
* filenames, volume names are not allowed to contain spaces. If
|
|
* they try it we just ignore them.
|
|
*
|
|
* Technote 8 doesn't actually talk about volume names. By
|
|
* experimentation the field was discovered at offset 0x1a from
|
|
* the start of the block, which is marked as "reserved" in Beneath
|
|
* Apple ProDOS.
|
|
*/
|
|
uint16_t lcFlags = GetShortLE(&blkBuf[0x1a]);
|
|
|
|
GenerateLowerCaseName(fVolumeName, fVolumeName, lcFlags, false);
|
|
}
|
|
|
|
if (fTotalBlocks <= kVolHeaderBlock) {
|
|
/* incr to min; don't use max, or bitmap count may be too large */
|
|
LOGI(" ProDOS found tiny fTotalBlocks (%d), increasing to minimum",
|
|
fTotalBlocks);
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"ProDOS filesystem blockcount (%d) too small, setting to %d.",
|
|
fTotalBlocks, kMinReasonableBlocks);
|
|
fTotalBlocks = kMinReasonableBlocks;
|
|
fEarlyDamage = true;
|
|
} else if (fTotalBlocks != fpImg->GetNumBlocks()) {
|
|
if (fTotalBlocks != 65535 || fpImg->GetNumBlocks() != 65536) {
|
|
LOGI(" ProDOS WARNING: total (%u) != img (%ld)",
|
|
fTotalBlocks, fpImg->GetNumBlocks());
|
|
// could AddNote here, but not really necessary
|
|
}
|
|
|
|
/*
|
|
* For safety (esp. vol bitmap read), constrain fTotalBlocks. We might
|
|
* consider not doing this for ".hdv", which can start small and then
|
|
* expand as files are added. (Check "fExpanded".)
|
|
*/
|
|
if (fTotalBlocks > fpImg->GetNumBlocks()) {
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"ProDOS filesystem blockcount (%d) exceeds disk image blocks (%ld).",
|
|
fTotalBlocks, fpImg->GetNumBlocks());
|
|
fTotalBlocks = (uint16_t) fpImg->GetNumBlocks();
|
|
fEarlyDamage = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test for funky volume bitmap pointer. Some disks (e.g. /RAM and
|
|
* ProSel-16) truncate the volume directory to eke a little more storage
|
|
* out of a disk. There's nothing wrong with that, but we don't want to
|
|
* try to use a volume bitmap pointer of zero or 0xffff, because it's
|
|
* probably garbage.
|
|
*/
|
|
if (fBitMapPointer != kExpectedBitmapStart) {
|
|
if (fBitMapPointer <= kVolHeaderBlock ||
|
|
fBitMapPointer > kExpectedBitmapStart)
|
|
{
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"Volume bitmap pointer (%d) is probably invalid.",
|
|
fBitMapPointer);
|
|
fBitMapPointer = 6; // just fix it and hope for the best
|
|
fEarlyDamage = true;
|
|
} else {
|
|
fpImg->AddNote(DiskImg::kNoteInfo,
|
|
"Unusual volume bitmap start (%d).", fBitMapPointer);
|
|
// try it and see
|
|
}
|
|
}
|
|
|
|
SetVolumeID();
|
|
|
|
/*
|
|
* Create a "magic" directory entry for the volume directory.
|
|
*
|
|
* Normally these values are pulled out of the file entry in the parent
|
|
* directory. Here, we synthesize them from the volume dir header.
|
|
*/
|
|
A2FileProDOS* pFile;
|
|
pFile = new A2FileProDOS(this);
|
|
if (pFile == NULL) {
|
|
dierr = kDIErrMalloc;
|
|
goto bail;
|
|
}
|
|
|
|
A2FileProDOS::DirEntry* pEntry;
|
|
pEntry = &pFile->fDirEntry;
|
|
|
|
int foundStorage;
|
|
foundStorage = (blkBuf[0x04] & 0xf0) >> 4;
|
|
if (foundStorage != A2FileProDOS::kStorageVolumeDirHeader) {
|
|
LOGI(" ProDOS WARNING: unexpected vol dir file type %d",
|
|
pEntry->storageType);
|
|
/* keep going */
|
|
}
|
|
pEntry->storageType = A2FileProDOS::kStorageVolumeDirHeader;
|
|
strcpy(pEntry->fileName, fVolumeName);
|
|
//nameLen = blkBuf[0x04] & 0x0f;
|
|
//memcpy(pEntry->fileName, &blkBuf[0x05], nameLen);
|
|
//pEntry->fileName[nameLen] = '\0';
|
|
pFile->SetPathName(":", pEntry->fileName);
|
|
pEntry->fileName[nameLen] = '\0';
|
|
pEntry->fileType = kTypeDIR;
|
|
pEntry->keyPointer = kVolHeaderBlock;
|
|
dierr = DetermineVolDirLen(GetShortLE(&blkBuf[0x02]), &pEntry->blocksUsed);
|
|
if (dierr != kDIErrNone) {
|
|
goto bail;
|
|
}
|
|
pEntry->eof = pEntry->blocksUsed * 512;
|
|
pEntry->createWhen = GetLongLE(&blkBuf[0x1c]);
|
|
pEntry->version = blkBuf[0x20];
|
|
pEntry->minVersion = blkBuf[0x21];
|
|
pEntry->access = blkBuf[0x22];
|
|
pEntry->auxType = 0;
|
|
// if (blkBuf[0x20] >= 5)
|
|
pEntry->modWhen = GetLongLE(&blkBuf[0x16]);
|
|
pEntry->headerPointer = 0;
|
|
|
|
pFile->fSparseDataEof = pEntry->eof;
|
|
pFile->fSparseRsrcEof = -1;
|
|
|
|
AddFileToList(pFile);
|
|
pFile = NULL;
|
|
|
|
bail:
|
|
delete pFile;
|
|
return dierr;
|
|
}
|
|
|
|
DIError DiskFSProDOS::DetermineVolDirLen(uint16_t nextBlock, uint16_t* pBlocksUsed) {
|
|
DIError dierr = kDIErrNone;
|
|
uint8_t blkBuf[kBlkSize];
|
|
uint16_t blocksUsed = 1;
|
|
int iterCount = 0;
|
|
|
|
// Traverse the volume directory chain, counting blocks. Normally this will have 4, but
|
|
// variations are possible.
|
|
while (nextBlock != 0) {
|
|
blocksUsed++;
|
|
|
|
if (nextBlock < 2 || nextBlock >= fpImg->GetNumBlocks()) {
|
|
LOGI(" ProDOS ERROR: invalid volume dir link block %u", nextBlock);
|
|
dierr = kDIErrInvalidBlock;
|
|
goto bail;
|
|
}
|
|
dierr = fpImg->ReadBlock(nextBlock, blkBuf);
|
|
if (dierr != kDIErrNone) {
|
|
goto bail;
|
|
}
|
|
|
|
nextBlock = GetShortLE(&blkBuf[0x02]);
|
|
|
|
// Watch for infinite loop.
|
|
iterCount++;
|
|
if (iterCount > fpImg->GetNumBlocks()) {
|
|
LOGI(" ProDOS ERROR: infinite vol directory loop found");
|
|
dierr = kDIErrDirectoryLoop;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
bail:
|
|
*pBlocksUsed = blocksUsed;
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Set the volume ID field.
|
|
*/
|
|
void DiskFSProDOS::SetVolumeID(void)
|
|
{
|
|
sprintf(fVolumeID, "ProDOS /%s", fVolumeName);
|
|
}
|
|
|
|
/*
|
|
* Dump what we pulled out of the volume header.
|
|
*/
|
|
void DiskFSProDOS::DumpVolHeader(void)
|
|
{
|
|
LOGI(" ProDOS volume header for '%s'", fVolumeName);
|
|
LOGI(" CreateWhen=0x%08x access=0x%02x bitmap=%d totalbl=%d",
|
|
fCreateWhen, fAccess, fBitMapPointer, fTotalBlocks);
|
|
|
|
time_t when;
|
|
when = A2FileProDOS::ConvertProDate(fCreateWhen);
|
|
LOGI(" CreateWhen is %.24s", ctime(&when));
|
|
|
|
//LOGI(" prev=%d next=%d bitmap=%d total=%d",
|
|
// fPrevBlock, fNextBlock, fBitMapPointer, fTotalBlocks);
|
|
//LOGI(" create date=0x%08lx access=0x%02x", fCreateWhen, fAccess);
|
|
//LOGI(" version=%d minVersion=%d entryLen=%d epb=%d",
|
|
// fVersion, fMinVersion, fEntryLength, fEntriesPerBlock);
|
|
//LOGI(" volume dir fileCount=%d", fFileCount);
|
|
}
|
|
|
|
/*
|
|
* Load the disk's volume bitmap into the object's "fBlockUseMap" pointer.
|
|
*
|
|
* Does not attempt to analyze the data.
|
|
*/
|
|
DIError DiskFSProDOS::LoadVolBitmap(void)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
int bitBlock, numBlocks;
|
|
|
|
if (fBitMapPointer <= kVolHeaderBlock)
|
|
return kDIErrBadDiskImage;
|
|
if (fTotalBlocks <= kVolHeaderBlock)
|
|
return kDIErrBadDiskImage;
|
|
|
|
/* should not already be allocated */
|
|
assert(fBlockUseMap == NULL);
|
|
delete[] fBlockUseMap; // just in case
|
|
|
|
bitBlock = fBitMapPointer;
|
|
|
|
numBlocks = GetNumBitmapBlocks(); // based on fTotalBlocks
|
|
assert(numBlocks > 0);
|
|
|
|
fBlockUseMap = new uint8_t[kBlkSize * numBlocks];
|
|
if (fBlockUseMap == NULL)
|
|
return kDIErrMalloc;
|
|
|
|
while (numBlocks--) {
|
|
dierr = fpImg->ReadBlock(bitBlock + numBlocks,
|
|
fBlockUseMap + kBlkSize * numBlocks);
|
|
if (dierr != kDIErrNone) {
|
|
delete[] fBlockUseMap;
|
|
fBlockUseMap = NULL;
|
|
return dierr;
|
|
}
|
|
}
|
|
|
|
return kDIErrNone;
|
|
}
|
|
|
|
/*
|
|
* Save our copy of the volume bitmap.
|
|
*/
|
|
DIError DiskFSProDOS::SaveVolBitmap(void)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
int bitBlock, numBlocks;
|
|
|
|
if (fBlockUseMap == NULL) {
|
|
assert(false);
|
|
return kDIErrNotReady;
|
|
}
|
|
assert(fBitMapPointer > kVolHeaderBlock);
|
|
assert(fTotalBlocks > kVolHeaderBlock);
|
|
|
|
bitBlock = fBitMapPointer;
|
|
|
|
numBlocks = GetNumBitmapBlocks();
|
|
assert(numBlocks > 0);
|
|
|
|
while (numBlocks--) {
|
|
dierr = fpImg->WriteBlock(bitBlock + numBlocks,
|
|
fBlockUseMap + kBlkSize * numBlocks);
|
|
if (dierr != kDIErrNone)
|
|
return dierr;
|
|
}
|
|
|
|
return kDIErrNone;
|
|
}
|
|
|
|
/*
|
|
* Throw away the volume bitmap, discarding any unsaved changes.
|
|
*
|
|
* It's okay to call this if the bitmap isn't loaded.
|
|
*/
|
|
void DiskFSProDOS::FreeVolBitmap(void)
|
|
{
|
|
delete[] fBlockUseMap;
|
|
fBlockUseMap = NULL;
|
|
}
|
|
|
|
/*
|
|
* Examine the volume bitmap, setting fields in the VolumeUsage map
|
|
* as appropriate.
|
|
*/
|
|
DIError DiskFSProDOS::ScanVolBitmap(void)
|
|
{
|
|
DIError dierr;
|
|
|
|
dierr = LoadVolBitmap();
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" ProDOS failed to load volume bitmap (err=%d)", dierr);
|
|
return dierr;
|
|
}
|
|
|
|
assert(fBlockUseMap != NULL);
|
|
|
|
/* mark the boot blocks as system */
|
|
SetBlockUsage(0, VolumeUsage::kChunkPurposeSystem);
|
|
SetBlockUsage(1, VolumeUsage::kChunkPurposeSystem);
|
|
|
|
/* mark the bitmap blocks as system */
|
|
int i;
|
|
for (i = GetNumBitmapBlocks(); i > 0; i--)
|
|
SetBlockUsage(fBitMapPointer + i -1, VolumeUsage::kChunkPurposeSystem);
|
|
|
|
/*
|
|
* Set the "isMarkedUsed" flag in VolumeUsage for all used blocks.
|
|
*/
|
|
VolumeUsage::ChunkState cstate;
|
|
|
|
long block = 0;
|
|
long numBytes = (fTotalBlocks + 7) / 8;
|
|
for (i = 0; i < numBytes; i++) {
|
|
uint8_t val = fBlockUseMap[i];
|
|
|
|
for (int j = 0; j < 8; j++) {
|
|
if (!(val & 0x80)) {
|
|
/* block is in use, mark it */
|
|
if (fVolumeUsage.GetChunkState(block, &cstate) != kDIErrNone)
|
|
{
|
|
assert(false);
|
|
// keep going, I guess
|
|
}
|
|
cstate.isMarkedUsed = true;
|
|
fVolumeUsage.SetChunkState(block, &cstate);
|
|
}
|
|
val <<= 1;
|
|
block++;
|
|
|
|
if (block >= fTotalBlocks)
|
|
break;
|
|
}
|
|
if (block >= fTotalBlocks)
|
|
break;
|
|
}
|
|
|
|
FreeVolBitmap();
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Generate an empty block use map. Used by disk formatter.
|
|
*/
|
|
DIError DiskFSProDOS::CreateEmptyBlockMap(void)
|
|
{
|
|
DIError dierr;
|
|
|
|
/* load from disk; this is just to allocate the data structures */
|
|
dierr = LoadVolBitmap();
|
|
if (dierr != kDIErrNone)
|
|
return dierr;
|
|
|
|
/*
|
|
* Set the bits, block by block. Not the most efficient way, but it's
|
|
* fast enough, and it exercises the standard set of functions.
|
|
*/
|
|
long block;
|
|
long firstEmpty =
|
|
kVolHeaderBlock + kFormatVolDirNumBlocks + GetNumBitmapBlocks();
|
|
for (block = 0; block < firstEmpty; block++)
|
|
SetBlockUseEntry(block, true);
|
|
for ( ; block < fTotalBlocks; block++)
|
|
SetBlockUseEntry(block, false);
|
|
|
|
dierr = SaveVolBitmap();
|
|
FreeVolBitmap();
|
|
if (dierr != kDIErrNone)
|
|
return dierr;
|
|
|
|
return kDIErrNone;
|
|
}
|
|
|
|
/*
|
|
* Get the state of an entry in the block use map.
|
|
*
|
|
* Returns "true" if it's in use, "false" otherwise.
|
|
*/
|
|
bool DiskFSProDOS::GetBlockUseEntry(long block) const
|
|
{
|
|
assert(block >= 0 && block < fTotalBlocks);
|
|
assert(fBlockUseMap != NULL);
|
|
|
|
int offset;
|
|
uint8_t mask;
|
|
|
|
offset = block / 8;
|
|
mask = 0x80 >> (block & 0x07);
|
|
if (fBlockUseMap[offset] & mask)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Change the state of an entry in the block use map.
|
|
*/
|
|
void DiskFSProDOS::SetBlockUseEntry(long block, bool inUse)
|
|
{
|
|
assert(block >= 0 && block < fTotalBlocks);
|
|
assert(fBlockUseMap != NULL);
|
|
|
|
if (block == 0 && !inUse) {
|
|
// shouldn't happen
|
|
assert(false);
|
|
}
|
|
|
|
int offset;
|
|
uint8_t mask;
|
|
|
|
offset = block / 8;
|
|
mask = 0x80 >> (block & 0x07);
|
|
if (!inUse)
|
|
fBlockUseMap[offset] |= mask;
|
|
else
|
|
fBlockUseMap[offset] &= ~mask;
|
|
}
|
|
|
|
/*
|
|
* Check for entries in the block use map past the point where they should be.
|
|
*
|
|
* Returns "true" if bogus entries were found, "false" if all is well.
|
|
*/
|
|
bool DiskFSProDOS::ScanForExtraEntries(void) const
|
|
{
|
|
assert(fBlockUseMap != NULL);
|
|
|
|
int offset, endOffset;
|
|
|
|
/* sloppy: we're not checking for excess bits within last byte */
|
|
offset = (fTotalBlocks / 8) +1;
|
|
endOffset = GetNumBitmapBlocks() * kBlkSize;
|
|
|
|
while (offset < endOffset) {
|
|
if (fBlockUseMap[offset] != 0) {
|
|
LOGI(" ProDOS found bogus bitmap junk 0x%02x at offset=%d",
|
|
fBlockUseMap[offset], offset);
|
|
return true;
|
|
}
|
|
offset++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Allocate a new block on a ProDOS volume.
|
|
*
|
|
* Only touches the in-memory copy.
|
|
*
|
|
* Returns the block number (0-65535) on success or -1 on failure.
|
|
*/
|
|
long DiskFSProDOS::AllocBlock(void)
|
|
{
|
|
assert(fBlockUseMap != NULL);
|
|
|
|
#if 0 // whoa... this is REALLY slow
|
|
/*
|
|
* Run through the entire set of blocks until we find one that's not
|
|
* allocated. We could probably make this faster by scanning bytes and
|
|
* then shifting bits, but this is easier and fast enough.
|
|
*
|
|
* We don't scan block 0 because (a) it should never be available and
|
|
* (b) it has a special meaning in some circumstances. We could probably
|
|
* start at kVolHeaderBlock+kVolHeaderNumBlocks.
|
|
*/
|
|
long block;
|
|
for (block = kVolHeaderBlock; block < fTotalBlocks; block++) {
|
|
if (!GetBlockUseEntry(block)) {
|
|
SetBlockUseEntry(block, true);
|
|
return block;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int offset;
|
|
int maxOffset = (fTotalBlocks + 7) / 8;
|
|
|
|
for (offset = 0; offset < maxOffset; offset++) {
|
|
if (fBlockUseMap[offset] != 0) {
|
|
/* got one, figure out which */
|
|
int subBlock = 0;
|
|
uint8_t uch = fBlockUseMap[offset];
|
|
while ((uch & 0x80) == 0) {
|
|
subBlock++;
|
|
uch <<= 1;
|
|
}
|
|
|
|
long block = offset * 8 + subBlock;
|
|
assert(!GetBlockUseEntry(block));
|
|
SetBlockUseEntry(block, true);
|
|
if (block == 0 || block == 1) {
|
|
LOGI("PRODOS: GLITCH: rejecting alloc of block 0");
|
|
continue;
|
|
}
|
|
return block;
|
|
}
|
|
}
|
|
|
|
LOGI("ProDOS: NOTE: AllocBlock just failed!");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Tally up the number of free blocks.
|
|
*/
|
|
DIError DiskFSProDOS::GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits,
|
|
int* pUnitSize) const
|
|
{
|
|
DIError dierr;
|
|
long block, freeBlocks;
|
|
freeBlocks = 0;
|
|
|
|
dierr = const_cast<DiskFSProDOS*>(this)->LoadVolBitmap();
|
|
if (dierr != kDIErrNone)
|
|
return dierr;
|
|
|
|
for (block = 0; block < fTotalBlocks; block++) {
|
|
if (!GetBlockUseEntry(block))
|
|
freeBlocks++;
|
|
}
|
|
|
|
*pTotalUnits = fTotalBlocks;
|
|
*pFreeUnits = freeBlocks;
|
|
*pUnitSize = kBlockSize;
|
|
|
|
const_cast<DiskFSProDOS*>(this)->FreeVolBitmap();
|
|
return kDIErrNone;
|
|
}
|
|
|
|
/*
|
|
* Update an entry in the VolumeUsage map.
|
|
*
|
|
* The VolumeUsage map spans the range of blocks
|
|
*/
|
|
void DiskFSProDOS::SetBlockUsage(long block, VolumeUsage::ChunkPurpose purpose)
|
|
{
|
|
VolumeUsage::ChunkState cstate;
|
|
|
|
fVolumeUsage.GetChunkState(block, &cstate);
|
|
if (cstate.isUsed) {
|
|
cstate.purpose = VolumeUsage::kChunkPurposeConflict;
|
|
LOGI(" ProDOS conflicting uses for bl=%ld", block);
|
|
} else {
|
|
cstate.isUsed = true;
|
|
cstate.purpose = purpose;
|
|
}
|
|
fVolumeUsage.SetChunkState(block, &cstate);
|
|
}
|
|
|
|
/*
|
|
* Pass in the number of the first block of the directory.
|
|
*
|
|
* Start with "pParent" set to the magic entry for the volume dir.
|
|
*/
|
|
DIError DiskFSProDOS::RecursiveDirAdd(A2File* pParent, uint16_t dirBlock,
|
|
const char* basePath, int depth)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
DirHeader header;
|
|
uint8_t blkBuf[kBlkSize];
|
|
int numEntries, iterations, foundCount;
|
|
bool first;
|
|
|
|
/* if we get too deep, assume it's a loop */
|
|
if (depth > kMaxDirectoryDepth) {
|
|
dierr = kDIErrDirectoryLoop;
|
|
goto bail;
|
|
}
|
|
|
|
|
|
if (dirBlock < kVolHeaderBlock || dirBlock >= fpImg->GetNumBlocks()) {
|
|
LOGI(" ProDOS ERROR: directory block %u out of range", dirBlock);
|
|
dierr = kDIErrInvalidBlock;
|
|
goto bail;
|
|
}
|
|
|
|
numEntries = 1;
|
|
iterations = 0;
|
|
foundCount = 0;
|
|
first = true;
|
|
|
|
while (dirBlock && iterations < kMaxCatalogIterations) {
|
|
dierr = fpImg->ReadBlock(dirBlock, blkBuf);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
if (pParent->IsVolumeDirectory())
|
|
SetBlockUsage(dirBlock, VolumeUsage::kChunkPurposeVolumeDir);
|
|
else
|
|
SetBlockUsage(dirBlock, VolumeUsage::kChunkPurposeSubdir);
|
|
|
|
if (first) {
|
|
/* this is the directory header entry */
|
|
dierr = GetDirHeader(blkBuf, &header);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
numEntries = header.fileCount;
|
|
//LOGI(" ProDOS got dir header numEntries = %d", numEntries);
|
|
}
|
|
|
|
/* slurp the entries out of this block */
|
|
dierr = SlurpEntries(pParent, &header, blkBuf, first, &foundCount,
|
|
basePath, dirBlock, depth);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
dirBlock = GetShortLE(&blkBuf[0x02]);
|
|
if (dirBlock != 0 &&
|
|
(dirBlock < 2 || dirBlock >= fpImg->GetNumBlocks()))
|
|
{
|
|
LOGI(" ProDOS ERROR: invalid dir link block %u in base='%s'",
|
|
dirBlock, basePath);
|
|
dierr = kDIErrInvalidBlock;
|
|
goto bail;
|
|
}
|
|
first = false;
|
|
iterations++;
|
|
}
|
|
if (iterations == kMaxCatalogIterations) {
|
|
LOGI(" ProDOS subdir iteration count exceeded");
|
|
dierr = kDIErrDirectoryLoop;
|
|
goto bail;
|
|
}
|
|
if (foundCount != numEntries) {
|
|
/* not significant; just means somebody isn't updating correctly */
|
|
LOGI(" ProDOS WARNING: numEntries=%d foundCount=%d in base='%s'",
|
|
numEntries, foundCount, basePath);
|
|
}
|
|
|
|
bail:
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Slurp the entries out of a single ProDOS directory block.
|
|
*
|
|
* Recursively calls RecursiveDirAdd for directories.
|
|
*
|
|
* "*pFound" is increased by the number of valid entries found in this block.
|
|
*/
|
|
DIError DiskFSProDOS::SlurpEntries(A2File* pParent, const DirHeader* pHeader,
|
|
const uint8_t* blkBuf, bool skipFirst, int* pCount,
|
|
const char* basePath, uint16_t thisBlock, int depth)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
int entriesThisBlock = pHeader->entriesPerBlock;
|
|
const uint8_t* entryBuf;
|
|
A2FileProDOS* pFile;
|
|
|
|
int idx = 0;
|
|
entryBuf = &blkBuf[0x04];
|
|
if (skipFirst) {
|
|
entriesThisBlock--;
|
|
entryBuf += pHeader->entryLength;
|
|
idx++;
|
|
}
|
|
|
|
for ( ; entriesThisBlock > 0 ;
|
|
entriesThisBlock--, idx++, entryBuf += pHeader->entryLength)
|
|
{
|
|
if (entryBuf >= blkBuf + kBlkSize) {
|
|
LOGI(" ProDOS whoops, just walked out of dirent buffer");
|
|
return kDIErrBadDirectory;
|
|
}
|
|
|
|
if ((entryBuf[0x00] & 0xf0) == A2FileProDOS::kStorageDeleted) {
|
|
/* skip deleted entries */
|
|
continue;
|
|
}
|
|
|
|
pFile = new A2FileProDOS(this);
|
|
if (pFile == NULL) {
|
|
dierr = kDIErrMalloc;
|
|
goto bail;
|
|
}
|
|
|
|
A2FileProDOS::DirEntry* pEntry;
|
|
pEntry = &pFile->fDirEntry;
|
|
A2FileProDOS::InitDirEntry(pEntry, entryBuf);
|
|
|
|
pFile->SetParent(pParent);
|
|
pFile->fParentDirBlock = thisBlock;
|
|
pFile->fParentDirIdx = idx;
|
|
|
|
pFile->SetPathName(basePath, pEntry->fileName);
|
|
|
|
if (pEntry->keyPointer <= kVolHeaderBlock) {
|
|
LOGI("ProDOS invalid key pointer %d on '%s'",
|
|
pEntry->keyPointer, pFile->GetPathName());
|
|
pFile->SetQuality(A2File::kQualityDamaged);
|
|
} else
|
|
if (pEntry->storageType == A2FileProDOS::kStorageExtended) {
|
|
dierr = ReadExtendedInfo(pFile);
|
|
if (dierr != kDIErrNone) {
|
|
pFile->SetQuality(A2File::kQualityDamaged);
|
|
dierr = kDIErrNone;
|
|
}
|
|
}
|
|
|
|
//pFile->Dump();
|
|
AddFileToList(pFile);
|
|
(*pCount)++;
|
|
|
|
if (!fpImg->UpdateScanProgress(NULL)) {
|
|
LOGI(" ProDOS cancelled by user");
|
|
dierr = kDIErrCancelled;
|
|
goto bail;
|
|
}
|
|
|
|
if (pEntry->storageType == A2FileProDOS::kStorageDirectory) {
|
|
// don't need to check for kStorageVolumeDirHeader here
|
|
dierr = RecursiveDirAdd(pFile, pEntry->keyPointer,
|
|
pFile->GetPathName(), depth+1);
|
|
if (dierr != kDIErrNone) {
|
|
if (dierr == kDIErrCancelled)
|
|
goto bail;
|
|
|
|
/* mark subdir as damaged and keep going */
|
|
pFile->SetQuality(A2File::kQualityDamaged);
|
|
dierr = kDIErrNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
bail:
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Pull the directory header out of the first block of a directory.
|
|
*/
|
|
DIError DiskFSProDOS::GetDirHeader(const uint8_t* blkBuf, DirHeader* pHeader)
|
|
{
|
|
int nameLen;
|
|
|
|
pHeader->storageType = (blkBuf[0x04] & 0xf0) >> 4;
|
|
if (pHeader->storageType != A2FileProDOS::kStorageSubdirHeader &&
|
|
pHeader->storageType != A2FileProDOS::kStorageVolumeDirHeader)
|
|
{
|
|
LOGI(" ProDOS WARNING: subdir header has wrong storage type (%d)",
|
|
pHeader->storageType);
|
|
/* keep going... might be bad idea */
|
|
}
|
|
nameLen = blkBuf[0x04] & 0x0f;
|
|
memcpy(pHeader->dirName, &blkBuf[0x05], nameLen);
|
|
pHeader->dirName[nameLen] = '\0';
|
|
pHeader->createWhen = GetLongLE(&blkBuf[0x1c]);
|
|
pHeader->version = blkBuf[0x20];
|
|
pHeader->minVersion = blkBuf[0x21];
|
|
pHeader->access = blkBuf[0x22];
|
|
pHeader->entryLength = blkBuf[0x23];
|
|
pHeader->entriesPerBlock = blkBuf[0x24];
|
|
pHeader->fileCount = GetShortLE(&blkBuf[0x25]);
|
|
pHeader->parentPointer = GetShortLE(&blkBuf[0x27]);
|
|
pHeader->parentEntry = blkBuf[0x29];
|
|
pHeader->parentEntryLength = blkBuf[0x2a];
|
|
|
|
if (pHeader->entryLength * pHeader->entriesPerBlock > kBlkSize ||
|
|
pHeader->entryLength * pHeader->entriesPerBlock == 0)
|
|
{
|
|
LOGI(" ProDOS invalid subdir header: entryLen=%d, entriesPerBlock=%d",
|
|
pHeader->entryLength, pHeader->entriesPerBlock);
|
|
return kDIErrBadDirectory;
|
|
}
|
|
|
|
return kDIErrNone;
|
|
}
|
|
|
|
/*
|
|
* Read the information from the key block of an extended file.
|
|
*
|
|
* There's some "HFS Finder information" stuffed into the key block
|
|
* right after the data fork info, but I'm planning to ignore that.
|
|
*/
|
|
DIError DiskFSProDOS::ReadExtendedInfo(A2FileProDOS* pFile)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
uint8_t blkBuf[kBlkSize];
|
|
|
|
dierr = fpImg->ReadBlock(pFile->fDirEntry.keyPointer, blkBuf);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" ProDOS ReadExtendedInfo: unable to read key block %d",
|
|
pFile->fDirEntry.keyPointer);
|
|
goto bail;
|
|
}
|
|
|
|
pFile->fExtData.storageType = blkBuf[0x0000] & 0x0f;
|
|
pFile->fExtData.keyBlock = GetShortLE(&blkBuf[0x0001]);
|
|
pFile->fExtData.blocksUsed = GetShortLE(&blkBuf[0x0003]);
|
|
pFile->fExtData.eof = GetLongLE(&blkBuf[0x0005]);
|
|
pFile->fExtData.eof &= 0x00ffffff;
|
|
|
|
pFile->fExtRsrc.storageType = blkBuf[0x0100] & 0x0f;
|
|
pFile->fExtRsrc.keyBlock = GetShortLE(&blkBuf[0x0101]);
|
|
pFile->fExtRsrc.blocksUsed = GetShortLE(&blkBuf[0x0103]);
|
|
pFile->fExtRsrc.eof = GetLongLE(&blkBuf[0x0105]);
|
|
pFile->fExtRsrc.eof &= 0x00ffffff;
|
|
|
|
if (pFile->fExtData.keyBlock <= kVolHeaderBlock ||
|
|
pFile->fExtRsrc.keyBlock <= kVolHeaderBlock)
|
|
{
|
|
LOGI(" ProDOS ReadExtendedInfo: found bad extended key blocks %d/%d",
|
|
pFile->fExtData.keyBlock, pFile->fExtRsrc.keyBlock);
|
|
return kDIErrBadFile;
|
|
}
|
|
|
|
bail:
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Scan all of the files on the disk, reading their block usage into the
|
|
* volume usage map. This is important for detecting damage, and makes
|
|
* later accesses easier.
|
|
*
|
|
* As a side-effect, we set the "sparse" length for the file.
|
|
*/
|
|
DIError DiskFSProDOS::ScanFileUsage(void)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
A2FileProDOS* pFile;
|
|
long blockCount, indexCount, sparseCount;
|
|
uint16_t* blockList = NULL;
|
|
uint16_t* indexList = NULL;
|
|
|
|
pFile = (A2FileProDOS*) GetNextFile(NULL);
|
|
while (pFile != NULL) {
|
|
if (!fpImg->UpdateScanProgress(NULL)) {
|
|
LOGI(" ProDOS cancelled by user");
|
|
dierr = kDIErrCancelled;
|
|
goto bail;
|
|
}
|
|
|
|
//pFile->Dump();
|
|
if (pFile->GetQuality() == A2File::kQualityDamaged)
|
|
goto skip;
|
|
|
|
if (pFile->fDirEntry.storageType == A2FileProDOS::kStorageExtended) {
|
|
/* resource fork */
|
|
if (!A2FileProDOS::IsRegularFile(pFile->fExtRsrc.storageType)) {
|
|
/* not expecting to find a directory here, but it happens */
|
|
dierr = kDIErrBadFile;
|
|
} else {
|
|
dierr = pFile->LoadBlockList(pFile->fExtRsrc.storageType,
|
|
pFile->fExtRsrc.keyBlock, pFile->fExtRsrc.eof,
|
|
&blockCount, &blockList, &indexCount, &indexList);
|
|
}
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" ProDOS skipping scan rsrc '%s'",
|
|
pFile->fDirEntry.fileName);
|
|
pFile->SetQuality(A2File::kQualityDamaged);
|
|
goto skip;
|
|
}
|
|
ScanBlockList(blockCount, blockList, indexCount, indexList,
|
|
&sparseCount);
|
|
pFile->fSparseRsrcEof =
|
|
(di_off_t) pFile->fExtRsrc.eof - sparseCount * kBlkSize;
|
|
//LOGI(" SparseCount %d rsrcEof %d '%s'",
|
|
// sparseCount, pFile->fSparseRsrcEof, pFile->fDirEntry.fileName);
|
|
delete[] blockList;
|
|
blockList = NULL;
|
|
delete[] indexList;
|
|
indexList = NULL;
|
|
|
|
/* data fork */
|
|
if (!A2FileProDOS::IsRegularFile(pFile->fExtRsrc.storageType)) {
|
|
dierr = kDIErrBadFile;
|
|
} else {
|
|
dierr = pFile->LoadBlockList(pFile->fExtData.storageType,
|
|
pFile->fExtData.keyBlock, pFile->fExtData.eof,
|
|
&blockCount, &blockList, &indexCount, &indexList);
|
|
}
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" ProDOS skipping scan data '%s'",
|
|
pFile->fDirEntry.fileName);
|
|
pFile->SetQuality(A2File::kQualityDamaged);
|
|
goto skip;
|
|
}
|
|
ScanBlockList(blockCount, blockList, indexCount, indexList,
|
|
&sparseCount);
|
|
pFile->fSparseDataEof =
|
|
(di_off_t) pFile->fExtData.eof - sparseCount * kBlkSize;
|
|
//LOGI(" SparseCount %ld dataEof %ld -> %lld '%s'",
|
|
// sparseCount, pFile->fExtData.eof, pFile->fSparseDataEof,
|
|
// pFile->fDirEntry.fileName);
|
|
delete[] blockList;
|
|
blockList = NULL;
|
|
delete[] indexList;
|
|
indexList = NULL;
|
|
|
|
/* mark the extended key block as in-use */
|
|
SetBlockUsage(pFile->fDirEntry.keyPointer,
|
|
VolumeUsage::kChunkPurposeFileStruct);
|
|
} else if (pFile->fDirEntry.storageType == A2FileProDOS::kStorageDirectory ||
|
|
pFile->fDirEntry.storageType == A2FileProDOS::kStorageVolumeDirHeader)
|
|
{
|
|
/* we already got these during the recursive descent */
|
|
/* (could do them here if we used "fake" directory entry
|
|
for volume dir to lead off the recursion) */
|
|
goto skip;
|
|
} else if (pFile->fDirEntry.storageType == A2FileProDOS::kStorageSeedling ||
|
|
pFile->fDirEntry.storageType == A2FileProDOS::kStorageSapling ||
|
|
pFile->fDirEntry.storageType == A2FileProDOS::kStorageTree)
|
|
{
|
|
/* standard file */
|
|
dierr = pFile->LoadBlockList(pFile->fDirEntry.storageType,
|
|
pFile->fDirEntry.keyPointer, pFile->fDirEntry.eof,
|
|
&blockCount, &blockList, &indexCount, &indexList);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" ProDOS skipping scan '%s'",
|
|
pFile->fDirEntry.fileName);
|
|
pFile->SetQuality(A2File::kQualityDamaged);
|
|
goto skip;
|
|
}
|
|
ScanBlockList(blockCount, blockList, indexCount, indexList,
|
|
&sparseCount);
|
|
pFile->fSparseDataEof =
|
|
(di_off_t) pFile->fDirEntry.eof - sparseCount * kBlkSize;
|
|
//LOGI(" +++ sparseCount=%ld blockCount=%ld sparseDataEof=%lld '%s'",
|
|
// sparseCount, blockCount, pFile->fSparseDataEof,
|
|
// pFile->fDirEntry.fileName);
|
|
|
|
delete[] blockList;
|
|
blockList = NULL;
|
|
delete[] indexList;
|
|
indexList = NULL;
|
|
} else {
|
|
LOGI(" ProDOS found weird storage type %d on '%s', ignoring",
|
|
pFile->fDirEntry.storageType, pFile->fDirEntry.fileName);
|
|
pFile->SetQuality(A2File::kQualityDamaged);
|
|
}
|
|
|
|
/*
|
|
* A completely empty file written as zero blocks (as opposed to simply
|
|
* having its EOF extended, e.g. "sparse seedlings") will have zero data
|
|
* blocks but possibly an EOF that doesn't land on 512 bytes. This can
|
|
* result in a slightly negative "sparse length", which we trim to zero
|
|
* here.
|
|
*/
|
|
//if (stricmp(pFile->fDirEntry.fileName, "EMPTY.SPARSE.R") == 0)
|
|
// LOGI("wahoo");
|
|
if (pFile->fSparseDataEof < 0)
|
|
pFile->fSparseDataEof = 0;
|
|
if (pFile->fSparseRsrcEof < 0)
|
|
pFile->fSparseRsrcEof = 0;
|
|
|
|
skip:
|
|
pFile = (A2FileProDOS*) GetNextFile(pFile);
|
|
}
|
|
|
|
dierr = kDIErrNone;
|
|
|
|
bail:
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Scan a block list into the volume usage map.
|
|
*/
|
|
void DiskFSProDOS::ScanBlockList(long blockCount, uint16_t* blockList,
|
|
long indexCount, uint16_t* indexList, long* pSparseCount)
|
|
{
|
|
assert(blockList != NULL);
|
|
assert(indexCount == 0 || indexList != NULL);
|
|
assert(pSparseCount != NULL);
|
|
|
|
*pSparseCount = 0;
|
|
|
|
int i;
|
|
for (i = 0; i < blockCount; i++) {
|
|
if (blockList[i] != 0) {
|
|
SetBlockUsage(blockList[i], VolumeUsage::kChunkPurposeUserData);
|
|
} else {
|
|
(*pSparseCount)++; // sparse data block
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < indexCount; i++) {
|
|
if (indexList[i] != 0) {
|
|
SetBlockUsage(indexList[i], VolumeUsage::kChunkPurposeFileStruct);
|
|
} // else sparse index block
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ProDOS disks may contain other filesystems. The typical DOS-in-ProDOS
|
|
* strategy involves marking a bunch of blocks at the end of the disc as
|
|
* "in use" without creating a file to go along with them.
|
|
*
|
|
* We look for certain types of embedded volume by looking for disk
|
|
* usage patterns and then testing those with the standard disk testing
|
|
* facilities.
|
|
*/
|
|
DIError DiskFSProDOS::ScanForSubVolumes(void)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
VolumeUsage::ChunkState cstate;
|
|
int firstBlock, matchCount;
|
|
int block;
|
|
|
|
/* this is guaranteed by constraint in volume header read */
|
|
assert(fTotalBlocks <= fpImg->GetNumBlocks());
|
|
|
|
if (fTotalBlocks != 1600) {
|
|
LOGI(" ProDOS ScanForSub: not 800K disk (%ld)",
|
|
fpImg->GetNumBlocks());
|
|
return kDIErrNone; // only scan 800K disks
|
|
}
|
|
|
|
matchCount = 0;
|
|
for (block = fTotalBlocks-1; block >= 0; block--) {
|
|
if (fVolumeUsage.GetChunkState(block, &cstate) != kDIErrNone) {
|
|
assert(false);
|
|
return kDIErrGeneric;
|
|
}
|
|
|
|
if (!cstate.isMarkedUsed || cstate.isUsed)
|
|
break;
|
|
|
|
matchCount++;
|
|
}
|
|
firstBlock = block+1;
|
|
|
|
LOGI("MATCH COUNT %d", matchCount);
|
|
if (matchCount < 35*8) // 280 blocks on 35-track floppy
|
|
return kDIErrNone;
|
|
//if (matchCount % 8 != 0) { // must have 4K tracks
|
|
// LOGI(" ProDOS ScanForSub: matchCount %d odd number",
|
|
// matchCount);
|
|
// return kDIErrNone;
|
|
//}
|
|
|
|
/*
|
|
* Try #1: this is a single DOS 3.3 volume (200K or less).
|
|
*/
|
|
if ((matchCount % 8) == 0 && matchCount <= (50*8)) { // max 50 tracks
|
|
DiskFS* pNewFS = NULL;
|
|
DiskImg* pNewImg = NULL;
|
|
LOGI(" Sub #1: looking for single DOS volume");
|
|
dierr = FindSubVolume(firstBlock, matchCount, &pNewImg, &pNewFS);
|
|
if (dierr == kDIErrNone) {
|
|
AddSubVolumeToList(pNewImg, pNewFS);
|
|
MarkSubVolumeBlocks(firstBlock, matchCount);
|
|
return kDIErrNone;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Try #2: there are multiple 140K DOS 3.3 volumes here.
|
|
*
|
|
* We may want to override their volume numbers, but it looks like
|
|
* DOS Master disks have distinct volume numbers anyway.
|
|
*/
|
|
const int kBlkCount140 = 140*2;
|
|
if ((matchCount % (kBlkCount140)) == 0) {
|
|
int i, count;
|
|
bool found = false;
|
|
|
|
count = matchCount / kBlkCount140;
|
|
LOGI(" Sub #2: looking for %d 140K volumes",
|
|
matchCount / kBlkCount140);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
DiskFS* pNewFS = NULL;
|
|
DiskImg* pNewImg = NULL;
|
|
LOGI(" Sub #2: looking for DOS volume at (%d)",
|
|
firstBlock + i * kBlkCount140);
|
|
dierr = FindSubVolume(firstBlock + i * kBlkCount140,
|
|
kBlkCount140, &pNewImg, &pNewFS);
|
|
if (dierr == kDIErrNone) {
|
|
AddSubVolumeToList(pNewImg, pNewFS);
|
|
MarkSubVolumeBlocks(firstBlock + i * kBlkCount140,
|
|
kBlkCount140);
|
|
found = true;
|
|
}
|
|
}
|
|
if (found)
|
|
return kDIErrNone;
|
|
}
|
|
|
|
/*
|
|
* Try #3: there are five 160K DOS 3.3 volumes here (which works out
|
|
* to exactly 800K). The first DOS volume loses early tracks as
|
|
* needed to accommodate the ProDOS directory and up to 28K of
|
|
* boot files.
|
|
*
|
|
* Because the first 160K volume starts at the front of the disk,
|
|
* we need to restrict this to non-ProDOS sub-volumes, or we'll see
|
|
* a "ghost" volume in the first position. This stuff is going to
|
|
* fail if we test for ProDOS before we check for DOS 3.3.
|
|
*/
|
|
const int kBlkCount160 = 160*2;
|
|
if (matchCount == 1537 || matchCount == 1593) {
|
|
int i, count;
|
|
bool found = false;
|
|
|
|
count = 1600 / kBlkCount160;
|
|
LOGI(" Sub #3: looking for %d 160K volumes",
|
|
matchCount / kBlkCount160);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
DiskFS* pNewFS = NULL;
|
|
DiskImg* pNewImg = NULL;
|
|
LOGI(" Sub #3: looking for DOS volume at (%d)",
|
|
i * kBlkCount160);
|
|
dierr = FindSubVolume(i * kBlkCount160,
|
|
kBlkCount160, &pNewImg, &pNewFS);
|
|
if (dierr == kDIErrNone) {
|
|
if (pNewImg->GetFSFormat() == DiskImg::kFormatDOS33) {
|
|
AddSubVolumeToList(pNewImg, pNewFS);
|
|
if (i == 0)
|
|
MarkSubVolumeBlocks(firstBlock, kBlkCount160 - firstBlock);
|
|
else
|
|
MarkSubVolumeBlocks(i * kBlkCount160, kBlkCount160);
|
|
} else {
|
|
delete pNewFS;
|
|
delete pNewImg;
|
|
pNewFS = NULL;
|
|
pNewImg = NULL;
|
|
}
|
|
}
|
|
}
|
|
if (found)
|
|
return kDIErrNone;
|
|
}
|
|
|
|
return kDIErrNone;
|
|
}
|
|
|
|
/*
|
|
* Look for a sub-volume at the specified location.
|
|
*
|
|
* On success, "*ppDiskImg" and "*ppDiskFS" are newly-allocated objects
|
|
* of the appropriate kind.
|
|
*/
|
|
DIError DiskFSProDOS::FindSubVolume(long blockStart, long blockCount,
|
|
DiskImg** ppDiskImg, DiskFS** ppDiskFS)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
DiskFS* pNewFS = NULL;
|
|
DiskImg* pNewImg = NULL;
|
|
|
|
pNewImg = new DiskImg;
|
|
if (pNewImg == NULL) {
|
|
dierr = kDIErrMalloc;
|
|
goto bail;
|
|
}
|
|
|
|
dierr = pNewImg->OpenImage(fpImg, blockStart, blockCount);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" Sub: OpenImage(%ld,%ld) failed (err=%d)",
|
|
blockStart, blockCount, dierr);
|
|
goto bail;
|
|
}
|
|
|
|
dierr = pNewImg->AnalyzeImage();
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" Sub: analysis failed (err=%d)", dierr);
|
|
goto bail;
|
|
}
|
|
|
|
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
|
|
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
|
|
{
|
|
LOGI(" Sub: unable to identify filesystem");
|
|
dierr = kDIErrFilesystemNotFound;
|
|
goto bail;
|
|
}
|
|
|
|
/* open a DiskFS for the sub-image */
|
|
LOGI(" Sub DiskImg succeeded, opening DiskFS");
|
|
pNewFS = pNewImg->OpenAppropriateDiskFS();
|
|
if (pNewFS == NULL) {
|
|
LOGI(" Sub: OpenAppropriateDiskFS failed");
|
|
dierr = kDIErrUnsupportedFSFmt;
|
|
goto bail;
|
|
}
|
|
|
|
/* load the files from the sub-image */
|
|
dierr = pNewFS->Initialize(pNewImg, kInitFull);
|
|
if (dierr != kDIErrNone) {
|
|
LOGE(" Sub: error %d reading list of files from disk", dierr);
|
|
goto bail;
|
|
}
|
|
|
|
bail:
|
|
if (dierr != kDIErrNone) {
|
|
delete pNewFS;
|
|
delete pNewImg;
|
|
} else {
|
|
assert(pNewImg != NULL && pNewFS != NULL);
|
|
*ppDiskImg = pNewImg;
|
|
*ppDiskFS = pNewFS;
|
|
}
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Mark the blocks used by a sub-volume as in-use.
|
|
*/
|
|
void DiskFSProDOS::MarkSubVolumeBlocks(long block, long count)
|
|
{
|
|
VolumeUsage::ChunkState cstate;
|
|
|
|
while (count--) {
|
|
if (fVolumeUsage.GetChunkState(block, &cstate) != kDIErrNone) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
assert(cstate.isMarkedUsed && !cstate.isUsed);
|
|
cstate.isUsed = true;
|
|
cstate.purpose = VolumeUsage::kChunkPurposeEmbedded;
|
|
if (fVolumeUsage.SetChunkState(block, &cstate) != kDIErrNone) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
block++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Put a ProDOS filesystem image on the specified DiskImg.
|
|
*/
|
|
DIError DiskFSProDOS::Format(DiskImg* pDiskImg, const char* volName)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
const bool allowLowerCase = (GetParameter(kParmProDOS_AllowLowerCase) != 0);
|
|
uint8_t blkBuf[kBlkSize];
|
|
long formatBlocks;
|
|
|
|
if (!IsValidVolumeName(volName))
|
|
return kDIErrInvalidArg;
|
|
|
|
/* set fpImg so calls that rely on it will work; we un-set it later */
|
|
assert(fpImg == NULL);
|
|
SetDiskImg(pDiskImg);
|
|
|
|
LOGI(" ProDOS formatting disk image");
|
|
|
|
/* write ProDOS blocks */
|
|
dierr = fpImg->OverrideFormat(fpImg->GetPhysicalFormat(),
|
|
DiskImg::kFormatGenericProDOSOrd, fpImg->GetSectorOrder());
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
formatBlocks = pDiskImg->GetNumBlocks();
|
|
if (formatBlocks > 65536) {
|
|
LOGI(" ProDOS: rejecting format req blocks=%ld", formatBlocks);
|
|
assert(false);
|
|
return kDIErrInvalidArg;
|
|
}
|
|
if (formatBlocks == 65536) {
|
|
LOGI(" ProDOS: trimming FS size from 65536 to 65535");
|
|
formatBlocks = 65535;
|
|
}
|
|
|
|
/*
|
|
* We should now zero out the disk blocks, but on a 32MB volume that can
|
|
* take a little while. The blocks are zeroed for us when a disk is
|
|
* created, so this is really only needed if we're re-formatting an
|
|
* existing disk. CiderPress currently doesn't do that, so we're going
|
|
* to skip it here.
|
|
*/
|
|
// dierr = fpImg->ZeroImage();
|
|
LOGI(" ProDOS (not zeroing blocks)");
|
|
|
|
/*
|
|
* Start by writing blocks 0 and 1 (the boot blocks). This is done from
|
|
* a standard boot block image that happens to be essentially the same
|
|
* for all types of disks. (Apparently these blocks are only used when
|
|
* booting 5.25" disks?)
|
|
*/
|
|
dierr = WriteBootBlocks();
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
/*
|
|
* Write the four-block disk volume entry. Start by writing the three
|
|
* empty ones (which only have the prev/next pointers), and finish by
|
|
* writing the first block, which has the volume directory header.
|
|
*/
|
|
int i;
|
|
memset(blkBuf, 0, sizeof(blkBuf));
|
|
for (i = kVolHeaderBlock+1; i < kVolHeaderBlock+kFormatVolDirNumBlocks; i++)
|
|
{
|
|
PutShortLE(&blkBuf[0x00], i-1);
|
|
if (i == kVolHeaderBlock+kFormatVolDirNumBlocks-1)
|
|
PutShortLE(&blkBuf[0x02], 0);
|
|
else
|
|
PutShortLE(&blkBuf[0x02], i+1);
|
|
|
|
dierr = fpImg->WriteBlock(i, blkBuf);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" Format: block %d write failed (err=%d)", i, dierr);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
char upperName[A2FileProDOS::kMaxFileName+1];
|
|
uint16_t lcFlags;
|
|
time_t now;
|
|
|
|
now = time(NULL);
|
|
|
|
/*
|
|
* Compute the lower-case flags, if desired. The test for "allowLowerCase"
|
|
* is probably bogus, because in most cases we just got created by the
|
|
* DiskImg and the app hasn't had time to set the "allow lower" flag.
|
|
* So it defaults to "enabled", which means the app needs to manually
|
|
* change the volume name to lower case.
|
|
*/
|
|
UpperCaseName(upperName, volName);
|
|
lcFlags = 0;
|
|
if (allowLowerCase)
|
|
lcFlags = GenerateLowerCaseBits(upperName, volName, false);
|
|
|
|
PutShortLE(&blkBuf[0x00], 0);
|
|
PutShortLE(&blkBuf[0x02], kVolHeaderBlock+1);
|
|
blkBuf[0x04] = (uint8_t)(strlen(upperName) | (A2FileProDOS::kStorageVolumeDirHeader << 4));
|
|
strncpy((char*) &blkBuf[0x05], upperName, A2FileProDOS::kMaxFileName);
|
|
PutLongLE(&blkBuf[0x16], A2FileProDOS::ConvertProDate(now));
|
|
PutShortLE(&blkBuf[0x1a], lcFlags);
|
|
PutLongLE(&blkBuf[0x1c], A2FileProDOS::ConvertProDate(now));
|
|
blkBuf[0x20] = 0; // GS/OS uses 5?
|
|
/* min_version is zero */
|
|
blkBuf[0x22] = 0xe3; // access (format/rename/backup/write/read)
|
|
blkBuf[0x23] = 0x27; // entry_length: always $27
|
|
blkBuf[0x24] = 0x0d; // entries_per_block: always $0d
|
|
/* file_count is zero - does not include volume dir */
|
|
PutShortLE(&blkBuf[0x27], kVolHeaderBlock + kFormatVolDirNumBlocks); // bit_map_pointer
|
|
PutShortLE(&blkBuf[0x29], (uint16_t) formatBlocks); // total_blocks
|
|
dierr = fpImg->WriteBlock(kVolHeaderBlock, blkBuf);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" Format: block %d write failed (err=%d)",
|
|
kVolHeaderBlock, dierr);
|
|
goto bail;
|
|
}
|
|
|
|
/* check our work, and set some object fields, by reading what we wrote */
|
|
dierr = LoadVolHeader();
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" GLITCH: couldn't read header we just wrote (err=%d)", dierr);
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Generate the initial block usage map. The only entries in use are
|
|
* right at the start of the disk.
|
|
*/
|
|
CreateEmptyBlockMap();
|
|
|
|
/* don't do this -- assume they're going to call Initialize() later */
|
|
//ScanVolBitmap();
|
|
|
|
bail:
|
|
SetDiskImg(NULL); // shouldn't really be set by us
|
|
return dierr;
|
|
}
|
|
|
|
|
|
/*
|
|
* The standard boot block found on ProDOS disks. The same thing appears
|
|
* to be written to both 5.25" and 3.5" disks, with some modifications
|
|
* made for HD images.
|
|
*
|
|
* This is block 0; block 1 is either zeroed out or filled with a repeating
|
|
* pattern.
|
|
*/
|
|
const uint8_t gFloppyBlock0[512] = {
|
|
0x01, 0x38, 0xb0, 0x03, 0x4c, 0x32, 0xa1, 0x86, 0x43, 0xc9, 0x03, 0x08,
|
|
0x8a, 0x29, 0x70, 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x85, 0x49, 0xa0,
|
|
0xff, 0x84, 0x48, 0x28, 0xc8, 0xb1, 0x48, 0xd0, 0x3a, 0xb0, 0x0e, 0xa9,
|
|
0x03, 0x8d, 0x00, 0x08, 0xe6, 0x3d, 0xa5, 0x49, 0x48, 0xa9, 0x5b, 0x48,
|
|
0x60, 0x85, 0x40, 0x85, 0x48, 0xa0, 0x63, 0xb1, 0x48, 0x99, 0x94, 0x09,
|
|
0xc8, 0xc0, 0xeb, 0xd0, 0xf6, 0xa2, 0x06, 0xbc, 0x1d, 0x09, 0xbd, 0x24,
|
|
0x09, 0x99, 0xf2, 0x09, 0xbd, 0x2b, 0x09, 0x9d, 0x7f, 0x0a, 0xca, 0x10,
|
|
0xee, 0xa9, 0x09, 0x85, 0x49, 0xa9, 0x86, 0xa0, 0x00, 0xc9, 0xf9, 0xb0,
|
|
0x2f, 0x85, 0x48, 0x84, 0x60, 0x84, 0x4a, 0x84, 0x4c, 0x84, 0x4e, 0x84,
|
|
0x47, 0xc8, 0x84, 0x42, 0xc8, 0x84, 0x46, 0xa9, 0x0c, 0x85, 0x61, 0x85,
|
|
0x4b, 0x20, 0x12, 0x09, 0xb0, 0x68, 0xe6, 0x61, 0xe6, 0x61, 0xe6, 0x46,
|
|
0xa5, 0x46, 0xc9, 0x06, 0x90, 0xef, 0xad, 0x00, 0x0c, 0x0d, 0x01, 0x0c,
|
|
0xd0, 0x6d, 0xa9, 0x04, 0xd0, 0x02, 0xa5, 0x4a, 0x18, 0x6d, 0x23, 0x0c,
|
|
0xa8, 0x90, 0x0d, 0xe6, 0x4b, 0xa5, 0x4b, 0x4a, 0xb0, 0x06, 0xc9, 0x0a,
|
|
0xf0, 0x55, 0xa0, 0x04, 0x84, 0x4a, 0xad, 0x02, 0x09, 0x29, 0x0f, 0xa8,
|
|
0xb1, 0x4a, 0xd9, 0x02, 0x09, 0xd0, 0xdb, 0x88, 0x10, 0xf6, 0x29, 0xf0,
|
|
0xc9, 0x20, 0xd0, 0x3b, 0xa0, 0x10, 0xb1, 0x4a, 0xc9, 0xff, 0xd0, 0x33,
|
|
0xc8, 0xb1, 0x4a, 0x85, 0x46, 0xc8, 0xb1, 0x4a, 0x85, 0x47, 0xa9, 0x00,
|
|
0x85, 0x4a, 0xa0, 0x1e, 0x84, 0x4b, 0x84, 0x61, 0xc8, 0x84, 0x4d, 0x20,
|
|
0x12, 0x09, 0xb0, 0x17, 0xe6, 0x61, 0xe6, 0x61, 0xa4, 0x4e, 0xe6, 0x4e,
|
|
0xb1, 0x4a, 0x85, 0x46, 0xb1, 0x4c, 0x85, 0x47, 0x11, 0x4a, 0xd0, 0xe7,
|
|
0x4c, 0x00, 0x20, 0x4c, 0x3f, 0x09, 0x26, 0x50, 0x52, 0x4f, 0x44, 0x4f,
|
|
0x53, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xa5, 0x60,
|
|
0x85, 0x44, 0xa5, 0x61, 0x85, 0x45, 0x6c, 0x48, 0x00, 0x08, 0x1e, 0x24,
|
|
0x3f, 0x45, 0x47, 0x76, 0xf4, 0xd7, 0xd1, 0xb6, 0x4b, 0xb4, 0xac, 0xa6,
|
|
0x2b, 0x18, 0x60, 0x4c, 0xbc, 0x09, 0xa9, 0x9f, 0x48, 0xa9, 0xff, 0x48,
|
|
0xa9, 0x01, 0xa2, 0x00, 0x4c, 0x79, 0xf4, 0x20, 0x58, 0xfc, 0xa0, 0x1c,
|
|
0xb9, 0x50, 0x09, 0x99, 0xae, 0x05, 0x88, 0x10, 0xf7, 0x4c, 0x4d, 0x09,
|
|
0xaa, 0xaa, 0xaa, 0xa0, 0xd5, 0xce, 0xc1, 0xc2, 0xcc, 0xc5, 0xa0, 0xd4,
|
|
0xcf, 0xa0, 0xcc, 0xcf, 0xc1, 0xc4, 0xa0, 0xd0, 0xd2, 0xcf, 0xc4, 0xcf,
|
|
0xd3, 0xa0, 0xaa, 0xaa, 0xaa, 0xa5, 0x53, 0x29, 0x03, 0x2a, 0x05, 0x2b,
|
|
0xaa, 0xbd, 0x80, 0xc0, 0xa9, 0x2c, 0xa2, 0x11, 0xca, 0xd0, 0xfd, 0xe9,
|
|
0x01, 0xd0, 0xf7, 0xa6, 0x2b, 0x60, 0xa5, 0x46, 0x29, 0x07, 0xc9, 0x04,
|
|
0x29, 0x03, 0x08, 0x0a, 0x28, 0x2a, 0x85, 0x3d, 0xa5, 0x47, 0x4a, 0xa5,
|
|
0x46, 0x6a, 0x4a, 0x4a, 0x85, 0x41, 0x0a, 0x85, 0x51, 0xa5, 0x45, 0x85,
|
|
0x27, 0xa6, 0x2b, 0xbd, 0x89, 0xc0, 0x20, 0xbc, 0x09, 0xe6, 0x27, 0xe6,
|
|
0x3d, 0xe6, 0x3d, 0xb0, 0x03, 0x20, 0xbc, 0x09, 0xbc, 0x88, 0xc0, 0x60,
|
|
0xa5, 0x40, 0x0a, 0x85, 0x53, 0xa9, 0x00, 0x85, 0x54, 0xa5, 0x53, 0x85,
|
|
0x50, 0x38, 0xe5, 0x51, 0xf0, 0x14, 0xb0, 0x04, 0xe6, 0x53, 0x90, 0x02,
|
|
0xc6, 0x53, 0x38, 0x20, 0x6d, 0x09, 0xa5, 0x50, 0x18, 0x20, 0x6f, 0x09,
|
|
0xd0, 0xe3, 0xa0, 0x7f, 0x84, 0x52, 0x08, 0x28, 0x38, 0xc6, 0x52, 0xf0,
|
|
0xce, 0x18, 0x08, 0x88, 0xf0, 0xf5, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
const uint8_t gHDBlock0[] = {
|
|
0x01, 0x38, 0xb0, 0x03, 0x4c, 0x1c, 0x09, 0x78, 0x86, 0x43, 0xc9, 0x03,
|
|
0x08, 0x8a, 0x29, 0x70, 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x85, 0x49,
|
|
0xa0, 0xff, 0x84, 0x48, 0x28, 0xc8, 0xb1, 0x48, 0xd0, 0x3a, 0xb0, 0x0e,
|
|
0xa9, 0x03, 0x8d, 0x00, 0x08, 0xe6, 0x3d, 0xa5, 0x49, 0x48, 0xa9, 0x5b,
|
|
0x48, 0x60, 0x85, 0x40, 0x85, 0x48, 0xa0, 0x5e, 0xb1, 0x48, 0x99, 0x94,
|
|
0x09, 0xc8, 0xc0, 0xeb, 0xd0, 0xf6, 0xa2, 0x06, 0xbc, 0x32, 0x09, 0xbd,
|
|
0x39, 0x09, 0x99, 0xf2, 0x09, 0xbd, 0x40, 0x09, 0x9d, 0x7f, 0x0a, 0xca,
|
|
0x10, 0xee, 0xa9, 0x09, 0x85, 0x49, 0xa9, 0x86, 0xa0, 0x00, 0xc9, 0xf9,
|
|
0xb0, 0x2f, 0x85, 0x48, 0x84, 0x60, 0x84, 0x4a, 0x84, 0x4c, 0x84, 0x4e,
|
|
0x84, 0x47, 0xc8, 0x84, 0x42, 0xc8, 0x84, 0x46, 0xa9, 0x0c, 0x85, 0x61,
|
|
0x85, 0x4b, 0x20, 0x27, 0x09, 0xb0, 0x66, 0xe6, 0x61, 0xe6, 0x61, 0xe6,
|
|
0x46, 0xa5, 0x46, 0xc9, 0x06, 0x90, 0xef, 0xad, 0x00, 0x0c, 0x0d, 0x01,
|
|
0x0c, 0xd0, 0x52, 0xa9, 0x04, 0xd0, 0x02, 0xa5, 0x4a, 0x18, 0x6d, 0x23,
|
|
0x0c, 0xa8, 0x90, 0x0d, 0xe6, 0x4b, 0xa5, 0x4b, 0x4a, 0xb0, 0x06, 0xc9,
|
|
0x0a, 0xf0, 0x71, 0xa0, 0x04, 0x84, 0x4a, 0xad, 0x20, 0x09, 0x29, 0x0f,
|
|
0xa8, 0xb1, 0x4a, 0xd9, 0x20, 0x09, 0xd0, 0xdb, 0x88, 0x10, 0xf6, 0xa0,
|
|
0x16, 0xb1, 0x4a, 0x4a, 0x6d, 0x1f, 0x09, 0x8d, 0x1f, 0x09, 0xa0, 0x11,
|
|
0xb1, 0x4a, 0x85, 0x46, 0xc8, 0xb1, 0x4a, 0x85, 0x47, 0xa9, 0x00, 0x85,
|
|
0x4a, 0xa0, 0x1e, 0x84, 0x4b, 0x84, 0x61, 0xc8, 0x84, 0x4d, 0x20, 0x27,
|
|
0x09, 0xb0, 0x35, 0xe6, 0x61, 0xe6, 0x61, 0xa4, 0x4e, 0xe6, 0x4e, 0xb1,
|
|
0x4a, 0x85, 0x46, 0xb1, 0x4c, 0x85, 0x47, 0x11, 0x4a, 0xd0, 0x18, 0xa2,
|
|
0x01, 0xa9, 0x00, 0xa8, 0x91, 0x60, 0xc8, 0xd0, 0xfb, 0xe6, 0x61, 0xea,
|
|
0xea, 0xca, 0x10, 0xf4, 0xce, 0x1f, 0x09, 0xf0, 0x07, 0xd0, 0xd8, 0xce,
|
|
0x1f, 0x09, 0xd0, 0xca, 0x58, 0x4c, 0x00, 0x20, 0x4c, 0x47, 0x09, 0x02,
|
|
0x26, 0x50, 0x52, 0x4f, 0x44, 0x4f, 0x53, 0xa5, 0x60, 0x85, 0x44, 0xa5,
|
|
0x61, 0x85, 0x45, 0x6c, 0x48, 0x00, 0x08, 0x1e, 0x24, 0x3f, 0x45, 0x47,
|
|
0x76, 0xf4, 0xd7, 0xd1, 0xb6, 0x4b, 0xb4, 0xac, 0xa6, 0x2b, 0x18, 0x60,
|
|
0x4c, 0xbc, 0x09, 0x20, 0x58, 0xfc, 0xa0, 0x14, 0xb9, 0x58, 0x09, 0x99,
|
|
0xb1, 0x05, 0x88, 0x10, 0xf7, 0x4c, 0x55, 0x09, 0xd5, 0xce, 0xc1, 0xc2,
|
|
0xcc, 0xc5, 0xa0, 0xd4, 0xcf, 0xa0, 0xcc, 0xcf, 0xc1, 0xc4, 0xa0, 0xd0,
|
|
0xd2, 0xcf, 0xc4, 0xcf, 0xd3, 0xa5, 0x53, 0x29, 0x03, 0x2a, 0x05, 0x2b,
|
|
0xaa, 0xbd, 0x80, 0xc0, 0xa9, 0x2c, 0xa2, 0x11, 0xca, 0xd0, 0xfd, 0xe9,
|
|
0x01, 0xd0, 0xf7, 0xa6, 0x2b, 0x60, 0xa5, 0x46, 0x29, 0x07, 0xc9, 0x04,
|
|
0x29, 0x03, 0x08, 0x0a, 0x28, 0x2a, 0x85, 0x3d, 0xa5, 0x47, 0x4a, 0xa5,
|
|
0x46, 0x6a, 0x4a, 0x4a, 0x85, 0x41, 0x0a, 0x85, 0x51, 0xa5, 0x45, 0x85,
|
|
0x27, 0xa6, 0x2b, 0xbd, 0x89, 0xc0, 0x20, 0xbc, 0x09, 0xe6, 0x27, 0xe6,
|
|
0x3d, 0xe6, 0x3d, 0xb0, 0x03, 0x20, 0xbc, 0x09, 0xbc, 0x88, 0xc0, 0x60,
|
|
0xa5, 0x40, 0x0a, 0x85, 0x53, 0xa9, 0x00, 0x85, 0x54, 0xa5, 0x53, 0x85,
|
|
0x50, 0x38, 0xe5, 0x51, 0xf0, 0x14, 0xb0, 0x04, 0xe6, 0x53, 0x90, 0x02,
|
|
0xc6, 0x53, 0x38, 0x20, 0x6d, 0x09, 0xa5, 0x50, 0x18, 0x20, 0x6f, 0x09,
|
|
0xd0, 0xe3, 0xa0, 0x7f, 0x84, 0x52, 0x08, 0x28, 0x38, 0xc6, 0x52, 0xf0,
|
|
0xce, 0x18, 0x08, 0x88, 0xf0, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
/*
|
|
* Write the ProDOS boot blocks onto the disk image.
|
|
*/
|
|
DIError DiskFSProDOS::WriteBootBlocks(void)
|
|
{
|
|
DIError dierr;
|
|
uint8_t block0[512];
|
|
uint8_t block1[512];
|
|
bool isHD;
|
|
|
|
assert(fpImg->GetHasBlocks());
|
|
|
|
if (fpImg->GetNumBlocks() == 280 || fpImg->GetNumBlocks() == 1600)
|
|
isHD = false;
|
|
else
|
|
isHD = true;
|
|
|
|
if (isHD) {
|
|
memcpy(block0, gHDBlock0, sizeof(block0));
|
|
// repeating 0x42 0x48 pattern
|
|
int i;
|
|
uint8_t* ucp;
|
|
for (i = 0, ucp = block1; i < (int)sizeof(block1); i++)
|
|
*ucp++ = 0x42 + 6 * (i & 0x01);
|
|
} else {
|
|
memcpy(block0, gFloppyBlock0, sizeof(block0));
|
|
memset(block1, 0, sizeof(block1));
|
|
}
|
|
|
|
dierr = fpImg->WriteBlock(0, block0);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" WriteBootBlocks: block0 write failed (err=%d)", dierr);
|
|
return dierr;
|
|
}
|
|
dierr = fpImg->WriteBlock(1, block1);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" WriteBootBlocks: block1 write failed (err=%d)", dierr);
|
|
return dierr;
|
|
}
|
|
|
|
return kDIErrNone;
|
|
}
|
|
|
|
/*
|
|
* Create a new, empty file. There are three different kinds of files we
|
|
* need to be able to handle:
|
|
* (1) Standard file. Create the directory entry and an empty "seedling"
|
|
* file with one block allocated. It does not appear that "sparse"
|
|
* allocation applies to seedlings.
|
|
* (2) Extended file. Create the directory entry, the extended key block,
|
|
* and allocate one seedling block for each fork.
|
|
* (3) Subdirectory. Allocate a block for the subdir and fill in the
|
|
* details in the subdir header.
|
|
*
|
|
* In all cases we need to add a new directory entry as well.
|
|
*
|
|
* By not flushing the updated block usage map and the updated directory
|
|
* block(s) until we're done, we can abort our changes at any time if we
|
|
* encounter a damaged sector or run out of disk space. We do need to be
|
|
* careful when updating our internal copies of things like file storage
|
|
* types and lengths, updating them only after everything else has
|
|
* succeeded.
|
|
*
|
|
* NOTE: if we detect an empty directory holder, "*ppNewFile" does NOT
|
|
* end up pointing at a file.
|
|
*
|
|
* NOTE: kParm_CreateUnique does *not* apply to creating subdirectories.
|
|
*/
|
|
DIError DiskFSProDOS::CreateFile(const CreateParms* pParms, A2File** ppNewFile)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
char* normalizedPath = NULL;
|
|
char* basePath = NULL;
|
|
char* fileName = NULL;
|
|
A2FileProDOS* pSubdir = NULL;
|
|
A2FileDescr* pOpenSubdir = NULL;
|
|
A2FileProDOS* pNewFile = NULL;
|
|
uint8_t* subdirBuf = NULL;
|
|
const bool allowLowerCase = (GetParameter(kParmProDOS_AllowLowerCase) != 0);
|
|
const bool createUnique = (GetParameter(kParm_CreateUnique) != 0);
|
|
char upperName[A2FileProDOS::kMaxFileName+1];
|
|
char lowerName[A2FileProDOS::kMaxFileName+1];
|
|
|
|
if (fpImg->GetReadOnly())
|
|
return kDIErrAccessDenied;
|
|
if (!fDiskIsGood)
|
|
return kDIErrBadDiskImage;
|
|
|
|
assert(pParms != NULL);
|
|
assert(pParms->pathName != NULL);
|
|
assert(pParms->storageType == A2FileProDOS::kStorageSeedling ||
|
|
pParms->storageType == A2FileProDOS::kStorageExtended ||
|
|
pParms->storageType == A2FileProDOS::kStorageDirectory);
|
|
// kStorageVolumeDirHeader not allowed -- that's created by Format
|
|
LOGI(" ProDOS ---v--- CreateFile '%s'", pParms->pathName);
|
|
*ppNewFile = NULL;
|
|
|
|
/*
|
|
* Normalize the pathname so that all components are ProDOS-safe
|
|
* and separated by ':'.
|
|
*/
|
|
assert(pParms->pathName != NULL);
|
|
dierr = DoNormalizePath(pParms->pathName, pParms->fssep,
|
|
&normalizedPath);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
assert(normalizedPath != NULL);
|
|
|
|
/*
|
|
* Split the base path and filename apart.
|
|
*/
|
|
char* cp;
|
|
cp = strrchr(normalizedPath, A2FileProDOS::kFssep);
|
|
if (cp == NULL) {
|
|
assert(basePath == NULL);
|
|
fileName = normalizedPath;
|
|
} else {
|
|
fileName = new char[strlen(cp+1) +1];
|
|
strcpy(fileName, cp+1);
|
|
*cp = '\0';
|
|
basePath = normalizedPath;
|
|
}
|
|
normalizedPath = NULL; // either fileName or basePath points here now
|
|
|
|
assert(fileName != NULL);
|
|
//LOGI(" ProDOS normalized to '%s':'%s'",
|
|
// basePath == NULL ? "" : basePath, fileName);
|
|
|
|
/*
|
|
* Open the base path. If it doesn't exist, create it recursively.
|
|
*/
|
|
if (basePath != NULL) {
|
|
LOGI(" ProDOS Creating '%s' in '%s'", fileName, basePath);
|
|
/* open the named subdir, creating it if it doesn't exist */
|
|
pSubdir = (A2FileProDOS*)GetFileByName(basePath);
|
|
if (pSubdir == NULL) {
|
|
LOGI(" ProDOS Creating subdir '%s'", basePath);
|
|
A2File* pNewSub;
|
|
CreateParms newDirParms;
|
|
newDirParms.pathName = basePath;
|
|
newDirParms.fssep = A2FileProDOS::kFssep;
|
|
newDirParms.storageType = A2FileProDOS::kStorageDirectory;
|
|
newDirParms.fileType = kTypeDIR; // 0x0f
|
|
newDirParms.auxType = 0;
|
|
newDirParms.access = 0xe3; // unlocked, backup bit set
|
|
newDirParms.createWhen = newDirParms.modWhen = time(NULL);
|
|
dierr = this->CreateFile(&newDirParms, &pNewSub);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
assert(pNewSub != NULL);
|
|
|
|
pSubdir = (A2FileProDOS*) pNewSub;
|
|
}
|
|
|
|
/*
|
|
* And now the annoying part. We need to reconstruct basePath out
|
|
* of the filenames actually present, rather than relying on the
|
|
* argument passed in. That's because some directories might have
|
|
* lower-case flags and some might not, and we do case-insensitive
|
|
* comparisons. It's not crucial for our inner workings, but the
|
|
* linear file list in the DiskFS should have accurate strings.
|
|
* (It'll work just fine, but the display might show the wrong values
|
|
* for parent directories until they reload the disk.)
|
|
*
|
|
* On the bright side, we know exactly how long the string needs
|
|
* to be, so we can just stomp on it in place. Assuming, of course,
|
|
* that the filename created matches up with what the filename
|
|
* normalizer came up with, which we can guarantee since (a) everybody
|
|
* uses the same normalizer and (b) the "uniqueify" stuff doesn't
|
|
* kick in for subdirs because we wouldn't be creating a new subdir
|
|
* if it didn't already exist.
|
|
*
|
|
* This is essentially the same as RegeneratePathName(), but that's
|
|
* meant for a situation where the filename already exists.
|
|
*/
|
|
A2FileProDOS* pBaseDir = pSubdir;
|
|
int basePathLen = strlen(basePath);
|
|
while (!pBaseDir->IsVolumeDirectory()) {
|
|
const char* fixedName = pBaseDir->GetFileName();
|
|
int fixedLen = strlen(fixedName);
|
|
if (fixedLen > basePathLen) {
|
|
assert(false);
|
|
break;
|
|
}
|
|
assert(basePathLen == fixedLen ||
|
|
*(basePath + (basePathLen-fixedLen-1)) == kDIFssep);
|
|
memcpy(basePath + (basePathLen-fixedLen), fixedName, fixedLen);
|
|
basePathLen -= fixedLen+1;
|
|
|
|
pBaseDir = (A2FileProDOS*) pBaseDir->GetParent();
|
|
assert(pBaseDir != NULL);
|
|
}
|
|
// check the math
|
|
if (pSubdir->IsVolumeDirectory())
|
|
assert(basePathLen == 0);
|
|
else
|
|
assert(basePathLen == -1);
|
|
} else {
|
|
/* open the volume directory */
|
|
LOGI(" ProDOS Creating '%s' in volume dir", fileName);
|
|
/* volume dir must be first in the list */
|
|
pSubdir = (A2FileProDOS*) GetNextFile(NULL);
|
|
assert(pSubdir != NULL);
|
|
assert(pSubdir->IsVolumeDirectory());
|
|
}
|
|
if (pSubdir == NULL) {
|
|
LOGI(" ProDOS Unable to open subdir '%s'", basePath);
|
|
dierr = kDIErrFileNotFound;
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Load the block usage map into memory. All changes, to the end of this
|
|
* function, are made to the in-memory copy and can be "undone" by simply
|
|
* throwing the temporary map away.
|
|
*/
|
|
dierr = LoadVolBitmap();
|
|
if (dierr != kDIErrNone)
|
|
return dierr;
|
|
|
|
/*
|
|
* Load the subdir or volume dir into memory, and alloc a new directory
|
|
* entry.
|
|
*/
|
|
dierr = pSubdir->Open(&pOpenSubdir, false);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
uint8_t* dirEntryPtr;
|
|
long dirLen;
|
|
uint16_t dirBlock, dirKeyBlock;
|
|
int dirEntrySlot;
|
|
dierr = AllocDirEntry(pOpenSubdir, &subdirBuf, &dirLen, &dirEntryPtr,
|
|
&dirKeyBlock, &dirEntrySlot, &dirBlock);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
assert(subdirBuf != NULL);
|
|
assert(dirLen > 0);
|
|
assert(dirKeyBlock > 0);
|
|
assert(dirEntrySlot >= 0);
|
|
assert(dirBlock > 0);
|
|
|
|
/*
|
|
* Create a copy of the filename with everything in upper case and spaces
|
|
* changed to periods.
|
|
*/
|
|
UpperCaseName(upperName, fileName);
|
|
|
|
/*
|
|
* Make the name unique within the current directory. This requires
|
|
* appending digits until the name doesn't match any others.
|
|
*
|
|
* The filename buffer ("upperName") must be able to hold kMaxFileName+1
|
|
* chars. It will be modified in place.
|
|
*/
|
|
if (createUnique &&
|
|
pParms->storageType != A2FileProDOS::kStorageDirectory)
|
|
{
|
|
MakeFileNameUnique(subdirBuf, dirLen, upperName);
|
|
} else {
|
|
/* check to see if it already exists */
|
|
if (NameExistsInDir(subdirBuf, dirLen, upperName)) {
|
|
if (pParms->storageType == A2FileProDOS::kStorageDirectory)
|
|
dierr = kDIErrDirectoryExists;
|
|
else
|
|
dierr = kDIErrFileExists;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allocate file storage and initialize:
|
|
* - For directory, a single block with the directory header.
|
|
* - For seedling, an empty block.
|
|
* - For extended, an extended key block entry and two empty blocks.
|
|
*/
|
|
long keyBlock;
|
|
int blocksUsed;
|
|
int newEOF;
|
|
keyBlock = -1;
|
|
blocksUsed = newEOF = -1;
|
|
|
|
dierr = AllocInitialFileStorage(pParms, upperName, dirBlock,
|
|
dirEntrySlot, &keyBlock, &blocksUsed, &newEOF);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
assert(blocksUsed > 0);
|
|
assert(keyBlock > 0);
|
|
assert(newEOF >= 0);
|
|
|
|
/*
|
|
* Fill out the newly-created directory entry pointed to by "dirEntryPtr".
|
|
*
|
|
* ProDOS filenames are always stored in upper case. ProDOS 8 v1.8 and
|
|
* later allow lower-case names with '.' converting to ' '. We optionally
|
|
* set the flags here, using the original file name to decide which parts
|
|
* are lower case. (Some parts of the original may have been stomped
|
|
* when the name was made unique, so we need to watch for that.)
|
|
*/
|
|
dirEntryPtr[0x00] = (uint8_t)((pParms->storageType << 4) | strlen(upperName));
|
|
strncpy((char*) &dirEntryPtr[0x01], upperName, A2FileProDOS::kMaxFileName);
|
|
if (pParms->fileType >= 0 && pParms->fileType <= 0xff)
|
|
dirEntryPtr[0x10] = (uint8_t) pParms->fileType;
|
|
else
|
|
dirEntryPtr[0x10] = 0; // HFS long type?
|
|
PutShortLE(&dirEntryPtr[0x11], (uint16_t) keyBlock);
|
|
PutShortLE(&dirEntryPtr[0x13], blocksUsed);
|
|
PutShortLE(&dirEntryPtr[0x15], newEOF);
|
|
dirEntryPtr[0x17] = 0; // high byte of EOF
|
|
PutLongLE(&dirEntryPtr[0x18], A2FileProDOS::ConvertProDate(pParms->createWhen));
|
|
if (allowLowerCase) {
|
|
uint16_t lcBits;
|
|
lcBits = GenerateLowerCaseBits(upperName, fileName, false);
|
|
GenerateLowerCaseName(upperName, lowerName, lcBits, false);
|
|
lowerName[strlen(upperName)] = '\0';
|
|
|
|
PutShortLE(&dirEntryPtr[0x1c], lcBits);
|
|
} else {
|
|
strcpy(lowerName, upperName);
|
|
PutShortLE(&dirEntryPtr[0x1c], 0); // version, min_version
|
|
}
|
|
dirEntryPtr[0x1e] = pParms->access;
|
|
if (pParms->auxType >= 0 && pParms->auxType <= 0xffff)
|
|
PutShortLE(&dirEntryPtr[0x1f], (uint16_t) pParms->auxType);
|
|
else
|
|
PutShortLE(&dirEntryPtr[0x1f], 0);
|
|
PutLongLE(&dirEntryPtr[0x21], A2FileProDOS::ConvertProDate(pParms->modWhen));
|
|
PutShortLE(&dirEntryPtr[0x25], dirKeyBlock);
|
|
|
|
/*
|
|
* Write updated directory. If this succeeds, we can no longer undo
|
|
* what we have done by simply bailing. If this fails partway through,
|
|
* we might have a corrupted disk, so it's best to ensure that it's not
|
|
* going to fail before we call.
|
|
*
|
|
* Assuming this isn't a nibble image with I/O errors, the only way we
|
|
* can really fail is by running out of disk space. The block has been
|
|
* pre-allocated, so this should always work.
|
|
*/
|
|
dierr = pOpenSubdir->Write(subdirBuf, dirLen);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" ProDOS directory write failed (dirLen=%ld)", dirLen);
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Flush updated block usage map.
|
|
*/
|
|
dierr = SaveVolBitmap();
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
/*
|
|
* Success!
|
|
*
|
|
* Create an A2File entry for this, and add it to the list. The calls
|
|
* below will re-process some of what we just created, which is slightly
|
|
* inefficient but helps guarantee that we aren't creating bogus data
|
|
* structures that won't match what we see when the disk is reloaded.
|
|
*
|
|
* - Regen or update internal VolumeUsage map?? Throw it away or mark
|
|
* it as invalid?
|
|
*/
|
|
pNewFile = new A2FileProDOS(this);
|
|
|
|
A2FileProDOS::DirEntry* pEntry;
|
|
pEntry = &pNewFile->fDirEntry;
|
|
|
|
A2FileProDOS::InitDirEntry(pEntry, dirEntryPtr);
|
|
|
|
pNewFile->fParentDirBlock = dirBlock;
|
|
pNewFile->fParentDirIdx = (dirEntrySlot-1) % kEntriesPerBlock;
|
|
pNewFile->fSparseDataEof = 0;
|
|
pNewFile->fSparseRsrcEof = 0;
|
|
|
|
/*
|
|
* Get the properly-cased filename for the file list. We already have
|
|
* a name in "lowerName", but it doesn't take AppleWorks aux type
|
|
* case stuff into account. If necessary, deal with it now.
|
|
*/
|
|
if (A2FileProDOS::UsesAppleWorksAuxType(pNewFile->fDirEntry.fileType)) {
|
|
DiskFSProDOS::GenerateLowerCaseName(pNewFile->fDirEntry.fileName,
|
|
lowerName, pNewFile->fDirEntry.auxType, true);
|
|
}
|
|
pNewFile->SetPathName(basePath == NULL ? "" : basePath, lowerName);
|
|
|
|
if (pEntry->storageType == A2FileProDOS::kStorageExtended) {
|
|
dierr = ReadExtendedInfo(pNewFile);
|
|
if (dierr != kDIErrNone) {
|
|
LOGI(" ProDOS GLITCH: readback of extended block failed!");
|
|
delete pNewFile;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
pNewFile->SetParent(pSubdir);
|
|
//pNewFile->Dump();
|
|
|
|
/*
|
|
* Because we're hierarchical, and we guarantee that the contents of
|
|
* subdirectories are grouped together, we must insert the file into an
|
|
* appropriate place in the list rather than just throwing it onto the
|
|
* end.
|
|
*
|
|
* The proper location for the new file in the linear list is after the
|
|
* previous file in our subdir. If we're the first item in the subdir,
|
|
* we get added right after the parent. If not, we need to scan, starting
|
|
* from the parent, for an entry in the file list whose key block pointer
|
|
* matches that of the previous item in the list.
|
|
*
|
|
* We wouldn't be this far if the disk were damaged, so we don't have to
|
|
* worry too much about weirdness. The directory entry allocator always
|
|
* returns the first available, so we know the previous entry is valid.
|
|
*/
|
|
uint8_t* prevDirEntryPtr;
|
|
prevDirEntryPtr = GetPrevDirEntry(subdirBuf, dirEntryPtr);
|
|
if (prevDirEntryPtr == NULL) {
|
|
/* previous entry is volume or subdir header */
|
|
InsertFileInList(pNewFile, pNewFile->GetParent());
|
|
LOGI("Inserted '%s' after '%s'",
|
|
pNewFile->GetPathName(), pNewFile->GetParent()->GetPathName());
|
|
} else {
|
|
/* dig out the key block pointer and find the matching file */
|
|
uint16_t prevKeyBlock;
|
|
assert((prevDirEntryPtr[0x00] & 0xf0) != 0); // verify storage type
|
|
prevKeyBlock = GetShortLE(&prevDirEntryPtr[0x11]);
|
|
A2File* pPrev;
|
|
pPrev = FindFileByKeyBlock(pNewFile->GetParent(), prevKeyBlock);
|
|
if (pPrev == NULL) {
|
|
/* should be impossible! */
|
|
assert(false);
|
|
AddFileToList(pNewFile);
|
|
} else {
|
|
/* insert the new file in the list after the previous file */
|
|
InsertFileInList(pNewFile, pPrev);
|
|
}
|
|
}
|
|
// LOGI("LIST NOW:");
|
|
// DumpFileList();
|
|
|
|
*ppNewFile = pNewFile;
|
|
pNewFile = NULL;
|
|
|
|
bail:
|
|
delete pNewFile;
|
|
if (pOpenSubdir != NULL)
|
|
pOpenSubdir->Close(); // writes updated dir entry in parent dir
|
|
FreeVolBitmap();
|
|
delete[] normalizedPath;
|
|
delete[] subdirBuf;
|
|
delete[] fileName;
|
|
delete[] basePath;
|
|
LOGI(" ProDOS ---^--- CreateFile '%s' DONE", pParms->pathName);
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Run through the DiskFS file list, looking for an entry with a matching
|
|
* key block.
|
|
*/
|
|
A2File* DiskFSProDOS::FindFileByKeyBlock(A2File* pStart, uint16_t keyBlock)
|
|
{
|
|
while (pStart != NULL) {
|
|
A2FileProDOS* pPro = (A2FileProDOS*) pStart;
|
|
|
|
if (pPro->fDirEntry.keyPointer == keyBlock)
|
|
return pStart;
|
|
|
|
pStart = GetNextFile(pStart);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Allocate the initial storage (key blocks, directory header) for a new file.
|
|
*
|
|
* Output values are the key block for the new file, the number of blocks
|
|
* used, and an EOF value.
|
|
*
|
|
* "upperName" is the upper-case name for the file. "dirBlock" and
|
|
* "dirEntrySlot" refer to the entry in the higher-level directory for this
|
|
* file, and are only needed when creating a new subdir (because the first
|
|
* entry in a subdir points to its entry in the parent dir).
|
|
*/
|
|
DIError DiskFSProDOS::AllocInitialFileStorage(const CreateParms* pParms,
|
|
const char* upperName, uint16_t dirBlock, int dirEntrySlot,
|
|
long* pKeyBlock, int* pBlocksUsed, int* pNewEOF)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
uint8_t blkBuf[kBlkSize];
|
|
long keyBlock;
|
|
int blocksUsed;
|
|
int newEOF;
|
|
|
|
blocksUsed = -1;
|
|
keyBlock = -1;
|
|
newEOF = 0;
|
|
memset(blkBuf, 0, sizeof(blkBuf));
|
|
|
|
if (pParms->storageType == A2FileProDOS::kStorageSeedling) {
|
|
keyBlock = AllocBlock();
|
|
if (keyBlock == -1) {
|
|
dierr = kDIErrDiskFull;
|
|
goto bail;
|
|
}
|
|
blocksUsed = 1;
|
|
|
|
/* write zeroed block */
|
|
dierr = fpImg->WriteBlock(keyBlock, blkBuf);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
} else if (pParms->storageType == A2FileProDOS::kStorageExtended) {
|
|
long dataBlock, rsrcBlock;
|
|
|
|
dataBlock = AllocBlock();
|
|
rsrcBlock = AllocBlock();
|
|
keyBlock = AllocBlock();
|
|
if (dataBlock < 0 || rsrcBlock < 0 || keyBlock < 0) {
|
|
dierr = kDIErrDiskFull;
|
|
goto bail;
|
|
}
|
|
blocksUsed = 3;
|
|
newEOF = kBlkSize;
|
|
|
|
/* write zeroed block */
|
|
dierr = fpImg->WriteBlock(dataBlock, blkBuf);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
dierr = fpImg->WriteBlock(rsrcBlock, blkBuf);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
/* fill in extended key block details */
|
|
blkBuf[0x00] = blkBuf[0x100] = A2FileProDOS::kStorageSeedling;
|
|
PutShortLE(&blkBuf[0x01], (uint16_t) dataBlock);
|
|
PutShortLE(&blkBuf[0x101], (uint16_t) rsrcBlock);
|
|
blkBuf[0x03] = blkBuf[0x103] = 1; // blocks used (lo byte)
|
|
/* 3 bytes at 0x05 hold EOF, currently 0 */
|
|
|
|
dierr = fpImg->WriteBlock(keyBlock, blkBuf);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
} else if (pParms->storageType == A2FileProDOS::kStorageDirectory) {
|
|
keyBlock = AllocBlock();
|
|
if (keyBlock == -1) {
|
|
dierr = kDIErrDiskFull;
|
|
goto bail;
|
|
}
|
|
blocksUsed = 1;
|
|
newEOF = kBlkSize;
|
|
|
|
/* fill in directory header fields */
|
|
// 0x00: prev, set to zero
|
|
// 0x02: next, set to zero
|
|
blkBuf[0x04] = (uint8_t)((A2FileProDOS::kStorageSubdirHeader << 4) | strlen(upperName));
|
|
strncpy((char*) &blkBuf[0x05], upperName, A2FileProDOS::kMaxFileName);
|
|
blkBuf[0x14] = 0x76; // 0x75 under old P8, 0x76 under GS/OS
|
|
PutLongLE(&blkBuf[0x1c], A2FileProDOS::ConvertProDate(pParms->createWhen));
|
|
blkBuf[0x20] = 5; // 0 under 1.0, 3 under v1.4?, 5 under GS/OS
|
|
blkBuf[0x21] = 0;
|
|
blkBuf[0x22] = pParms->access;
|
|
blkBuf[0x23] = kEntryLength;
|
|
blkBuf[0x24] = kEntriesPerBlock;
|
|
PutShortLE(&blkBuf[0x25], 0); // file count
|
|
PutShortLE(&blkBuf[0x27], dirBlock);
|
|
blkBuf[0x29] = (uint8_t) dirEntrySlot;
|
|
blkBuf[0x2a] = kEntryLength; // the parent dir's entry length
|
|
|
|
dierr = fpImg->WriteBlock(keyBlock, blkBuf);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
} else {
|
|
assert(false);
|
|
dierr = kDIErrInternal;
|
|
goto bail;
|
|
}
|
|
|
|
*pKeyBlock = keyBlock;
|
|
*pBlocksUsed = blocksUsed;
|
|
*pNewEOF = newEOF;
|
|
|
|
bail:
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Scan for damaged files and mysterious or conflicting block usage map
|
|
* entries.
|
|
*
|
|
* Appends some entries to the DiskImg notes, so this should only be run
|
|
* once per DiskFS.
|
|
*
|
|
* This function doesn't set anything; it's effectively "const" except
|
|
* that LoadVolBitmap is inherently non-const.
|
|
*
|
|
* Returns "true" if disk appears to be perfect, "false" otherwise.
|
|
*/
|
|
bool DiskFSProDOS::CheckDiskIsGood(void)
|
|
{
|
|
DIError dierr;
|
|
bool result = true;
|
|
int i;
|
|
|
|
if (fEarlyDamage)
|
|
result = false;
|
|
|
|
dierr = LoadVolBitmap();
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
/*
|
|
* Check the system blocks to see if any of them are marked as free.
|
|
* If so, refuse to write to this disk.
|
|
*/
|
|
if (!GetBlockUseEntry(0) || !GetBlockUseEntry(1)) {
|
|
fpImg->AddNote(DiskImg::kNoteWarning, "Block 0/1 marked as free.");
|
|
result = false;
|
|
}
|
|
for (i = GetNumBitmapBlocks(); i > 0; i--) {
|
|
if (!GetBlockUseEntry(fBitMapPointer + i -1)) {
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"One or more bitmap blocks are marked as free.");
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check for used blocks that aren't marked in-use.
|
|
*
|
|
* This requires that VolumeUsage be accurate. Since this function is
|
|
* only run during initial startup, any later deviation between VU and
|
|
* the block use map is irrelevant.
|
|
*/
|
|
VolumeUsage::ChunkState cstate;
|
|
long blk, notMarked, extraUsed, conflicts;
|
|
notMarked = extraUsed = conflicts = 0;
|
|
for (blk = 0; blk < fVolumeUsage.GetNumChunks(); blk++) {
|
|
dierr = fVolumeUsage.GetChunkState(blk, &cstate);
|
|
if (dierr != kDIErrNone) {
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"Internal volume usage error on blk=%ld.", blk);
|
|
result = false;
|
|
goto bail;
|
|
}
|
|
|
|
if (cstate.isUsed && !cstate.isMarkedUsed)
|
|
notMarked++;
|
|
if (!cstate.isUsed && cstate.isMarkedUsed)
|
|
extraUsed++;
|
|
if (cstate.purpose == VolumeUsage::kChunkPurposeConflict)
|
|
conflicts++;
|
|
}
|
|
if (extraUsed > 0) {
|
|
fpImg->AddNote(DiskImg::kNoteInfo,
|
|
"%ld block%s marked used but not part of any file.",
|
|
extraUsed, extraUsed == 1 ? " is" : "s are");
|
|
// not a problem, really
|
|
}
|
|
if (notMarked > 0) {
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"%ld block%s used by files but not marked used.",
|
|
notMarked, notMarked == 1 ? " is" : "s are");
|
|
result = false; // very bad -- any change could trash files
|
|
}
|
|
if (conflicts > 0) {
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"%ld block%s used by more than one file.",
|
|
conflicts, conflicts == 1 ? " is" : "s are");
|
|
result = false; // kinda bad -- file deletion leads to trouble
|
|
}
|
|
|
|
/*
|
|
* Check for bits set past the end of the actually-needed bits. For
|
|
* some reason P8 and GS/OS both examine these bits, and GS/OS will
|
|
* freak out completely and claim the disk is unrecognizeable ("would
|
|
* you like to format?") if they're set.
|
|
*/
|
|
if (ScanForExtraEntries()) {
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"Blocks past the end of the disk are marked 'in use' in the"
|
|
" volume bitmap.");
|
|
/* don't flunk the disk just for this */
|
|
}
|
|
|
|
/*
|
|
* Scan for "damaged" or "suspicious" files diagnosed earlier.
|
|
*/
|
|
bool damaged, suspicious;
|
|
ScanForDamagedFiles(&damaged, &suspicious);
|
|
|
|
if (damaged) {
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"One or more files are damaged.");
|
|
result = false;
|
|
} else if (suspicious) {
|
|
fpImg->AddNote(DiskImg::kNoteWarning,
|
|
"One or more files look suspicious.");
|
|
result = false;
|
|
}
|
|
|
|
bail:
|
|
FreeVolBitmap();
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Test a string for validity as a ProDOS volume name. Syntax is the same as
|
|
* ProDOS file names, but we also disallow spaces.
|
|
*/
|
|
/*static*/ bool DiskFSProDOS::IsValidVolumeName(const char* name)
|
|
{
|
|
assert((int) A2FileProDOS::kMaxFileName == (int) kMaxVolumeName);
|
|
if (!IsValidFileName(name))
|
|
return false;
|
|
while (*name != '\0') {
|
|
if (*name++ == ' ')
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Test a string for validity as a ProDOS file name. Names may be 1-15
|
|
* characters long, must start with a letter, and may contain letters and
|
|
* digits.
|
|
*
|
|
* Lower case and spaces (a/k/a lower-case '.') are accepted. Trailing
|
|
* spaces are not allowed.
|
|
*/
|
|
/*static*/ bool DiskFSProDOS::IsValidFileName(const char* name)
|
|
{
|
|
if (name == NULL) {
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
/* must be 1-15 characters long */
|
|
if (name[0] == '\0')
|
|
return false;
|
|
if (strlen(name) > A2FileProDOS::kMaxFileName)
|
|
return false;
|
|
|
|
/* must begin with letter; this also catches zero-length filenames */
|
|
if (toupper(name[0]) < 'A' || toupper(name[0]) > 'Z')
|
|
return false;
|
|
|
|
/* no trailing spaces */
|
|
if (name[strlen(name)-1] == ' ')
|
|
return false;
|
|
|
|
/* must be A-Za-z 0-9 '.' ' ' */
|
|
name++;
|
|
while (*name != '\0') {
|
|
if (!( (toupper(*name) >= 'A' && toupper(*name) <= 'Z') ||
|
|
(*name >= '0' && *name <= '9') ||
|
|
(*name == '.') ||
|
|
(*name == ' ')
|
|
))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
name++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Generate lower case flags by comparing "upperName" to "lowerName".
|
|
*
|
|
* It's okay for "lowerName" to be longer than "upperName". The extra chars
|
|
* are just ignored. Similarly, "lowerName" does not need to be
|
|
* null-terminated. "lowerName" does need to point to storage with at least
|
|
* as many valid bytes as "upperName", though, or we could crash.
|
|
*
|
|
* Returns the mask to use in a ProDOS dir. If "forAppleWorks" is set to
|
|
* "true", the mask is modified for use with an AppleWorks aux type.
|
|
*/
|
|
/*static*/ uint16_t DiskFSProDOS::GenerateLowerCaseBits(const char* upperName,
|
|
const char* lowerName, bool forAppleWorks)
|
|
{
|
|
uint16_t caseMask = 0x8000;
|
|
uint16_t caseBit = 0x8000;
|
|
int len, i;
|
|
char lowch;
|
|
|
|
len = strlen(upperName);
|
|
assert(len <= A2FileProDOS::kMaxFileName);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
caseBit >>= 1;
|
|
lowch = A2FileProDOS::NameToLower(upperName[i]);
|
|
if (lowch == lowerName[i])
|
|
caseMask |= caseBit;
|
|
}
|
|
|
|
if (forAppleWorks) {
|
|
uint16_t adjusted;
|
|
caseMask <<= 1;
|
|
adjusted = caseMask << 8 | caseMask >> 8;
|
|
return adjusted;
|
|
} else {
|
|
if (caseMask == 0x8000)
|
|
return 0; // all upper case, don't freak out pre-v1.8
|
|
else
|
|
return caseMask;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Generate the lower-case version of a ProDOS filename, using the supplied
|
|
* lower case flags. "lowerName" must be able to hold 15 chars (enough for
|
|
* a filename or volname).
|
|
*
|
|
* The string will NOT be null-terminated, but the output buffer will be padded
|
|
* with NULs out to the maximum filename len. This makes it suitable for
|
|
* copying directly into directory block buffers.
|
|
*
|
|
* It's okay to pass the same buffer for "upperName" and "lowerName".
|
|
*
|
|
* "lcFlags" is either ProDOS directory flags or AppleWorks aux type flags,
|
|
* depending on the value of "fromAppleWorks".
|
|
*/
|
|
/*static*/ void DiskFSProDOS::GenerateLowerCaseName(const char* upperName,
|
|
char* lowerName, uint16_t lcFlags, bool fromAppleWorks)
|
|
{
|
|
int nameLen = strlen(upperName);
|
|
int bit;
|
|
assert(nameLen <= A2FileProDOS::kMaxFileName);
|
|
|
|
if (fromAppleWorks) {
|
|
/* handle AppleWorks lower-case-in-auxtype */
|
|
uint16_t caseMask = // swap bytes
|
|
(lcFlags << 8) | (lcFlags >> 8);
|
|
for (bit = 0; bit < nameLen ; bit++) {
|
|
if ((caseMask & 0x8000) != 0)
|
|
lowerName[bit] = A2FileProDOS::NameToLower(upperName[bit]);
|
|
else
|
|
lowerName[bit] = upperName[bit];
|
|
caseMask <<= 1;
|
|
}
|
|
for ( ; bit < A2FileProDOS::kMaxFileName; bit++)
|
|
lowerName[bit] = '\0';
|
|
} else {
|
|
/* handle lower-case conversion; see GS/OS tech note #8 */
|
|
if (lcFlags != 0 && !(lcFlags & 0x8000)) {
|
|
// Should be zero or 0x8000 plus other bits; shouldn't be
|
|
// bunch of bits without 0x8000 or 0x8000 by itself. Not
|
|
// really a problem, just unexpected.
|
|
assert(false);
|
|
memcpy(lowerName, upperName, A2FileProDOS::kMaxFileName);
|
|
return;
|
|
}
|
|
for (bit = 0; bit < nameLen; bit++) {
|
|
lcFlags <<= 1;
|
|
if ((lcFlags & 0x8000) != 0)
|
|
lowerName[bit] = A2FileProDOS::NameToLower(upperName[bit]);
|
|
else
|
|
lowerName[bit] = upperName[bit];
|
|
}
|
|
}
|
|
for ( ; bit < A2FileProDOS::kMaxFileName; bit++)
|
|
lowerName[bit] = '\0';
|
|
}
|
|
|
|
/*
|
|
* Normalize a ProDOS path. Invokes DoNormalizePath and handles the buffer
|
|
* management (if the normalized path doesn't fit in "*pNormalizedBufLen"
|
|
* bytes, we set "*pNormalizedBufLen to the required length).
|
|
*
|
|
* This is invoked from the generalized "add" function in CiderPress, which
|
|
* doesn't want to understand the ins and outs of ProDOS pathnames.
|
|
*/
|
|
DIError DiskFSProDOS::NormalizePath(const char* path, char fssep,
|
|
char* normalizedBuf, int* pNormalizedBufLen)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
char* normalizedPath = NULL;
|
|
int len;
|
|
|
|
assert(pNormalizedBufLen != NULL);
|
|
assert(normalizedBuf != NULL || *pNormalizedBufLen == 0);
|
|
|
|
dierr = DoNormalizePath(path, fssep, &normalizedPath);
|
|
if (dierr != kDIErrNone)
|
|
goto bail;
|
|
|
|
assert(normalizedPath != NULL);
|
|
len = strlen(normalizedPath);
|
|
if (normalizedBuf == NULL || *pNormalizedBufLen <= len) {
|
|
/* too short */
|
|
dierr = kDIErrDataOverrun;
|
|
} else {
|
|
/* fits */
|
|
strcpy(normalizedBuf, normalizedPath);
|
|
}
|
|
|
|
*pNormalizedBufLen = len+1; // alloc room for the '\0'
|
|
|
|
bail:
|
|
delete[] normalizedPath;
|
|
return dierr;
|
|
}
|
|
|
|
/*
|
|
* Normalize a ProDOS path. This requires separating each path component
|
|
* out, making it ProDOS-compliant, and then putting it back in.
|
|
* The fssep could be anything, so we need to change it to kFssep.
|
|
*
|
|
* We don't try to identify duplicates here. If more than one subdir maps
|
|
* to the same thing, then you're just going to end up with lots of files
|
|
* in the same subdir. If this is unacceptable then it will have to be
|
|
* fixed at a higher level.
|
|
*
|
|
* Lower-case letters and spaces are left in place. They're expected to
|
|
* be removed later.
|
|
*
|
|
* The caller must delete[] "*pNormalizedPath".
|
|
*/
|
|
DIError DiskFSProDOS::DoNormalizePath(const char* path, char fssep,
|
|
char** pNormalizedPath)
|
|
{
|
|
DIError dierr = kDIErrNone;
|
|
char* workBuf = NULL;
|
|
char* partBuf = NULL;
|
|
char* outputBuf = NULL;
|
|
char* start;
|
|
char* end;
|
|
char* outPtr;
|
|
|
|
assert(path != NULL);
|
|
workBuf = new char[strlen(path)+1];
|
|
partBuf = new char[strlen(path)+1 +1]; // need +1 for prepending letter
|
|
outputBuf = new char[strlen(path) * 2];
|
|
if (workBuf == NULL || partBuf == NULL || outputBuf == NULL) {
|
|
dierr = kDIErrMalloc;
|
|
goto bail;
|
|
}
|
|
|
|
strcpy(workBuf, path);
|
|
outputBuf[0] = '\0';
|
|
|
|
outPtr = outputBuf;
|
|
start = workBuf;
|
|
while (*start != '\0') {
|
|
//char* origStart = start; // need for debug msg
|
|
int partIdx;
|
|
|
|
if (fssep == '\0') {
|
|
end = NULL;
|
|
} else {
|
|
end = strchr(start, fssep);
|
|
if (end != NULL)
|
|
*end = '\0';
|
|
}
|
|
partIdx = 0;
|
|
|
|
/*
|
|
* Skip over everything up to the first letter. If we encounter a
|
|
* number or a '\0' first, insert a leading letter.
|
|
*/
|
|
while (*start != '\0') {
|
|
if (toupper(*start) >= 'A' && toupper(*start) <= 'Z') {
|
|
partBuf[partIdx++] = *start++;
|
|
break;
|
|
}
|
|
if (*start >= '0' && *start <= '9') {
|
|
partBuf[partIdx++] = 'A';
|
|
break;
|
|