/* * CiderPress * Copyright (C) 2009 by CiderPress authors. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Implementation of DiskFSGutenberg and A2FileGutenberg classes. * */ #include "StdAfx.h" #include "DiskImgPriv.h" /* * =========================================================================== * DiskFSGutenberg * =========================================================================== */ const int kMaxSectors = 32; const int kMaxVolNameLen = 9; const int kSctSize = 256; const int kVTOCTrack = 17; const int kVTOCSector = 7; const int kCatalogEntryOffset = 0x20; // first entry in cat sect starts here const int kCatalogEntrySize = 16; // length in bytes of catalog entries const int kCatalogEntriesPerSect = 15; // #of entries per catalog sector const int kEntryDeleted = 0x40; // this is used to designate deleted files const int kEntryUnused = 0x00; // this is track# in never-used entries const int kMaxTSPairs = 0x7a; // 122 entries for 256-byte sectors const int kTSOffset = 0x0c; // first T/S entry in a T/S list const int kMaxTSIterations = 32; /* * Get a pointer to the Nth entry in a catalog sector. */ static inline uint8_t* GetCatalogEntryPtr(uint8_t* basePtr, int entryNum) { assert(entryNum >= 0 && entryNum < kCatalogEntriesPerSect); return basePtr + kCatalogEntryOffset + entryNum * kCatalogEntrySize; } /* * Test this image for Gutenberg-ness. * */ static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder, int* pGoodCount) { DIError dierr = kDIErrNone; uint8_t sctBuf[kSctSize]; // int numTracks, numSectors; int catTrack = kVTOCTrack; int catSect = kVTOCSector; int foundGood = 0; int iterations = 0; *pGoodCount = 0; /* * Walk through the catalog track to try to figure out ordering. */ while (iterations < DiskFSGutenberg::kMaxCatalogSectors) { dierr = pImg->ReadTrackSectorSwapped(catTrack, catSect, sctBuf, imageOrder, DiskImg::kSectorOrderDOS); if (dierr != kDIErrNone) { dierr = kDIErrNone; break; /* allow it if earlier stuff was okay */ } if (catTrack == sctBuf[0] && catSect == sctBuf[1]) { foundGood++; if (sctBuf[0x0f] == 0x8d && sctBuf[0x1f] == 0x8d && sctBuf[0x2f] == 0x8d && sctBuf[0x3f] == 0x8d && sctBuf[0x4f] == 0x8d && sctBuf[0x5f] == 0x8d && sctBuf[0x6f] == 0x8d && sctBuf[0x7f] == 0x8d && sctBuf[0x8f] == 0x8d && sctBuf[0x9f] == 0x8d) foundGood++; } else if (catTrack >0x80) { LOGI(" Gutenberg detected end-of-catalog on cat (%d,%d)", catTrack, catSect); break; } catTrack = sctBuf[0x04]; catSect = sctBuf[0x05]; iterations++; // watch for infinite loops } if (iterations >= DiskFSGutenberg::kMaxCatalogSectors) { /* possible cause: LF->CR conversion screws up link to sector $0a */ dierr = kDIErrDirectoryLoop; LOGI(" Gutenberg directory links cause a loop (order=%d)", imageOrder); goto bail; } LOGI(" Gutenberg foundGood=%d order=%d", foundGood, imageOrder); *pGoodCount = foundGood; bail: return dierr; } /* * Test to see if the image is a Gutenberg word processor data disk. */ /*static*/ DIError DiskFSGutenberg::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, DiskImg::FSFormat* pFormat, FSLeniency leniency) { if (pImg->GetNumTracks() > kMaxInterestingTracks) return kDIErrFilesystemNotFound; DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax]; DiskImg::GetSectorOrderArray(ordering, *pOrder); DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown; int bestCount = 0; for (int i = 0; i < DiskImg::kSectorOrderMax; i++) { int goodCount = 0; if (ordering[i] == DiskImg::kSectorOrderUnknown) continue; if (TestImage(pImg, ordering[i], &goodCount) == kDIErrNone) { if (goodCount > bestCount) { bestCount = goodCount; bestOrder = ordering[i]; } } } if (bestCount >= 2 || (leniency == kLeniencyVery && bestCount >= 1)) { LOGI(" Gutenberg test: bestCount=%d for order=%d", bestCount, bestOrder); assert(bestOrder != DiskImg::kSectorOrderUnknown); *pOrder = bestOrder; *pFormat = DiskImg::kFormatGutenberg; return kDIErrNone; } LOGI(" Gutenberg didn't find a valid filesystem."); 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 is * completely full and has no files on it. */ DIError DiskFSGutenberg::Initialize(InitMode initMode) { DIError dierr = kDIErrNone; fVolumeUsage.Create(fpImg->GetNumTracks(), fpImg->GetNumSectPerTrack()); /* read the contents of the catalog, creating our A2File list */ dierr = ReadCatalog(); if (dierr != kDIErrNone) goto bail; /* run through and get file lengths and data offsets */ dierr = GetFileLengths(); if (dierr != kDIErrNone) goto bail; sprintf(fDiskVolumeID, "Gutenberg: %s", fDiskVolumeName); fDiskIsGood = CheckDiskIsGood(); fVolumeUsage.Dump(); bail: return dierr; } /* * Get the amount of free space remaining. */ DIError DiskFSGutenberg::GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits, int* pUnitSize) const { *pTotalUnits = fpImg->GetNumTracks() * fpImg->GetNumSectPerTrack(); *pFreeUnits = 0; *pUnitSize = kSectorSize; return kDIErrNone; } /* * Read the disk's catalog. */ DIError DiskFSGutenberg::ReadCatalog(void) { DIError dierr = kDIErrNone; uint8_t sctBuf[kSctSize]; int catTrack, catSect; int iterations; catTrack = 17; catSect = 7; iterations = 0; memset(fCatalogSectors, 0, sizeof(fCatalogSectors)); while (catTrack < 35 && catSect < 16 && iterations < kMaxCatalogSectors) { LOGI(" Gutenberg reading catalog sector T=%d S=%d", catTrack, catSect); dierr = fpImg->ReadTrackSector(catTrack, catSect, sctBuf); if (dierr != kDIErrNone) goto bail; memcpy(fDiskVolumeName, &sctBuf[6], kMaxVolNameLen); // Copy out the volume name; it should be the same on all catalog sectors. fDiskVolumeName[kMaxVolNameLen] = 0x00; DiskFSGutenberg::LowerASCII((uint8_t*)fDiskVolumeName, kMaxVolNameLen); A2FileGutenberg::TrimTrailingSpaces(fDiskVolumeName); dierr = ProcessCatalogSector(catTrack, catSect, sctBuf); if (dierr != kDIErrNone) goto bail; fCatalogSectors[iterations].track = catTrack; fCatalogSectors[iterations].sector = catSect; catTrack = sctBuf[0x04]; catSect = sctBuf[0x05]; iterations++; // watch for infinite loops } if (iterations >= kMaxCatalogSectors) { dierr = kDIErrDirectoryLoop; goto bail; } bail: return dierr; } /* * Process the list of files in one sector of the catalog. * * Pass in the track, sector, and the contents of that track and sector. * (We only use "catTrack" and "catSect" to fill out some fields.) */ DIError DiskFSGutenberg::ProcessCatalogSector(int catTrack, int catSect, const uint8_t* sctBuf) { A2FileGutenberg* pFile; const uint8_t* pEntry; int i; pEntry = &sctBuf[kCatalogEntryOffset]; for (i = 0; i < kCatalogEntriesPerSect; i++) { if (pEntry[0x0d] != kEntryDeleted && pEntry[0x00] != 0xa0 && pEntry[0x00] != 0x00) { pFile = new A2FileGutenberg(this); pFile->SetQuality(A2File::kQualityGood); pFile->fTrack = pEntry[0x0c]; pFile->fSector = pEntry[0x0d]; memcpy(pFile->fFileName, &pEntry[0x00], A2FileGutenberg::kMaxFileName); pFile->fFileName[A2FileGutenberg::kMaxFileName] = '\0'; pFile->FixFilename(); //pFile->fCatTS.track = catTrack; //pFile->fCatTS.sector = catSect; pFile->fCatEntryNum = i; /* can't do these yet, so just set to defaults */ pFile->fLength = 0; pFile->fSparseLength = 0; pFile->fDataOffset = 0; pFile->fLengthInSectors = 0; pFile->fLengthInSectors = 0; AddFileToList(pFile); } //if (pEntry[0x00] == 0xa0) // break; pEntry += kCatalogEntrySize; } return kDIErrNone; } /* * Perform consistency checks on the filesystem. * * Returns "true" if disk appears to be perfect, "false" otherwise. */ bool DiskFSGutenberg::CheckDiskIsGood(void) { bool result = true; return result; } /* * Run through our list of files, computing the lengths and marking file * usage in the VolumeUsage object. */ DIError DiskFSGutenberg::GetFileLengths(void) { A2FileGutenberg* pFile; uint8_t sctBuf[kSctSize]; int tsCount = 0; uint16_t currentTrack, currentSector; pFile = (A2FileGutenberg*) GetNextFile(NULL); while (pFile != NULL) { DIError dierr; tsCount = 0; currentTrack = pFile->fTrack; currentSector = pFile->fSector; while (currentTrack < 0x80) { tsCount ++; dierr = fpImg->ReadTrackSector(currentTrack, currentSector, sctBuf); if (dierr != kDIErrNone) { LOGI("Gutenberg failed loading track/sector for '%s'", pFile->GetPathName()); goto bail; } currentTrack = sctBuf[0x04]; currentSector = sctBuf[0x05]; } pFile->fLengthInSectors = tsCount; pFile->fLength = tsCount * 250; // First six bytes of sector are t/s pointers pFile = (A2FileGutenberg*) GetNextFile(pFile); } bail: return kDIErrNone; } /* * Convert high ASCII to low ASCII. * * Some people put inverse and flashing text into filenames, not to mention * control characters, so we have to cope with those too. * * We modify the first "len" bytes of "buf" in place. */ /*static*/ void DiskFSGutenberg::LowerASCII(uint8_t* buf, long len) { while (len--) { if (*buf & 0x80) { if (*buf >= 0xa0) *buf &= 0x7f; else *buf = (*buf & 0x7f) + 0x20; } else *buf = ((*buf & 0x3f) ^ 0x20) + 0x20; buf++; } } /* * =========================================================================== * A2FileGutenberg * =========================================================================== */ /* * Constructor. */ A2FileGutenberg::A2FileGutenberg(DiskFS* pDiskFS) : A2File(pDiskFS) { fTrack = -1; fSector = -1; fLengthInSectors = 0; fLocked = true; fFileName[0] = '\0'; fFileType = kTypeText; fCatTS.track = fCatTS.sector = 0; fCatEntryNum = -1; fAuxType = 0; fDataOffset = 0; fLength = -1; fSparseLength = -1; fpOpenFile = NULL; } /* * Destructor. Make sure an "open" file gets "closed". */ A2FileGutenberg::~A2FileGutenberg(void) { delete fpOpenFile; } /* * Convert the filetype enum to a ProDOS type. * */ long A2FileGutenberg::GetFileType(void) const { return 0x04; // TXT; } /* * "Fix" a filename. Convert DOS-ASCII to normal ASCII, and strip * trailing spaces. */ void A2FileGutenberg::FixFilename(void) { DiskFSGutenberg::LowerASCII((uint8_t*)fFileName, kMaxFileName); TrimTrailingSpaces(fFileName); } /* * Trim the spaces off the end of a filename. * * Assumes the filename has already been converted to low ASCII. */ /*static*/ void A2FileGutenberg::TrimTrailingSpaces(char* filename) { char* lastspc = filename + strlen(filename); assert(*lastspc == '\0'); while (--lastspc) { if (*lastspc != ' ') break; } *(lastspc+1) = '\0'; } /* * Encode a filename into high ASCII, padded out with spaces to * kMaxFileName chars. Lower case is converted to upper case. This * does not filter out control characters or other chunk. * * "buf" must be able to hold kMaxFileName+1 chars. */ /*static*/ void A2FileGutenberg::MakeDOSName(char* buf, const char* name) { for (int i = 0; i < kMaxFileName; i++) { if (*name == '\0') *buf++ = (char) 0xa0; else *buf++ = toupper(*name++) | 0x80; } *buf = '\0'; } /* * Set up state for this file. */ DIError A2FileGutenberg::Open(A2FileDescr** ppOpenFile, bool readOnly, bool rsrcFork /*=false*/) { DIError dierr = kDIErrNone; A2FDGutenberg* pOpenFile = NULL; if (!readOnly) { if (fpDiskFS->GetDiskImg()->GetReadOnly()) return kDIErrAccessDenied; if (fpDiskFS->GetFSDamaged()) return kDIErrBadDiskImage; } if (fpOpenFile != NULL) { dierr = kDIErrAlreadyOpen; goto bail; } if (rsrcFork) return kDIErrForkNotFound; pOpenFile = new A2FDGutenberg(this); pOpenFile->fOffset = 0; pOpenFile->fOpenEOF = fLength; pOpenFile->fOpenSectorsUsed = fLengthInSectors; fpOpenFile = pOpenFile; // add it to our single-member "open file set" *ppOpenFile = pOpenFile; pOpenFile = NULL; bail: delete pOpenFile; return dierr; } /* * Dump the contents of an A2FileGutenberg. */ void A2FileGutenberg::Dump(void) const { LOGI("A2FileGutenberg '%s'", fFileName); LOGI(" TS T=%-2d S=%-2d", fTrack, fSector); LOGI(" Cat T=%-2d S=%-2d", fCatTS.track, fCatTS.sector); LOGI(" type=%d lck=%d slen=%d", fFileType, fLocked, fLengthInSectors); LOGI(" auxtype=0x%04x length=%ld", fAuxType, (long) fLength); } /* * =========================================================================== * A2FDGutenberg * =========================================================================== */ /* * Read data from the current offset. * */ DIError A2FDGutenberg::Read(void* buf, size_t len, size_t* pActual) { LOGD(" Gutenberg reading %lu bytes from '%s' (offset=%ld)", (unsigned long) len, fpFile->GetPathName(), (long) fOffset); A2FileGutenberg* pFile = (A2FileGutenberg*) fpFile; DIError dierr = kDIErrNone; uint8_t sctBuf[kSctSize]; short currentTrack, currentSector; //di_off_t actualOffset = fOffset + pFile->fDataOffset; // adjust for embedded len int bufOffset = 6; size_t thisCount; if (len == 0) return kDIErrNone; assert(fOpenEOF != 0); currentTrack = pFile->fTrack; currentSector = pFile->fSector; /* could be more clever in here and avoid double-buffering */ while (len) { dierr = pFile->GetDiskFS()->GetDiskImg()->ReadTrackSector( currentTrack, currentSector, sctBuf); if (dierr != kDIErrNone) { LOGI(" Gutenberg error reading file '%s'", pFile->GetPathName()); return dierr; } thisCount = kSctSize - bufOffset; if (thisCount > len) thisCount = len; memcpy(buf, sctBuf + bufOffset, thisCount); len -= thisCount; buf = (char*)buf + thisCount; currentTrack = sctBuf[0x04]; currentSector = sctBuf[0x05]; } return dierr; } /* * Writing Gutenberg files isn't supported. */ DIError A2FDGutenberg::Write(const void* buf, size_t len, size_t* pActual) { return kDIErrNotSupported; } /* * Seek to the specified offset. */ DIError A2FDGutenberg::Seek(di_off_t offset, DIWhence whence) { return kDIErrNotSupported; } /* * Return current offset. */ di_off_t A2FDGutenberg::Tell(void) { return kDIErrNotSupported; } /* * Release file state. * * If the file was modified, we need to update the sector usage count in * the catalog track, and possibly a length word in the first sector of * the file (for A/I/B). * * Given the current "write all at once" implementation of Write, we could * have handled the length word back when initially writing the data, but * someday we may fix that and I don't want to have to rewrite this part. * * Most applications don't check the value of "Close", or call it from a * destructor, so we call CloseDescr whether we succeed or not. */ DIError A2FDGutenberg::Close(void) { DIError dierr = kDIErrNone; fpFile->CloseDescr(this); return dierr; } /* * Return the #of sectors/blocks in the file. */ long A2FDGutenberg::GetSectorCount(void) const { return fTSCount; } long A2FDGutenberg::GetBlockCount(void) const { return (fTSCount+1)/2; } /* * Return the Nth track/sector in this file. * * Returns (0,0) for a sparse sector. */ DIError A2FDGutenberg::GetStorage(long sectorIdx, long* pTrack, long* pSector) const { return kDIErrInvalidIndex; } /* * Unimplemented */ DIError A2FDGutenberg::GetStorage(long blockIdx, long* pBlock) const { return kDIErrInvalidIndex; }