mirror of
https://github.com/fadden/ciderpress.git
synced 2024-12-02 10:51:30 +00:00
aa3145856c
Focusing on the diskimg library this time, which deals with a lot of filesystem structures that have specific widths. This is still a bit lax in places, e.g. using "long" for lengths. Should either specify a bit width or use di_off_t. Also, added "override" keyword where appropriate. Also, bumped library version to 5.0.0.
538 lines
13 KiB
C++
538 lines
13 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
/*
|
|
* DiskFS base class.
|
|
*/
|
|
#include "StdAfx.h"
|
|
#include "DiskImgPriv.h"
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* A2File
|
|
* ===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Set the quality level (a/k/a damage level) of a file.
|
|
*
|
|
* Refuse to "improve" the quality level of a file.
|
|
*/
|
|
void A2File::SetQuality(FileQuality quality)
|
|
{
|
|
if (quality == kQualityGood &&
|
|
(fFileQuality == kQualitySuspicious || fFileQuality == kQualityDamaged))
|
|
{
|
|
assert(false);
|
|
return;
|
|
}
|
|
if (quality == kQualitySuspicious && fFileQuality == kQualityDamaged) {
|
|
//assert(false);
|
|
return;
|
|
}
|
|
|
|
fFileQuality = quality;
|
|
}
|
|
|
|
/*
|
|
* Reset the quality level after making repairs.
|
|
*/
|
|
void A2File::ResetQuality(void)
|
|
{
|
|
fFileQuality = kQualityGood;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* DiskFS
|
|
* ===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Set the DiskImg pointer. We add or subtract from the DiskImg's ref count
|
|
* so that it can be sure there are no DiskFS objects left dangling when the
|
|
* DiskImg is deleted.
|
|
*/
|
|
void DiskFS::SetDiskImg(DiskImg* pImg)
|
|
{
|
|
if (pImg == NULL && fpImg == NULL) {
|
|
LOGI("SetDiskImg: no-op (both NULL)");
|
|
return;
|
|
} else if (fpImg == pImg) {
|
|
LOGI("SetDiskImg: no-op (old == new)");
|
|
return;
|
|
}
|
|
|
|
if (fpImg != NULL)
|
|
fpImg->RemoveDiskFS(this);
|
|
if (pImg != NULL)
|
|
pImg->AddDiskFS(this);
|
|
fpImg = pImg;
|
|
}
|
|
|
|
/*
|
|
* Flush changes to disk.
|
|
*/
|
|
DIError DiskFS::Flush(DiskImg::FlushMode mode)
|
|
{
|
|
SubVolume* pSubVol = GetNextSubVolume(NULL);
|
|
DIError dierr;
|
|
|
|
while (pSubVol != NULL) {
|
|
|
|
// quick sanity check
|
|
assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg());
|
|
|
|
dierr = pSubVol->GetDiskFS()->Flush(mode); // recurse
|
|
if (dierr != kDIErrNone)
|
|
return dierr;
|
|
|
|
pSubVol = GetNextSubVolume(pSubVol);
|
|
}
|
|
|
|
assert(fpImg != NULL);
|
|
|
|
return fpImg->FlushImage(mode);
|
|
}
|
|
|
|
/*
|
|
* Set the "read only" flag on our DiskImg and those of our sub-volumes.
|
|
*/
|
|
void DiskFS::SetAllReadOnly(bool val)
|
|
{
|
|
SubVolume* pSubVol = GetNextSubVolume(NULL);
|
|
|
|
/* put current volume in read-only mode */
|
|
if (fpImg != NULL)
|
|
fpImg->SetReadOnly(val);
|
|
|
|
/* handle our kids */
|
|
while (pSubVol != NULL) {
|
|
// quick sanity check
|
|
assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg());
|
|
|
|
//pSubVol->GetDiskImg()->SetReadOnly(val);
|
|
pSubVol->GetDiskFS()->SetAllReadOnly(val); // recurse
|
|
|
|
pSubVol = GetNextSubVolume(pSubVol);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* The file list looks something like this:
|
|
*
|
|
* volume-dir
|
|
* file1
|
|
* file2
|
|
* subdir1
|
|
* subdir1:file1
|
|
* subdir1:file2
|
|
* subdir1:subsub1
|
|
* subdir1:subsub1:file1
|
|
* subdir1:subsub2
|
|
* subdir1:subsub2:file1
|
|
* subdir1:subsub2:file2
|
|
* subdir1:file3
|
|
* file3
|
|
*
|
|
* Everything contained within a subdir comes after the subdir entry and
|
|
* before any entries from later subdirs at the same level.
|
|
*
|
|
* It's unclear whether a linear list or a hierarchical tree structure is
|
|
* the most appropriate way to hold the data. The tree is easier to update,
|
|
* but the linear list corresponds to the primary view in CiderPress, and
|
|
* lists are simpler and easier to manage. For now I'm sticking with a list.
|
|
*
|
|
* The files MUST be in the order in which they came from the disk. This
|
|
* doesn't matter most of the time, but for Pascal volumes it's essential
|
|
* for ensuring that the Write command doesn't run over the next file.
|
|
*/
|
|
|
|
/*
|
|
* Add a file to the end of our list.
|
|
*/
|
|
void DiskFS::AddFileToList(A2File* pFile)
|
|
{
|
|
assert(pFile->GetNext() == NULL);
|
|
|
|
if (fpA2Head == NULL) {
|
|
assert(fpA2Tail == NULL);
|
|
fpA2Head = fpA2Tail = pFile;
|
|
} else {
|
|
pFile->SetPrev(fpA2Tail);
|
|
fpA2Tail->SetNext(pFile);
|
|
fpA2Tail = pFile;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Insert a file into its appropriate place in the list, based on a file
|
|
* hierarchy.
|
|
*
|
|
* Pass in the thing to be added ("pFile") and the previous entry ("pPrev").
|
|
* An empty hierarchic filesystem will have an entry for the volume dir, so
|
|
* we should never have an empty list or a NULL pPrev.
|
|
*
|
|
* The part where things go pear-shaped happens if "pPrev" is a subdirectory.
|
|
* If so, we need to come after all of the subdir's entries, including any
|
|
* entries for sub-subdirs. There's no graceful way to go about this in a
|
|
* linear list.
|
|
*
|
|
* (We'd love to be able to find the *next* entry and then back up one,
|
|
* but odds are that there isn't a "next" entry if we're busily creating
|
|
* files.)
|
|
*/
|
|
void DiskFS::InsertFileInList(A2File* pFile, A2File* pPrev)
|
|
{
|
|
assert(pFile->GetNext() == NULL);
|
|
|
|
if (fpA2Head == NULL) {
|
|
assert(pPrev == NULL);
|
|
fpA2Head = fpA2Tail = pFile;
|
|
return;
|
|
} else if (pPrev == NULL) {
|
|
// create two entries on DOS disk, delete first, add new file
|
|
pFile->SetNext(fpA2Head);
|
|
fpA2Head = pFile;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If we're inserting after the parent (i.e. we're the very first thing
|
|
* in a subdir) or after a plain file, just drop it in.
|
|
*
|
|
* If we're inserting after a subdir, go fish.
|
|
*/
|
|
if (pPrev->IsDirectory() && pFile->GetParent() != pPrev) {
|
|
pPrev = SkipSubdir(pPrev);
|
|
}
|
|
|
|
pFile->SetNext(pPrev->GetNext());
|
|
pPrev->SetNext(pFile);
|
|
}
|
|
|
|
/*
|
|
* Skip over all entries in the subdir we're pointing to.
|
|
*
|
|
* The return value is the very last entry in the subdir.
|
|
*/
|
|
A2File* DiskFS::SkipSubdir(A2File* pSubdir)
|
|
{
|
|
if (pSubdir->GetNext() == NULL)
|
|
return pSubdir; // end of list reached -- subdir is empty
|
|
|
|
A2File* pCur = pSubdir;
|
|
A2File* pNext = NULL;
|
|
|
|
assert(pCur != NULL); // at least one time through the loop
|
|
|
|
while (pCur != NULL) {
|
|
pNext = pCur->GetNext();
|
|
if (pNext == NULL) // end of list reached
|
|
return pCur;
|
|
|
|
if (pNext->GetParent() != pSubdir) // end of dir reached
|
|
return pCur;
|
|
if (pNext->IsDirectory())
|
|
pCur = SkipSubdir(pNext); // get last entry in dir
|
|
else
|
|
pCur = pNext; // advance forward one
|
|
}
|
|
|
|
/* should never get here */
|
|
assert(false);
|
|
return pNext;
|
|
}
|
|
|
|
/*
|
|
* Delete a member from the list.
|
|
*
|
|
* We're currently singly-linked, making this rather expensive.
|
|
*/
|
|
void DiskFS::DeleteFileFromList(A2File* pFile)
|
|
{
|
|
if (fpA2Head == pFile) {
|
|
/* delete the head of the list */
|
|
fpA2Head = fpA2Head->GetNext();
|
|
delete pFile;
|
|
} else {
|
|
A2File* pCur = fpA2Head;
|
|
while (pCur != NULL) {
|
|
if (pCur->GetNext() == pFile) {
|
|
/* found it */
|
|
A2File* pNextNext = pCur->GetNext()->GetNext();
|
|
delete pCur->GetNext();
|
|
pCur->SetNext(pNextNext);
|
|
break;
|
|
}
|
|
pCur = pCur->GetNext();
|
|
}
|
|
|
|
if (pCur == NULL) {
|
|
LOGI("GLITCH: couldn't find element to delete!");
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Access the "next" pointer.
|
|
*
|
|
* Because we apparently can't declare an anonymous class as a friend
|
|
* in MSVC++6.0, this can't be an inline function.
|
|
*/
|
|
A2File* DiskFS::GetNextFile(A2File* pFile) const
|
|
{
|
|
if (pFile == NULL)
|
|
return fpA2Head;
|
|
else
|
|
return pFile->GetNext();
|
|
}
|
|
|
|
/*
|
|
* Return the #of elements in the linear file list.
|
|
*
|
|
* Right now the only code that calls this is the disk info panel in
|
|
* CiderPress, so we don't need it to be efficient.
|
|
*/
|
|
long DiskFS::GetFileCount(void) const
|
|
{
|
|
long count = 0;
|
|
|
|
A2File* pFile = fpA2Head;
|
|
while (pFile != NULL) {
|
|
count++;
|
|
pFile = pFile->GetNext();
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
* Delete all entries in the list.
|
|
*/
|
|
void DiskFS::DeleteFileList(void)
|
|
{
|
|
A2File* pFile;
|
|
A2File* pNext;
|
|
|
|
pFile = fpA2Head;
|
|
while (pFile != NULL) {
|
|
pNext = pFile->GetNext();
|
|
delete pFile;
|
|
pFile = pNext;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Dump file list.
|
|
*/
|
|
void DiskFS::DumpFileList(void)
|
|
{
|
|
A2File* pFile;
|
|
|
|
LOGI("DiskFS file list contents:");
|
|
|
|
pFile = GetNextFile(NULL);
|
|
while (pFile != NULL) {
|
|
LOGI(" %s", pFile->GetPathName());
|
|
pFile = GetNextFile(pFile);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Run through the list of files and find one that matches (case-insensitive).
|
|
*
|
|
* This does not attempt to open files in sub-volumes. We could, but it's
|
|
* likely that the application has "decorated" the name in some fashion,
|
|
* e.g. by prepending the sub-volume's volume name to the filename. May
|
|
* be best to let the application dig for the sub-volume.
|
|
*/
|
|
A2File* DiskFS::GetFileByName(const char* fileName, StringCompareFunc func)
|
|
{
|
|
A2File* pFile;
|
|
|
|
if (func == NULL)
|
|
func = ::strcasecmp;
|
|
|
|
pFile = GetNextFile(NULL);
|
|
while (pFile != NULL) {
|
|
if ((*func)(pFile->GetPathName(), fileName) == 0)
|
|
return pFile;
|
|
|
|
pFile = GetNextFile(pFile);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Add a sub-volume to the end of our list.
|
|
*
|
|
* Copies some parameters from "this" into pDiskFS, such as whether to
|
|
* scan for sub-volumes and the various DiskFS parameters.
|
|
*
|
|
* Note this happens AFTER the disk has been scanned.
|
|
*/
|
|
void DiskFS::AddSubVolumeToList(DiskImg* pDiskImg, DiskFS* pDiskFS)
|
|
{
|
|
SubVolume* pSubVol;
|
|
|
|
/*
|
|
* Check the arguments.
|
|
*/
|
|
if (pDiskImg == NULL || pDiskFS == NULL) {
|
|
LOGI(" DiskFS bogus sub volume ptrs %08lx %08lx",
|
|
(long) pDiskImg, (long) pDiskFS);
|
|
assert(false);
|
|
return;
|
|
}
|
|
if (pDiskImg == fpImg || pDiskFS == this) {
|
|
LOGI(" DiskFS attempt to add self to sub-vol list");
|
|
assert(false);
|
|
return;
|
|
}
|
|
if (pDiskFS->GetDiskImg() == NULL) {
|
|
LOGI(" DiskFS lacks a DiskImg pointer");
|
|
assert(false);
|
|
return;
|
|
}
|
|
pSubVol = fpSubVolumeHead;
|
|
while (pSubVol != NULL) {
|
|
if (pSubVol->GetDiskImg() == pDiskImg ||
|
|
pSubVol->GetDiskFS() == pDiskFS)
|
|
{
|
|
LOGI(" DiskFS multiple adds on diskimg or diskfs");
|
|
assert(false);
|
|
return;
|
|
}
|
|
pSubVol = pSubVol->GetNext();
|
|
}
|
|
|
|
assert(pDiskFS->GetDiskImg() == pDiskImg);
|
|
|
|
/*
|
|
* Looks good. Add it.
|
|
*/
|
|
pSubVol = new SubVolume;
|
|
if (pSubVol == NULL)
|
|
return;
|
|
|
|
pSubVol->Create(pDiskImg, pDiskFS);
|
|
|
|
if (fpSubVolumeHead == NULL) {
|
|
assert(fpSubVolumeTail == NULL);
|
|
fpSubVolumeHead = fpSubVolumeTail = pSubVol;
|
|
} else {
|
|
pSubVol->SetPrev(fpSubVolumeTail);
|
|
fpSubVolumeTail->SetNext(pSubVol);
|
|
fpSubVolumeTail = pSubVol;
|
|
}
|
|
|
|
/* make sure inheritable stuff gets copied */
|
|
CopyInheritables(pDiskFS);
|
|
}
|
|
|
|
/*
|
|
* Copy parameters to a sub-volume.
|
|
*/
|
|
void DiskFS::CopyInheritables(DiskFS* pNewFS)
|
|
{
|
|
for (int i = 0; i < (int) NELEM(fParmTable); i++)
|
|
pNewFS->fParmTable[i] = fParmTable[i];
|
|
|
|
pNewFS->fScanForSubVolumes = fScanForSubVolumes;
|
|
|
|
#if 0
|
|
/* copy scan progress update stuff */
|
|
pNewFS->fpScanProgressCallback = fpScanProgressCallback;
|
|
pNewFS->fpScanProgressCookie = fpScanProgressCookie;
|
|
pNewFS->fpScanCount = -1;
|
|
strcpy(pNewFS->fpScanMsg, "HEY");
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Access the "next" pointer.
|
|
*
|
|
* Because we apparently can't declare an anonymous class as a friend
|
|
* in MSVC++6.0, this can't be an inline function.
|
|
*/
|
|
DiskFS::SubVolume* DiskFS::GetNextSubVolume(const SubVolume* pSubVol) const
|
|
{
|
|
if (pSubVol == NULL)
|
|
return fpSubVolumeHead;
|
|
else
|
|
return pSubVol->GetNext();
|
|
}
|
|
|
|
/*
|
|
* Delete all entries in the list.
|
|
*/
|
|
void DiskFS::DeleteSubVolumeList(void)
|
|
{
|
|
SubVolume* pSubVol;
|
|
SubVolume* pNext;
|
|
|
|
pSubVol = fpSubVolumeHead;
|
|
while (pSubVol != NULL) {
|
|
pNext = pSubVol->GetNext();
|
|
delete pSubVol;
|
|
pSubVol = pNext;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Get a parameter.
|
|
*/
|
|
long DiskFS::GetParameter(DiskFSParameter parm)
|
|
{
|
|
assert(parm > kParmUnknown && parm < kParmMax);
|
|
return fParmTable[parm];
|
|
}
|
|
|
|
/*
|
|
* Set a parameter.
|
|
*
|
|
* The setting propagates to all sub-volumes.
|
|
*/
|
|
void DiskFS::SetParameter(DiskFSParameter parm, long val)
|
|
{
|
|
assert(parm > kParmUnknown && parm < kParmMax);
|
|
fParmTable[parm] = val;
|
|
|
|
SubVolume* pSubVol = GetNextSubVolume(NULL);
|
|
while (pSubVol != NULL) {
|
|
pSubVol->GetDiskFS()->SetParameter(parm, val);
|
|
pSubVol = GetNextSubVolume(pSubVol);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Scan for damaged or suspicious files.
|
|
*/
|
|
void DiskFS::ScanForDamagedFiles(bool* pDamaged, bool* pSuspicious)
|
|
{
|
|
A2File* pFile;
|
|
|
|
*pDamaged = *pSuspicious = false;
|
|
|
|
pFile = GetNextFile(NULL);
|
|
while (pFile != NULL) {
|
|
if (pFile->GetQuality() == A2File::kQualityDamaged)
|
|
*pDamaged = true;
|
|
if (pFile->GetQuality() != A2File::kQualityGood)
|
|
*pSuspicious = true;
|
|
pFile = GetNextFile(pFile);
|
|
}
|
|
}
|