Microsoft-3D-Movie-Maker/kauai/SRC/CHUNK.CPP

4618 lines
113 KiB
C++

/* Copyright (c) Microsoft Corporation.
Licensed under the MIT License. */
/***************************************************************************
Author: ShonK
Project: Kauai
Reviewed:
Copyright (c) Microsoft Corporation
Chunky file classes.
Chunky files store distinct "chunks" of data. Each chunk is identified
by a chunk tag (CTG) and chunk number (CNO) pair. By convention, CTGs
consist of 4 ASCII characters. CTGs that are shorter than 4 characters
should be padded with spaces (eg, 'GST '). The CFL class does not
enforce these conventions, but many Kauai tools assume that CTGs
consist of 4 printable characters.
Chunks store arbitrary data. Chunks can have names. Most importantly,
chunks can have child chunks. A chunk can also be a child of more
than one chunk and can be a child of a chunk mutliple times. The main
restriction on parent-child relationships is that there can be no loops.
Mathematically speaking, the chunks form an acyclic directed graph.
A parent-child relationship has a client specified number associated
with it. This is called the child id (CHID). Child id's don't have to
be unique. CFL places no significance on child id's. The only
restriction concerning CHIDs is that if chunk B is to be a child of
chunk A twice, then those two parent-child relationships must have
different child id's. In a nutshell, a parent-child relationship
(an arrow in the acyclic directed graph) is uniquely identified by
the 5-tuple (ctgPar, cnoPar, ctgChild, cnoChild, chid).
On disk, chunky files are organized as follows:
* A header (CFP struct defined below).
* The raw chunk data arranged in arbitrary order (a heap).
* The index for the chunky file.
* An optional free map indicating which portions of the heap
are currently not being used to store chunk data.
The header stores signatures, version numbers, and pointers to the
index and free map. The heap contains only the raw chunk data.
The index is not updated on disk until FSave is called.
The index is implemented as a general group (GG). The fixed portion
of each entry is a CRP (defined below). The variable portion contains
the list of children of the chunk (including CHID values) and the
chunk name (if it has one).
The CRP indicates where in the heap the chunk data is, whether
the chunk data is compressed, whether the chunk is a "loner" and how
many parents the chunk has. Loner chunks are ones that can exist without
a parent. When pcfl->Delete(ctg, cno) is called, the chunk's loner
flag is cleared. If the chunk has no parents, the chunk is deleted
(by removing its CRP from the index and adding the space in the
heap that it occupied to the free map). We also decrement the
parent counts of the children of the chunk. Child chunks whose
parent count becomes zero and are not loner chunks are then deleted,
and the process continues down through the chunk graph.
The CFL class implements intelligent chunk copying within a run-time
session. Suppose a source CFL contains two chunks A and B, with B a
child of A. If chunk A is copied to another chunky file, its entire
subgraph is copied, so B is copied with it. Suppose however that chunk
B was previously copied to the destination, then A is copied. We see
that chunk B is already in the destination CFL, so don't actually copy
the chunk - we just copy A and make the previously coped B a child of
the newly copied A. If this is not what you want, use FClone instead
of FCopy. FClone copies the entire chunk subgraph, but does not use
any previously copied chunks. The intelligent chunk copying only
happens within a single run-time session (from the files point of view).
If the chunky files are closed and re-opened, no sharing will occur.
***************************************************************************/
#include "util.h"
ASSERTNAME
/* HISTORY of CFL version numbers
1 ShonK: instantiated, 10/28/93
2 ShonK: added compression support, 2/14/95
3 ShonK: changed format of chunk names to be a saved STN, 3/30/95
4 ShonK: implemented support for compact index (CRP is smaller). 8/21/95
5 ShonK: added the fcrpForest flag. 9/4/95
*/
// A file written by this version of chunk.cpp receives this cvn. Any
// file with this cvn value has exactly the same file format
const short kcvnCur = 5;
// A file written by this version of chunk.cpp can be read by any version
// of chunk.cpp whose kcvnCur is >= to this (this should be <= kcvnCur)
const short kcvnBack = 4;
// A file whose cvn is less than kcvnMin cannot be directly read by
// this version of chunk.cpp (maybe a converter will read it).
// (this should be <= kcvnCur)
// NOTE: (ShonK, 3/30/95): if this is >= 3, the fOldNames handling in
// _FReadIndex can be removed!
// NOTE: (ShonK, 8/21/95): if this is >= 4, the fOldIndex handling in
// _FReadIndex can be removed!
const short kcvnMin = 1;
const kcvnMinStnNames = 3;
const kcvnMinGrfcrp = 4;
const kcvnMinSmallIndex = 4;
const kcvnMinForest = 5;
const long klwMagicChunky = BigLittle('CHN2', '2NHC'); //chunky file signature
// chunky file prefix
struct CFP
{
long lwMagic; // identifies this as a chunky file
CTG ctgCreator; // program that created this file
DVER dver; // chunky file version
short bo; // byte order
short osk; // which system wrote this
FP fpMac; // logical end of file
FP fpIndex; // location of chunky index
long cbIndex; // size of chunky index
FP fpMap; // location of free space map
long cbMap; // size of free space map (may be 0)
long rglwReserved[23]; // reserved for future use - should be zero
};
const BOM kbomCfp = 0xB55FFC00L;
// free space map entry
struct FSM
{
FP fp;
long cb;
};
const BOM kbomFsm = 0xF0000000L;
enum
{
fcrpNil = 0,
fcrpOnExtra = 0x01, // data is on the extra file
fcrpLoner = 0x02, // the chunk can stand (may also be a child)
fcrpPacked = 0x04, // the data is compressed
fcrpMarkT = 0x08, // used for consistency checks (see _TValidIndex)
fcrpForest = 0x10, // the chunk contains a forest of chunks
};
// Chunk Representation (big version) - fixed element in pggcrp
// variable part of group element is an rgkid and stn data (the name)
const kcbMaxCrpbg = klwMax;
struct CRPBG
{
CKI cki; // chunk id
FP fp; // location on file
long cb; // size of data on file
long ckid; // number of owned chunks
long ccrpRef; // number of owners of this chunk
long rti; // run-time id
union
{
struct
{
// for cvn <= kcvnMinGrfcrp
byte fOnExtra; // fcrpOnExtra
byte fLoner; // fcrpLoner
byte fPacked; // fcrpPacked
byte bT; // fcrpMarkT
};
// for cvn >= kcvnMinGrfcrp
ulong grfcrp;
};
long BvRgch(void)
{ return LwMul(ckid, size(KID)); }
long CbRgch(long cbVar)
{ return cbVar - BvRgch(); }
ulong Grfcrp(ulong grfcrpMask = (ulong)(-1))
{ return grfcrp & grfcrpMask; }
void ClearGrfcrp(ulong grfcrpT)
{ grfcrp &= ~grfcrpT; }
void SetGrfcrp(ulong grfcrpT)
{ grfcrp |= grfcrpT; }
void AssignGrfcrp(ulong grfcrpT, ulong grfcrpMask = (ulong)(-1))
{ grfcrp = grfcrp & ~grfcrpMask | grfcrpT & grfcrpMask; }
long Cb(void)
{ return cb; }
void SetCb(long cbT)
{ cb = cbT; }
};
const BOM kbomCrpbgGrfcrp = 0xFFFF0000L;
const BOM kbomCrpbgBytes = 0xFFFE0000L;
// Chunk Representation (small version) - fixed element in pggcrp
// variable part of group element is an rgkid and stn data (the name)
const kcbMaxCrpsm = 0x00FFFFFF;
const long kcbitGrfcrp = 8;
const ulong kgrfcrpAll = (1 << kcbitGrfcrp) - 1;
struct CRPSM
{
CKI cki; // chunk id
FP fp; // location on file
ulong luGrfcrpCb; // low byte is the grfcrp, high 3 bytes is cb
ushort ckid; // number of owned chunks
ushort ccrpRef; // number of owners of this chunk
long BvRgch(void)
{ return LwMul(ckid, size(KID)); }
long CbRgch(long cbVar)
{ return cbVar - BvRgch(); }
ulong Grfcrp(ulong grfcrpMask = (ulong)(-1))
{ return luGrfcrpCb & grfcrpMask & kgrfcrpAll; }
void ClearGrfcrp(ulong grfcrpT)
{ luGrfcrpCb &= ~(grfcrpT & kgrfcrpAll); }
void SetGrfcrp(ulong grfcrpT)
{ luGrfcrpCb |= grfcrpT & kgrfcrpAll; }
void AssignGrfcrp(ulong grfcrpT, ulong grfcrpMask = (ulong)(-1))
{
luGrfcrpCb = luGrfcrpCb & ~(grfcrpMask & kgrfcrpAll) |
grfcrpT & grfcrpMask & kgrfcrpAll;
}
long Cb(void)
{ return luGrfcrpCb >> kcbitGrfcrp; }
void SetCb(long cbT)
{ luGrfcrpCb = (cbT << kcbitGrfcrp) | luGrfcrpCb & kgrfcrpAll; }
};
const BOM kbomCrpsm = 0xFF500000L;
#ifdef CHUNK_BIG_INDEX
typedef CRPBG CRP;
typedef CRPSM CRPOTH;
const long kcbMaxCrp = kcbMaxCrpbg;
typedef long CKID;
const long kckidMax = kcbMax;
const BOM kbomCrpsm = kbomCrpbgGrfcrp;
#else //!CHUNK_BIG_INDEX
typedef CRPSM CRP;
typedef CRPBG CRPOTH;
const long kcbMaxCrp = kcbMaxCrpsm;
typedef ushort CKID;
const long kckidMax = ksuMax;
const BOM kbomCrp = kbomCrpsm;
#endif //!CHUNK_BIG_INDEX
#define _BvKid(ikid) LwMul(ikid, size(KID))
const long rtiNil = 0; // no rti assigned
long CFL::_rtiLast = rtiNil;
PCFL CFL::_pcflFirst;
#ifdef CHUNK_STATS
bool vfDumpChunkRequests = fTrue;
PFIL _pfilStats;
FP _fpStats;
/***************************************************************************
Static method to dump a string to the chunk stats file
***************************************************************************/
void CFL::DumpStn(PSTN pstn, PFIL pfil)
{
FNI fni;
STN stn;
if (pvNil == _pfilStats)
{
stn = PszLit("ChnkStat.txt");
if (!fni.FGetTemp() || !fni.FSetLeaf(&stn) ||
pvNil == (_pfilStats = FIL::PfilCreate(&fni)))
{
return;
}
}
if (pvNil != pfil)
{
pfil->GetFni(&fni);
fni.GetStnPath(&stn);
stn.FAppendSz(PszLit(": "));
_pfilStats->FWriteRgbSeq(stn.Prgch(), LwMul(stn.Cch(), size(achar)),
&_fpStats);
}
_pfilStats->FWriteRgbSeq(pstn->Prgch(), LwMul(pstn->Cch(), size(achar)),
&_fpStats);
_pfilStats->FWriteRgbSeq(PszLit("\xD\xA"),
MacWin(size(achar), 2 * size(achar)), &_fpStats);
}
#endif //CHUNK_STATS
RTCLASS(CFL)
RTCLASS(CGE)
/***************************************************************************
Constructor for CFL - private.
***************************************************************************/
CFL::CFL(void)
{
// add it to the linked list
_Attach(&_pcflFirst);
AssertBaseThis(0);
}
/***************************************************************************
Close the associated files. Private.
***************************************************************************/
CFL::~CFL(void)
{
AssertBaseThis(0);
ReleasePpo(&_csto.pfil);
ReleasePpo(&_csto.pglfsm);
ReleasePpo(&_cstoExtra.pfil);
ReleasePpo(&_cstoExtra.pglfsm);
ReleasePpo(&_pggcrp);
#ifndef CHUNK_BIG_INDEX
ReleasePpo(&_pglrtie);
#endif //!CHUNK_BIG_INDEX
}
/***************************************************************************
Static method: open an existing file as a chunky file. Increments the
open count.
***************************************************************************/
PCFL CFL::PcflOpen(FNI *pfni, ulong grfcfl)
{
AssertPo(pfni, ffniFile);
PCFL pcfl;
ulong grffil;
Assert(!(grfcfl & fcflTemp), "can't open a file as temp");
if (pvNil != (pcfl = PcflFromFni(pfni)))
{
if (!pcfl->FSetGrfcfl(grfcfl))
return pvNil;
pcfl->AddRef();
return pcfl;
}
grffil = _GrffilFromGrfcfl(grfcfl);
if ((pcfl = NewObj CFL()) == pvNil)
goto LFail;
if ((pcfl->_csto.pfil = FIL::PfilOpen(pfni, grffil)) == pvNil ||
!pcfl->_FReadIndex())
{
goto LFail;
}
if (tYes != pcfl->_TValidIndex())
{
LFail:
ReleasePpo(&pcfl);
PushErc(ercCflOpen);
return pvNil;
}
AssertDo(pcfl->FSetGrfcfl(grfcfl), 0);
// We don't assert with fcflGraph, because we've already
// called _TValidIndex.
AssertPo(pcfl, fcflFull);
return pcfl;
}
/***************************************************************************
If the file is read-only, toss the index and extra file and re-read
the index.
***************************************************************************/
bool CFL::FReopen(void)
{
AssertThis(0);
CSTO csto, cstoExtra;
PGG pggcrp;
#ifndef CHUNK_BIG_INDEX
PGL pglrtie;
#endif //CHUNK_BIG_INDEX
bool fFreeMapNotRead;
bool fRet;
if (_csto.pfil->GrffilCur() & ffilWriteEnable || _fInvalidMainFile)
return fFalse;
pggcrp = _pggcrp;
_pggcrp = pvNil;
#ifndef CHUNK_BIG_INDEX
pglrtie = _pglrtie;
_pglrtie = pvNil;
#endif //CHUNK_BIG_INDEX
fFreeMapNotRead = _fFreeMapNotRead;
_fFreeMapNotRead = fFalse;
csto = _csto;
ClearPb(&_csto, size(csto));
_csto.pfil = csto.pfil;
_csto.pfil->AddRef();
cstoExtra = _cstoExtra;
ClearPb(&_cstoExtra, size(cstoExtra));
fRet = _FReadIndex() && tYes == _TValidIndex();
if (!fRet)
{
SwapVars(&_pggcrp, &pggcrp);
#ifndef CHUNK_BIG_INDEX
SwapVars(&_pglrtie, &pglrtie);
#endif //CHUNK_BIG_INDEX
SwapVars(&_csto, &csto);
SwapVars(&_cstoExtra, &cstoExtra);
_fFreeMapNotRead = FPure(fFreeMapNotRead);
}
ReleasePpo(&pggcrp);
#ifndef CHUNK_BIG_INDEX
ReleasePpo(&pglrtie);
#endif //CHUNK_BIG_INDEX
ReleasePpo(&csto.pfil);
ReleasePpo(&csto.pglfsm);
ReleasePpo(&cstoExtra.pfil);
ReleasePpo(&cstoExtra.pglfsm);
AssertThis(0);
return fRet;
}
/***************************************************************************
Static method to get file options corresponding to the given chunky
file options.
***************************************************************************/
ulong CFL::_GrffilFromGrfcfl(ulong grfcfl)
{
ulong grffil = ffilDenyWrite;
if (grfcfl & fcflWriteEnable)
grffil |= ffilWriteEnable | ffilDenyRead;
if (grfcfl & fcflTemp)
grffil |= ffilTemp;
return grffil;
}
/***************************************************************************
Static method: create a new file. Increments the open count.
***************************************************************************/
PCFL CFL::PcflCreate(FNI *pfni, ulong grfcfl)
{
AssertPo(pfni, ffniFile);
PCFL pcfl;
ulong grffil;
grfcfl |= fcflWriteEnable;
grffil = _GrffilFromGrfcfl(grfcfl);
if (pvNil != (pcfl = CFL::PcflFromFni(pfni)))
{
Bug("trying to create an open file");
goto LFail;
}
if ((pcfl = NewObj CFL()) == pvNil)
goto LFail;
if ((pcfl->_pggcrp = GG::PggNew(size(CRP))) == pvNil ||
(pcfl->_csto.pfil = FIL::PfilCreate(pfni, grffil)) == pvNil ||
!pcfl->_csto.pfil->FSetFpMac(size(CFP)))
{
ReleasePpo(&pcfl);
LFail:
PushErc(ercCflCreate);
return pvNil;
}
pcfl->_csto.fpMac = size(CFP);
AssertDo(pcfl->FSetGrfcfl(grfcfl), 0);
AssertPo(pcfl, fcflFull | fcflGraph);
return pcfl;
}
/***************************************************************************
Static method to create a temporary chunky file in the same directory as
*pfni and with the same ftg. If pfni is nil, the file is created in
the standard place with a temp ftg.
***************************************************************************/
PCFL CFL::PcflCreateTemp(FNI *pfni)
{
AssertNilOrPo(pfni, ffniFile);
FNI fni;
if (pvNil != pfni)
{
fni = *pfni;
if (!fni.FGetUnique(pfni->Ftg()))
goto LFail;
}
else if (!fni.FGetTemp())
{
LFail:
PushErc(ercCflCreate);
return pvNil;
}
return PcflCreate(&fni, fcflTemp);
}
/***************************************************************************
Static method: if we have the chunky file indicated by fni open, returns
the pcfl, otherwise returns pvNil. Doesn't affect the open count.
***************************************************************************/
PCFL CFL::PcflFromFni(FNI *pfni)
{
AssertPo(pfni, ffniFile);
PFIL pfil;
PCFL pcfl;
if ((pfil = FIL::PfilFromFni(pfni)) == pvNil)
return pvNil;
for (pcfl = _pcflFirst; pcfl != pvNil; pcfl = pcfl->PcflNext())
{
Assert(pfil != pcfl->_cstoExtra.pfil, "who is using this FNI?");
if (pcfl->_csto.pfil == pfil && !pcfl->_fInvalidMainFile)
break;
}
return pcfl;
}
/***************************************************************************
Embedded chunk descriptor on file. Chunks and their subtrees can
be combined into a single stream by the following rules:
CF stands for a chunk forest. CT stands for a chunk tree.
ECDF(n, m) stands for an ECDF structure with cb == n and ckid == m.
data(n) stands for n bytes of data.
CF -> (empty)
CF -> CT CF
CT -> ECDF(n, 0) data(n)
CT -> EDCF(n, m) data(n) CT CT ... CT (m times)
CFL::FWriteChunkTree writes a CT from a chunk and its children.
CFL::PcflReadForestFromFlo reads a CF from a flo and creates a CFL
around the data.
***************************************************************************/
struct ECDF
{
short bo;
short osk;
CTG ctg;
CHID chid;
long cb;
long ckid;
ulong grfcrp;
};
const BOM kbomEcdf = 0x5FFC0000L;
/***************************************************************************
Combine the indicated chunk and its children into an embedded chunk.
If pfil is pvNil, just computes the size.
***************************************************************************/
bool CFL::FWriteChunkTree(CTG ctg, CNO cno, PFIL pfilDst, FP fpDst, long *pcb)
{
AssertThis(0);
AssertNilOrPo(pfilDst, 0);
AssertNilOrVarMem(pcb);
CGE cge;
ECDF ecdf;
FLO floSrc, floDst;
KID kid;
CKI ckiPar;
ulong grfcge;
if (pvNil != pcb)
*pcb = 0;
cge.Init(this, ctg, cno);
while (cge.FNextKid(&kid, &ckiPar, &grfcge, fcgeNil))
{
if (grfcge & fcgeError)
goto LFail;
if (!(grfcge & fcgePre))
continue;
AssertDo(FFindFlo(kid.cki.ctg, kid.cki.cno, &floSrc), 0);
if (pvNil != pcb)
*pcb += size(ECDF) + floSrc.cb;
if (pvNil != pfilDst)
{
// write the data
ecdf.bo = kboCur;
ecdf.osk = koskCur;
ecdf.ctg = kid.cki.ctg;
ecdf.chid = (grfcge & fcgeRoot) ? chidNil : kid.chid;
ecdf.cb = floSrc.cb;
ecdf.ckid = Ckid(kid.cki.ctg, kid.cki.cno);
ecdf.grfcrp = fcrpNil;
if (FPacked(kid.cki.ctg, kid.cki.cno))
ecdf.grfcrp |= fcrpPacked;
if (FForest(kid.cki.ctg, kid.cki.cno))
ecdf.grfcrp |= fcrpForest;
if (!pfilDst->FWriteRgbSeq(&ecdf, size(ecdf), &fpDst))
goto LFail;
floDst.pfil = pfilDst;
floDst.fp = fpDst;
floDst.cb = floSrc.cb;
if (!floSrc.FCopy(&floDst))
{
LFail:
TrashVar(pcb);
return fFalse;
}
fpDst += floDst.cb;
}
}
return fTrue;
}
/***************************************************************************
Static method to read a serialized chunk stream (as written by a series
of calls to FWriteChunkTree) and create a CFL around it. If fCopyData
is false, we construct the CFL to use pointers to the data in the FLO.
Otherwise, we copy the data to a new file.
***************************************************************************/
PCFL CFL::PcflReadForestFromFlo(PFLO pflo, bool fCopyData)
{
AssertVarMem(pflo);
AssertPo(pflo->pfil, 0);
// embedded chunk stack descriptor
struct ECSD
{
CTG ctg;
CNO cno;
long ckid;
};
PCFL pcfl;
ECDF ecdf;
ECSD ecsdT, ecsdCur;
FP fpSrc, fpLimSrc;
PGL pglecsd = pvNil;
if (pvNil == (pglecsd = GL::PglNew(size(ECSD))))
goto LFail;
if ((pcfl = NewObj CFL()) == pvNil)
goto LFail;
if (pvNil == (pcfl->_pggcrp = GG::PggNew(size(CRP))))
goto LFail;
if (fCopyData)
{
if (pvNil == (pcfl->_csto.pfil = FIL::PfilCreateTemp()) ||
!pcfl->_csto.pfil->FSetFpMac(size(CFP)))
{
goto LFail;
}
pcfl->_csto.fpMac = size(CFP);
}
else
{
pcfl->_csto.pfil = pflo->pfil;
pcfl->_csto.pfil->AddRef();
pcfl->_csto.fpMac = pflo->fp + pflo->cb;
pcfl->_fInvalidMainFile = fTrue;
pcfl->_fAddToExtra = fTrue;
}
AssertPo(pcfl, fcflFull | fcflGraph);
ClearPb(&ecsdCur, size(ecsdCur));
fpSrc = pflo->fp;
fpLimSrc = pflo->fp + pflo->cb;
for (;;)
{
if (fpSrc >= fpLimSrc)
break;
if (fpSrc > fpLimSrc + size(ecdf) ||
!pflo->pfil->FReadRgbSeq(&ecdf, size(ecdf), &fpSrc))
{
goto LFail;
}
if (kboOther == ecdf.bo)
SwapBytesBom(&ecdf, kbomEcdf);
else if (kboCur != ecdf.bo)
goto LFail;
if (!FIn(ecdf.cb, 0, fpLimSrc - fpSrc + 1))
goto LFail;
if (fCopyData)
{
BLCK blck(pflo->pfil, fpSrc, ecdf.cb, ecdf.grfcrp & fcrpPacked);
if (!pcfl->FAddBlck(&blck, ecdf.ctg, &ecsdT.cno))
goto LFail;
}
else
{
CRP *qcrp;
long icrp;
pcfl->_GetUniqueCno(ecdf.ctg, &icrp, &ecsdT.cno);
if (!pcfl->_FAdd(0, ecdf.ctg, ecsdT.cno, icrp, pvNil))
goto LFail;
qcrp = (CRP *)pcfl->_pggcrp->QvFixedGet(icrp);
qcrp->fp = ecdf.cb > 0 ? fpSrc : 0;
qcrp->SetCb(ecdf.cb);
qcrp->AssignGrfcrp(ecdf.grfcrp, fcrpPacked | fcrpForest);
}
fpSrc += ecdf.cb;
if (pglecsd->IvMac() > 0)
{
Assert(ecsdCur.ckid > 0, 0);
// Make this a child
if (!pcfl->FAdoptChild(ecsdCur.ctg, ecsdCur.cno,
ecdf.ctg, ecsdT.cno, ecdf.chid))
{
goto LFail;
}
ecsdCur.ckid--;
}
if (ecdf.ckid > 0)
{
// This one has children, so we need to push the current ecsd and
// make this the current one
ecsdT.ctg = ecdf.ctg;
ecsdT.ckid = ecdf.ckid;
if (!pglecsd->FPush(&ecsdCur))
goto LFail;
ecsdCur = ecsdT;
}
else
{
// pop up while there are no more children and there's something
// to pop.
while (ecsdCur.ckid <= 0 && pglecsd->IvMac() > 0)
pglecsd->FPop(&ecsdCur);
}
}
if (pglecsd->IvMac() > 0 || ecsdCur.ckid != 0)
{
LFail:
// something failed or the data was bad
ReleasePpo(&pcfl);
}
ReleasePpo(&pglecsd);
AssertNilOrPo(pcfl, fcflFull | fcflGraph);
return pcfl;
}
/***************************************************************************
Return whether the chunk contains a forest of subchunks.
***************************************************************************/
bool CFL::FForest(CTG ctg, CNO cno)
{
AssertThis(0);
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
return fFalse;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
Assert(qcrp->cki.ctg == ctg && qcrp->cki.cno == cno, 0);
return FPure(qcrp->Grfcrp(fcrpForest));
}
/***************************************************************************
Set or clear the "forest" flag.
***************************************************************************/
void CFL::SetForest(CTG ctg, CNO cno, bool fForest)
{
AssertThis(0);
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
return;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
Assert(qcrp->cki.ctg == ctg && qcrp->cki.cno == cno, 0);
if (FPure(qcrp->Grfcrp(fcrpForest)) != FPure(fForest))
{
if (fForest)
qcrp->SetGrfcrp(fcrpForest);
else
qcrp->ClearGrfcrp(fcrpForest);
_FSetRti(ctg, cno, rtiNil);
}
}
/***************************************************************************
Read the chunk's data as an embedded forest and construct a CFL around
the data.
***************************************************************************/
PCFL CFL::PcflReadForest(CTG ctg, CNO cno, bool fCopyData)
{
AssertThis(0);
FLO flo;
if (!FFindFlo(ctg, cno, &flo))
return pvNil;
if (FPacked(ctg, cno))
{
BLCK blck(&flo, fTrue);
if (!blck.FUnpackData() || !blck.FGetFlo(&flo))
return pvNil;
fCopyData = fFalse;
}
return PcflReadForestFromFlo(&flo, fCopyData);
}
/***************************************************************************
Static method: clear the marks for chunky files.
***************************************************************************/
void CFL::ClearMarks(void)
{
PCFL pcfl;
for (pcfl = _pcflFirst; pcfl != pvNil; pcfl = pcfl->PcflNext())
{
AssertPo(pcfl, 0);
pcfl->_fMark = fFalse;
}
}
/***************************************************************************
Static method: close any chunky files that are unmarked and have 0
open count.
***************************************************************************/
void CFL::CloseUnmarked(void)
{
PCFL pcfl, pcflNext;
for (pcfl = _pcflFirst; pcfl != pvNil; pcfl = pcflNext)
{
AssertPo(pcfl, 0);
pcflNext = pcfl->PcflNext();
if (!pcfl->_fMark && pcfl->_cactRef == 0)
delete pcfl;
}
}
/***************************************************************************
Set the grfcfl options. This sets or clears fcflTemp and only sets
(never clears) fcflMark, fcflWriteEnable and fcflAddToExtra. This
can only fail if fcflWriteEnable is specified and we can't make
the base file write enabled.
***************************************************************************/
bool CFL::FSetGrfcfl(ulong grfcfl, ulong grfcflMask)
{
AssertThis(0);
ulong grffil, grffilMask;
grfcfl &= grfcflMask;
grffil = _GrffilFromGrfcfl(grfcfl);
grffilMask = _GrffilFromGrfcfl(grfcflMask);
if ((grffil ^ _csto.pfil->GrffilCur()) & grffilMask)
{
// need to change some file properties
if (_fInvalidMainFile || !_csto.pfil->FSetGrffil(grffil, grffilMask))
return fFalse;
}
// If we're becoming write enabled and we haven't read the free map yet,
// do so now. Reading the free map is non-critical.
if ((grfcfl & fcflWriteEnable) && _fFreeMapNotRead)
_ReadFreeMap();
// don't clear the mark field if it's already set
if (grfcfl & fcflMark)
_fMark = fTrue;
// don't clear the _fAddToExtra field if it's already set
if ((grfcfl & fcflAddToExtra) || ((grfcfl ^ grfcflMask) & fcflWriteEnable))
_fAddToExtra = fTrue;
if (grfcfl & fcflReadFromExtra)
_fReadFromExtra = fTrue;
return fTrue;
}
/***************************************************************************
Return the level of error that we've encountered for this chunky file.
***************************************************************************/
long CFL::ElError(void)
{
AssertThis(0);
long elT;
elT = _csto.pfil->ElError();
if (pvNil != _cstoExtra.pfil)
elT = LwMax(elT, _cstoExtra.pfil->ElError());
return elT;
}
/***************************************************************************
Make sure the el level on both files is at or below el.
***************************************************************************/
void CFL::ResetEl(long el)
{
AssertThis(0);
AssertIn(el, elNil, kcbMax);
if (_csto.pfil->ElError() > el)
_csto.pfil->SetEl(el);
if (pvNil != _cstoExtra.pfil && _cstoExtra.pfil->ElError() > el)
_cstoExtra.pfil->SetEl(el);
}
/***************************************************************************
Decrement the open count. If it is zero and the chunky file isn't
marked, the file is closed.
***************************************************************************/
void CFL::Release(void)
{
AssertBaseThis(0);
if (_cactRef <= 0)
{
Bug("calling Release without an AddRef");
return;
}
if (--_cactRef == 0 && !_fMark)
delete this;
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of the chunky file. If fcflFull, does some checking
of the index. If fcflGraph, does fcflFull and checks the graph structure
for cycles.
***************************************************************************/
void CFL::AssertValid(ulong grfcfl)
{
CFL_PAR::AssertValid(fobjAllocated);
bool fFirstCrp;
long cbTot = 0;
long cbTotExtra = 0;
CKI ckiOld;
CKI ckiNew;
KID kid, kidOld;
bool fFirstKid;
long icrp, icrpT;
long ccrpRefTot = 0;
long ckidTot = 0;
long cbVar, cbRgch;
CRP crp;
long ikid;
FP fpBase = _fInvalidMainFile ? 0 : size(CFP);
Assert(!_fInvalidMainFile || _fAddToExtra, 0);
AssertPo(_pggcrp, 0);
AssertPo(_csto.pfil, 0);
if (_csto.pglfsm != pvNil)
{
AssertPo(_csto.pglfsm, 0);
Assert(!_fFreeMapNotRead, "free map never read, but exists!");
}
Assert(_csto.fpMac >= fpBase, "fpMac wrong");
if (_cstoExtra.pfil != pvNil)
{
AssertPo(_cstoExtra.pfil, 0);
if (_cstoExtra.pglfsm != pvNil)
AssertPo(_cstoExtra.pglfsm, 0);
}
else
Assert(_cstoExtra.pglfsm == pvNil, 0);
#ifndef CHUNK_BIG_INDEX
AssertNilOrPo(_pglrtie, 0);
#endif //!CHUNK_BIG_INDEX
if (!(grfcfl & (fcflFull | fcflGraph)))
return;
SuspendAssertValid();
// Verify that the index and free map(s) are in sorted order.
// Would be nice to verify ccrpRef and that crp's don't overlap,
// but this is hard, so instead we verify that the sum of ccrpRef
// values is correct and that the total length of all blocks is not
// too big.
fFirstCrp = fTrue;
for (icrp = _pggcrp->IvMac(); icrp-- != 0; )
{
_pggcrp->GetFixed(icrp, &crp);
cbVar = _pggcrp->Cb(icrp);
cbRgch = crp.CbRgch(cbVar);
AssertIn(crp.ckid, 0, kckidMax + 1);
AssertIn(cbRgch, 0, kcbMaxDataStn + 1);
// we use this in checking the graph structure
Assert(!crp.Grfcrp(fcrpMarkT), "fcrpMarkT set");
ckiNew = crp.cki;
ccrpRefTot += crp.ccrpRef;
ckidTot += crp.ckid;
#ifdef CHUNK_BIG_INDEX
AssertIn(crp.rti, 0, _rtiLast + 1);
#endif //CHUNK_BIG_INDEX
// assert that the file storage (fp,cb) is OK
if (crp.Grfcrp(fcrpOnExtra))
{
Assert(_cstoExtra.pfil != pvNil, "fcrpOnExtra wrong");
AssertIn(crp.fp, 0, _cstoExtra.fpMac);
AssertIn(crp.Cb(), 1, _cstoExtra.fpMac - crp.fp + 1);
cbTotExtra += crp.Cb();
}
else
{
AssertVar(
crp.Cb() > 0 && (crp.fp >= fpBase) ||
crp.Cb() == 0 && crp.fp == 0, "bad fp", &crp.fp);
AssertVar(crp.fp <= _csto.fpMac, "bad fp", &crp.fp);
AssertIn(crp.Cb(), 0, _csto.fpMac - crp.fp + 1);
cbTot += crp.Cb();
}
//assert that this crp is in order
AssertVar(fFirstCrp || ckiNew.ctg < ckiOld.ctg ||
ckiNew.ctg == ckiOld.ctg && ckiNew.cno < ckiOld.cno,
"crp not in order", &ckiNew);
ckiOld = ckiNew;
fFirstCrp = fFalse;
fFirstKid = fTrue;
for (ikid = 0; ikid < crp.ckid; ikid++)
{
_pggcrp->GetRgb(icrp, _BvKid(ikid), size(KID), &kid);
AssertVar(_FFindCtgCno(kid.cki.ctg, kid.cki.cno, &icrpT),
"child doesn't exist", &kid.cki);
Assert(kid.cki.ctg != ckiOld.ctg || kid.cki.cno != ckiOld.cno,
"chunk is child of itself!");
Assert(fFirstKid || kidOld.chid < kid.chid ||
kidOld.chid == kid.chid && kidOld.cki.ctg < kid.cki.ctg ||
kidOld.chid == kid.chid && kidOld.cki.ctg == kid.cki.ctg &&
kidOld.cki.cno < kid.cki.cno,
"kid's not sorted or duplicate kid");
kidOld = kid;
fFirstKid = fFalse;
}
}
Assert(ccrpRefTot == ckidTot, "ref counts messed up");
Assert(cbTotExtra <= _cstoExtra.fpMac, "overlapping chunks on extra");
Assert(cbTot <= _csto.fpMac - fpBase, "overlapping chunks on file");
if (_csto.pglfsm != pvNil)
{
long ifsm;
FSM fsm;
FP fpOld;
fpOld = _csto.fpMac;
for (ifsm = _csto.pglfsm->IvMac(); ifsm-- != 0; )
{
_csto.pglfsm->Get(ifsm, &fsm);
Assert(fsm.fp >= fpBase && fsm.fp < fpOld, "bad fp in fsm");
Assert(fsm.cb + fsm.fp > fsm.fp && fsm.cb + fsm.fp < fpOld,
"bad cb in fsm");
fpOld = fsm.fp;
cbTot += fsm.cb;
}
Assert(cbTot <= _csto.fpMac - fpBase, "too much free space");
}
if (_cstoExtra.pglfsm != pvNil)
{
long ifsm;
FSM fsm;
FP fpOld;
fpOld = _cstoExtra.fpMac;
for (ifsm = _cstoExtra.pglfsm->IvMac(); ifsm-- != 0; )
{
_cstoExtra.pglfsm->Get(ifsm, &fsm);
Assert(fsm.fp >= 0 && fsm.fp < fpOld, "bad fp in fsm");
Assert(fsm.cb + fsm.fp > fsm.fp && fsm.cb + fsm.fp < fpOld,
"bad cb in fsm");
fpOld = fsm.fp;
cbTotExtra += fsm.cb;
}
Assert(cbTotExtra <= _cstoExtra.fpMac, "too much free space on extra");
}
if (grfcfl & fcflGraph)
Assert(_TValidIndex() != tNo, "bad index");
#ifndef CHUNK_BIG_INDEX
// verify that the _pglrtie is in sorted order and all the chunks exist
if (pvNil != _pglrtie && _pglrtie->IvMac() > 0)
{
long irtie;
RTIE rtie, rtiePrev;
long icrp;
irtie = _pglrtie->IvMac() - 1;
_pglrtie->Get(irtie, &rtie);
Assert(rtiNil != rtie.rti && _FFindCtgCno(rtie.ctg, rtie.cno, &icrp),
"Bad RTIE");
while (irtie-- > 0)
{
rtiePrev = rtie;
_pglrtie->Get(irtie, &rtie);
Assert(rtiNil != rtie.rti &&
_FFindCtgCno(rtie.ctg, rtie.cno, &icrp), "Bad RTIE");
Assert(rtie.ctg < rtiePrev.ctg ||
rtie.ctg == rtiePrev.ctg && rtie.cno < rtiePrev.cno,
"RTIE out of order");
}
}
#endif //!CHUNK_BIG_INDEX
ResumeAssertValid();
}
/***************************************************************************
Mark memory used by the CFL
***************************************************************************/
void CFL::MarkMem(void)
{
AssertThis(0);
CFL_PAR::MarkMem();
MarkMemObj(_pggcrp);
MarkMemObj(_csto.pglfsm);
MarkMemObj(_cstoExtra.pglfsm);
#ifndef CHUNK_BIG_INDEX
MarkMemObj(_pglrtie);
#endif //!CHUNK_BIG_INDEX
}
#endif //DEBUG
/***************************************************************************
Checks for cycles and other consistency in the chunky index. If
we find something wrong, return tNo. If a memory error occurs,
return tMaybe. If it's AOK, return tYes.
***************************************************************************/
bool CFL::_TValidIndex(void)
{
//WARNING: this is called by a full CFL::AssertValid().
long icrp, icrpT;
CRP *qcrp;
CGE cge;
ulong grfcge, grfcgeIn;
KID kid;
long cbVar;
long ccrpRefTot;
long ckidTot;
//first clear all fcrpMarkT fields
ccrpRefTot = ckidTot = 0;
for (icrp = _pggcrp->IvMac(); icrp-- != 0; )
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp, &cbVar);
if (!FIn(qcrp->ckid, 0, cbVar / size(KID) + 1))
return tNo;
if (!FIn(qcrp->ccrpRef, 0, kckidMax + 1))
return tNo;
ccrpRefTot += qcrp->ccrpRef;
ckidTot += qcrp->ckid;
qcrp->ClearGrfcrp(fcrpMarkT);
}
if (ccrpRefTot != ckidTot)
return tNo;
SuspendAssertValid();
SuspendCheckPointers();
// now enumerate over root level graphs, marking descendents on the
// pre-pass and unmarking on the post pass - this catches cycles
// NOTE: we must do this before checking for orphan subgraphs, so we don't
// end up in an infinite loop in the orphan check.
for (icrp = _pggcrp->IvMac(); icrp-- != 0; )
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (qcrp->ccrpRef > 0 || qcrp->ckid == 0)
continue;
//this is not a child node and has children, so enum its subgraph
cge.Init(this, qcrp->cki.ctg, qcrp->cki.cno);
while (cge.FNextKid(&kid, pvNil, &grfcge, fcgeNil))
{
if (grfcge & fcgeError)
{
ResumeAssertValid();
ResumeCheckPointers();
return tMaybe;
}
if (!_FFindCtgCno(kid.cki.ctg, kid.cki.cno, &icrpT))
{
ResumeAssertValid();
ResumeCheckPointers();
return tNo;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrpT);
if (grfcge & fcgePre)
{
if (qcrp->Grfcrp(fcrpMarkT))
{
// has a cycle here
ResumeAssertValid();
ResumeCheckPointers();
return tNo;
}
qcrp->SetGrfcrp(fcrpMarkT);
}
if (grfcge & fcgePost)
{
Assert(qcrp->Grfcrp(fcrpMarkT), "why isn't this marked?");
qcrp->ClearGrfcrp(fcrpMarkT);
}
}
}
// now enumerate over root level graphs, marking all descendents
// this will find orphan subgraphs and validate (ccrpRef > 0).
for (icrp = _pggcrp->IvMac(); icrp-- != 0; )
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (qcrp->ccrpRef > 0 || qcrp->ckid == 0)
continue;
// this is not a child node and has some children, so enum its subgraph
cge.Init(this, qcrp->cki.ctg, qcrp->cki.cno);
grfcgeIn = fcgeNil;
while (cge.FNextKid(&kid, pvNil, &grfcge, grfcgeIn))
{
grfcgeIn = fcgeNil;
if (grfcge & fcgeError)
{
ResumeAssertValid();
ResumeCheckPointers();
return tMaybe;
}
if ((grfcge & fcgePre) && !(grfcge & fcgeRoot))
{
if (!_FFindCtgCno(kid.cki.ctg, kid.cki.cno, &icrpT))
{
ResumeAssertValid();
ResumeCheckPointers();
return tNo;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrpT);
if (qcrp->Grfcrp(fcrpMarkT))
grfcgeIn = fcgeSkipToSib;
else
qcrp->SetGrfcrp(fcrpMarkT);
}
}
}
// make sure the fcrpMarkT fields are set iff (ccrpRef > 0)
for (icrp = _pggcrp->IvMac(); icrp-- != 0; )
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if ((qcrp->ccrpRef != 0) != FPure(qcrp->Grfcrp(fcrpMarkT)))
{
ResumeAssertValid();
ResumeCheckPointers();
return tNo;
}
qcrp->ClearGrfcrp(fcrpMarkT);
}
ResumeAssertValid();
ResumeCheckPointers();
return tYes;
}
/***************************************************************************
Verifies that this is a chunky file and reads the index into memory.
Sets the _fpFreeMap and _cbFreeMap fields and sets the _fFreeMapNotRead
flag. The free map is read separately by _ReadFreeMap. This is to avoid
hogging memory for the free map when we may never need it.
***************************************************************************/
bool CFL::_FReadIndex(void)
{
AssertBaseThis(0);
AssertPo(_csto.pfil, 0);
Assert(!_fInvalidMainFile, 0);
CFP cfp;
FP fpMac;
short bo;
short osk;
long cbVar;
long cbRgch;
long icrp, ccrp;
long cbFixed;
BOM bom;
// used for old name stuff
SZS szsName;
STN stn;
bool fOldNames;
// used for old index stuff
bool fOldIndex;
Assert(_pggcrp == pvNil && _csto.pglfsm == pvNil &&
_cstoExtra.pfil == pvNil && _cstoExtra.pglfsm == pvNil,
"cfl has wrong non-nil entries");
// verify that this is a chunky file
if ((fpMac = _csto.pfil->FpMac()) < size(CFP))
return fFalse;
if (!_csto.pfil->FReadRgb(&cfp, size(cfp), 0))
return fFalse;
// check the magic number and byte order indicator
if (cfp.lwMagic != klwMagicChunky ||
cfp.bo != kboCur && cfp.bo != kboOther)
{
return fFalse;
}
if (cfp.bo == kboOther)
SwapBytesBom(&cfp, kbomCfp);
// check the version numbers
if (!cfp.dver.FReadable(kcvnCur, kcvnMin))
return fFalse;
// if the file has old style chunk names, translate them
fOldNames = cfp.dver._swCur < kcvnMinStnNames;
// whether the index needs converted
fOldIndex = cfp.dver._swCur < kcvnMinGrfcrp;
// verify the fp's and cb's
// the index and map should be last
if (!FIn(cfp.fpMac, size(cfp), fpMac + 1) ||
!FIn(cfp.fpIndex, size(cfp), cfp.fpMac + 1) ||
!FIn(cfp.cbIndex, 1, cfp.fpMac - cfp.fpIndex + 1) ||
cfp.fpMap != cfp.fpIndex + cfp.cbIndex ||
cfp.fpMap + cfp.cbMap != cfp.fpMac)
{
return fFalse;
}
// read and validate the index
if ((_pggcrp = GG::PggRead(_csto.pfil, cfp.fpIndex, cfp.cbIndex,
&bo, &osk)) == pvNil)
{
return fFalse;
}
cbFixed = _pggcrp->CbFixed();
if (cbFixed != size(CRPBG) && (fOldIndex || cbFixed != size(CRPSM)))
return fFalse;
// Clean the index
AssertBomRglw(kbomKid, size(KID));
_pggcrp->Lock();
if (cbFixed == size(CRPBG))
{
// Big index
CRPBG *pcrpbg;
bom = (bo != kboCur) ?
(fOldIndex ? kbomCrpbgBytes : kbomCrpbgGrfcrp) : bomNil;
for (icrp = _pggcrp->IvMac(); icrp-- != 0; )
{
pcrpbg = (CRPBG *)_pggcrp->QvFixedGet(icrp, &cbVar);
#ifndef CHUNK_BIG_INDEX
// make sure we can convert this CRP to a small index CRP
if (pcrpbg->cb > kcbMaxCrpsm || pcrpbg->ckid > kckidMax ||
pcrpbg->ccrpRef > kckidMax)
{
goto LBadFile;
}
#endif //!CHUNK_BIG_INDEX
if (!FIn(cbVar, 0, kcbMax))
goto LBadFile;
if (bomNil != bom)
SwapBytesBom(pcrpbg, bom);
if (!FIn(pcrpbg->ckid, 0, cbVar / size(KID) + 1) ||
(cbRgch = pcrpbg->CbRgch(cbVar)) > kcbMaxDataStn)
{
goto LBadFile;
}
if (bomNil != bom && pcrpbg->ckid > 0)
{
SwapBytesRglw(_pggcrp->QvGet(icrp),
pcrpbg->ckid * (size(KID) / size(long)));
}
pcrpbg->rti = rtiNil;
if (fOldIndex)
{
ulong grfcrp = fcrpNil;
if (pcrpbg->fLoner)
grfcrp |= fcrpLoner;
if (pcrpbg->fPacked)
grfcrp |= fcrpPacked;
pcrpbg->grfcrp = grfcrp;
}
else
pcrpbg->ClearGrfcrp(fcrpOnExtra);
if (pcrpbg->ccrpRef == 0)
pcrpbg->SetGrfcrp(fcrpLoner);
AssertIn(cbRgch, 0, kcbMaxDataStn + 1);
if (fOldNames && cbRgch > 0)
{
// translate the name
long bvRgch = pcrpbg->BvRgch();
if (cbRgch < size(szsName))
{
long cbNew;
CopyPb(PvAddBv(_pggcrp->QvGet(icrp), bvRgch), szsName,
cbRgch);
szsName[cbRgch] = 0;
stn.SetSzs(szsName);
cbNew = stn.CbData();
if (cbRgch != cbNew)
{
_pggcrp->Unlock();
if (cbRgch < cbNew)
{
if (!_pggcrp->FInsertRgb(icrp, bvRgch,
cbNew - cbRgch, pvNil))
{
goto LNukeName;
}
}
else
_pggcrp->DeleteRgb(icrp, bvRgch, cbRgch - cbNew);
_pggcrp->Lock();
}
stn.GetData(PvAddBv(_pggcrp->QvGet(icrp), bvRgch));
}
else
{
// just nuke the name
_pggcrp->Unlock();
LNukeName:
_pggcrp->DeleteRgb(icrp, bvRgch, cbRgch);
_pggcrp->Lock();
}
}
}
}
else
{
// Small index
Assert(size(CRPSM) == cbFixed, 0);
CRPSM *pcrpsm;
bom = (bo != kboCur) ? kbomCrpsm : bomNil;
for (icrp = _pggcrp->IvMac(); icrp-- != 0; )
{
pcrpsm = (CRPSM *)_pggcrp->QvFixedGet(icrp, &cbVar);
if (!FIn(cbVar, 0, kcbMax))
goto LBadFile;
if (bomNil != bom)
SwapBytesBom(pcrpsm, bom);
if (!FIn(pcrpsm->ckid, 0, cbVar / size(KID) + 1) ||
(cbRgch = pcrpsm->CbRgch(cbVar)) > kcbMaxDataStn)
{
goto LBadFile;
}
if (pcrpsm->ckid > 0 && bomNil != bom)
{
SwapBytesRglw(_pggcrp->QvGet(icrp),
pcrpsm->ckid * (size(KID) / size(long)));
}
pcrpsm->ClearGrfcrp(fcrpOnExtra);
if (pcrpsm->ccrpRef == 0 && !pcrpsm->Grfcrp(fcrpLoner))
{
LBadFile:
_pggcrp->Unlock();
Bug("corrupt crp");
ReleasePpo(&_pggcrp);
return fFalse;
}
}
}
if (size(CRP) != cbFixed)
{
// need to convert the index (from big to small or small to big)
PGG pggcrp;
CRPOTH *pcrpOld;
CRP crp;
if (pvNil == (pggcrp = GG::PggNew(size(CRP), _pggcrp->IvMac())))
goto LFail;
for (ccrp = _pggcrp->IvMac(), icrp = 0; icrp < ccrp; icrp++)
{
pcrpOld = (CRPOTH *)_pggcrp->QvFixedGet(icrp, &cbVar);
crp.cki = pcrpOld->cki;
crp.fp = pcrpOld->fp;
crp.SetCb(pcrpOld->Cb());
crp.ckid = (CKID)pcrpOld->ckid;
crp.ccrpRef = (CKID)pcrpOld->ccrpRef;
crp.AssignGrfcrp(pcrpOld->Grfcrp());
#ifdef CHUNK_BIG_INDEX
crp.rti = rtiNil;
#endif //CHUNK_BIG_INDEX
if (!pggcrp->FInsert(icrp,
_pggcrp->Cb(icrp), _pggcrp->QvGet(icrp), &crp))
{
ReleasePpo(&pggcrp);
LFail:
_pggcrp->Unlock();
return fFalse;
}
}
_pggcrp->Unlock();
ReleasePpo(&_pggcrp);
_pggcrp = pggcrp;
}
else
_pggcrp->Unlock();
_cbFreeMap = cfp.cbMap;
_fpFreeMap = cfp.fpMap;
_fFreeMapNotRead = cfp.cbMap > 0;
_csto.fpMac = cfp.fpIndex;
return fTrue;
}
/***************************************************************************
Assert that the free map hasn't been read (and doesn't exist). Read
the free map from the file. This _cannot_ be called after chunk
data has been added to the main file (as opposed to the extra file).
This _cannot_ be called before _FReadIndex has been called.
***************************************************************************/
void CFL::_ReadFreeMap(void)
{
AssertBaseThis(0);
Assert(_fFreeMapNotRead, "free map already read");
Assert(!_fInvalidMainFile, 0);
short bo;
short osk;
long cfsm;
//clear this even if reading the free map fails - so we don't try to
//read again
_fFreeMapNotRead = fFalse;
if (_csto.pglfsm != pvNil)
{
Bug("free map already exists");
return;
}
if (_cbFreeMap > 0)
{
if ((_csto.pglfsm = GL::PglRead(_csto.pfil, _fpFreeMap, _cbFreeMap,
&bo, &osk)) == pvNil ||
_csto.pglfsm->CbEntry() != size(FSM))
{
// it failed, but so what
ReleasePpo(&_csto.pglfsm);
return;
}
// swap bytes
AssertBomRglw(kbomFsm, size(FSM));
if (bo != kboCur && (cfsm = _csto.pglfsm->IvMac()) > 0)
{
SwapBytesRglw(_csto.pglfsm->QvGet(0),
cfsm * (size(FSM) / size(long)));
}
}
}
/***************************************************************************
If we have don't have write permission or there's an extra file,
write out a new file and do the rename stuff. If not, just write
the index and free map.
***************************************************************************/
bool CFL::FSave(CTG ctgCreator, FNI *pfni)
{
AssertThis(fcflFull | fcflGraph);
AssertNilOrPo(pfni, ffniFile);
FNI fni;
FLO floSrc, floDst;
long ccrp, icrp;
CRP *qcrp;
PFIL pfilOld;
if (_fInvalidMainFile)
{
if (pvNil == pfni)
{
Bug("can't save a CFL that has no file attached!");
return fFalse;
}
fni = *pfni;
}
else
{
GetFni(&fni);
if (pfni != pvNil)
{
if (pfni->FEqual(&fni))
pfni = pvNil;
else
fni = *pfni;
}
}
if (!_fAddToExtra && _cstoExtra.pfil == pvNil && pfni == pvNil)
{
// just write the index
Assert(!_fFreeMapNotRead, "why hasn't the free map been read?");
if (!_FWriteIndex(ctgCreator))
goto LError;
return fTrue;
}
// get a temp name in the same directory as the target
if ((floDst.pfil = FIL::PfilCreateTemp(&fni)) == pvNil)
goto LError;
if (!floDst.pfil->FSetFpMac(size(CFP)))
goto LFail;
floDst.fp = size(CFP);
ccrp = _pggcrp->IvMac();
for (icrp = 0; icrp < ccrp; icrp++)
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
floSrc.pfil = qcrp->Grfcrp(fcrpOnExtra) ? _cstoExtra.pfil : _csto.pfil;
floSrc.fp = qcrp->fp;
floSrc.cb = floDst.cb = qcrp->Cb();
if (floSrc.cb > 0 && !floSrc.FCopy(&floDst))
{
LFail:
Assert(floDst.pfil->FTemp(), "file not a temp");
ReleasePpo(&floDst.pfil);
goto LError;
}
floDst.fp += floDst.cb;
}
// All the data has been copied. Update the index to point to the new file.
floSrc.fp = size(CFP);
for (icrp = 0; icrp < ccrp; icrp++)
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
qcrp->ClearGrfcrp(fcrpOnExtra);
qcrp->fp = qcrp->Cb() > 0 ? floSrc.fp : 0;
floSrc.fp += qcrp->Cb();
}
Assert(floSrc.fp == floDst.fp, "what happened? - file messed up");
// update the csto's and write the index
pfilOld = _csto.pfil;
ReleasePpo(&_csto.pglfsm);
_fFreeMapNotRead = fFalse;
_csto.pfil = floDst.pfil;
_csto.fpMac = floDst.fp;
_csto.pfil->SetTemp(_fInvalidMainFile || pfilOld->FTemp());
if (_cstoExtra.pfil != pvNil)
{
_cstoExtra.fpMac = 0;
ReleasePpo(&_cstoExtra.pfil);
ReleasePpo(&_cstoExtra.pglfsm);
}
// write the index
if (!_FWriteIndex(ctgCreator))
goto LIndexFail;
if (pfni != pvNil)
{
// delete any existing file with this name, then rename our output
// file to the given name
if (pfni->TExists() != tNo)
pfni->FDelete();
if (!_csto.pfil->FRename(pfni))
goto LIndexFail;
_fInvalidMainFile = fFalse;
}
else if (!_csto.pfil->FSwapNames(pfilOld))
{
LIndexFail:
if (_fInvalidMainFile)
{
// we can just use the new file now.
ReleasePpo(&pfilOld); // release our claim on the old file
}
else
{
// restore the original csto and make floDst.pfil the extra file
_csto.pfil = pfilOld;
_csto.fpMac = size(CFP);
for (icrp = 0; icrp < ccrp; icrp++)
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (qcrp->Cb() > 0)
qcrp->SetGrfcrp(fcrpOnExtra);
}
_cstoExtra.pfil = floDst.pfil;
floDst.pfil->SetTemp(fTrue);
_cstoExtra.fpMac = floDst.fp;
}
LError:
PushErc(ercCflSave);
AssertThis(fcflFull | fcflGraph);
return fFalse;
}
// everything worked
if (pfni == pvNil)
pfilOld->SetTemp(fTrue);
ReleasePpo(&pfilOld); // release our claim on the old file
AssertThis(fcflFull | fcflGraph);
return fTrue;
}
/***************************************************************************
Write the chunky index and free map to the end of the file.
***************************************************************************/
bool CFL::_FWriteIndex(CTG ctgCreator)
{
AssertBaseThis(0);
AssertPo(_pggcrp, 0);
AssertPo(_csto.pfil, 0);
CFP cfp;
BLCK blck;
ClearPb(&cfp, size(cfp));
cfp.lwMagic = klwMagicChunky;
cfp.ctgCreator = ctgCreator;
cfp.dver.Set(kcvnCur, kcvnBack);
cfp.bo = kboCur;
cfp.osk = koskCur;
blck.Set(_csto.pfil, cfp.fpIndex = _csto.fpMac,
cfp.cbIndex = _pggcrp->CbOnFile());
if (!_pggcrp->FWrite(&blck))
return fFalse;
cfp.fpMap = cfp.fpIndex + cfp.cbIndex;
if (_csto.pglfsm != pvNil)
{
AssertDo(blck.FMoveMin(cfp.cbIndex), 0);
AssertDo(blck.FMoveLim(cfp.cbMap = _csto.pglfsm->CbOnFile()), 0);
if (!_csto.pglfsm->FWrite(&blck))
return fFalse;
}
else
cfp.cbMap = 0;
cfp.fpMac = cfp.fpMap + cfp.cbMap;
return _csto.pfil->FWriteRgb(&cfp, size(cfp), 0);
}
/***************************************************************************
Save a copy of the chunky file out to *pfni. The CFL and its FIL
are untouched.
***************************************************************************/
bool CFL::FSaveACopy(CTG ctgCreator, FNI *pfni)
{
AssertThis(fcflFull | fcflGraph);
AssertPo(pfni, ffniFile);
PCFL pcflDst;
long icrp, ccrp;
CRP *pcrp;
CRP crp;
FLO floSrc, floDst;
if (pvNil == (pcflDst = CFL::PcflCreate(pfni, fcflWriteEnable)))
goto LError;
// initialize the destination FLO.
floDst.pfil = pcflDst->_csto.pfil;
floDst.fp = size(CFP);
// need to lock the _pggcrp for the FInsert operations below
ccrp = _pggcrp->IvMac();
_pggcrp->Lock();
for (icrp = 0; icrp < ccrp; icrp++, floDst.fp += floDst.cb)
{
pcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
crp = *pcrp;
// get the source and destination FLOs
floSrc.pfil = pcrp->Grfcrp(fcrpOnExtra) ? _cstoExtra.pfil : _csto.pfil;
floSrc.fp = pcrp->fp;
floDst.cb = floSrc.cb = pcrp->Cb();
// copy the data
if (!floSrc.FCopy(&floDst))
{
_pggcrp->Unlock();
goto LFail;
}
// create the index entry - the only things that change are the
// (fp, cb) and the fcrpOnExtra flag.
crp.fp = floDst.fp;
crp.SetCb(floDst.cb);
crp.ClearGrfcrp(fcrpOnExtra);
if (!pcflDst->_pggcrp->FInsert(icrp, _pggcrp->Cb(icrp),
_pggcrp->QvGet(icrp), &crp))
{
_pggcrp->Unlock();
goto LFail;
}
}
_pggcrp->Unlock();
// set the fpMac of the destination CFL
pcflDst->_csto.fpMac = floDst.fp;
if (!pcflDst->FSave(ctgCreator))
{
LFail:
pcflDst->SetTemp(fTrue);
ReleasePpo(&pcflDst);
LError:
PushErc(ercCflSaveCopy);
return fFalse;
}
ReleasePpo(&pcflDst);
return fTrue;
}
/***************************************************************************
Return whether the chunk's data is on the extra file.
***************************************************************************/
bool CFL::FOnExtra(CTG ctg, CNO cno)
{
AssertThis(0);
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
return fFalse;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
return FPure(qcrp->Grfcrp(fcrpOnExtra));
}
/***************************************************************************
Make sure the given chunk's data is on the extra file. Fails iff the
chunk doesn't exist or copying the data failed.
***************************************************************************/
bool CFL::FEnsureOnExtra(CTG ctg, CNO cno)
{
AssertThis(0);
long icrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
return fFalse;
return _FEnsureOnExtra(icrp);
}
/***************************************************************************
Make sure the given chunk's data is on the extra file. Optionally get
the flo for the data.
***************************************************************************/
bool CFL::_FEnsureOnExtra(long icrp, FLO *pflo)
{
AssertThis(0);
AssertNilOrVarMem(pflo);
CRP *qcrp;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (qcrp->Grfcrp(fcrpOnExtra) || qcrp->Cb() == 0)
{
if (pvNil != pflo)
{
pflo->pfil = _cstoExtra.pfil;
pflo->fp = qcrp->fp;
pflo->cb = qcrp->Cb();
}
}
else
{
FLO floSrc, floDst;
#ifdef CHUNK_STATS
if (vfDumpChunkRequests)
{
STN stn;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
stn.FFormatSz(
PszLit("Cache: '%f', 0x%08x, fp = 0x%08x, cb = 0x%08x"),
qcrp->cki.ctg, qcrp->cki.cno, qcrp->fp, qcrp->Cb());
DumpStn(&stn, _csto.pfil);
}
#endif //CHUNK_STATS
floSrc.pfil = _csto.pfil;
floSrc.fp = qcrp->fp;
floSrc.cb = qcrp->Cb();
if (!_FAllocFlo(floSrc.cb, &floDst, fTrue))
return fFalse;
if (!floSrc.FCopy(&floDst))
{
_FreeFpCb(fTrue, floDst.fp, floDst.cb);
return fFalse;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
qcrp->SetGrfcrp(fcrpOnExtra);
qcrp->fp = floDst.fp;
if (pvNil != pflo)
*pflo = floDst;
}
#ifdef CHUNK_STATS
if (pvNil != pflo && vfDumpChunkRequests)
{
STN stn;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
stn.FFormatSz(
PszLit("Fetch from extra: '%f', 0x%08x, fp = 0x%08x, cb = 0x%08x"),
qcrp->cki.ctg, qcrp->cki.cno, qcrp->fp, qcrp->Cb());
DumpStn(&stn, _csto.pfil);
}
#endif //CHUNK_STATS
return fTrue;
}
/***************************************************************************
Get the FLO from the chunk.
***************************************************************************/
void CFL::_GetFlo(long icrp, PFLO pflo)
{
AssertThis(0);
AssertVarMem(pflo);
if (!_fReadFromExtra || !_FEnsureOnExtra(icrp, pflo))
{
CRP *qcrp;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
pflo->pfil = qcrp->Grfcrp(fcrpOnExtra) ? _cstoExtra.pfil : _csto.pfil;
pflo->fp = qcrp->fp;
pflo->cb = qcrp->Cb();
#ifdef CHUNK_STATS
if (vfDumpChunkRequests)
{
STN stn;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
stn.FFormatSz(
PszLit("Fetch from %z: '%f', 0x%08x, fp = 0x%08x, cb = 0x%08x"),
qcrp->Grfcrp(fcrpOnExtra) ? PszLit("extra") : PszLit("main"),
qcrp->cki.ctg, qcrp->cki.cno, qcrp->fp, qcrp->Cb());
DumpStn(&stn, _csto.pfil);
}
#endif //CHUNK_STATS
}
AssertPo(pflo->pfil, 0);
}
/***************************************************************************
Get the BLCK from the chunk.
***************************************************************************/
void CFL::_GetBlck(long icrp, PBLCK pblck)
{
AssertThis(0);
AssertPo(pblck, 0);
FLO flo;
CRP *qcrp;
_GetFlo(icrp, &flo);
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
pblck->Set(&flo, qcrp->Grfcrp(fcrpPacked));
}
/***************************************************************************
Map the (ctg, cno) to a BLCK.
***************************************************************************/
bool CFL::FFind(CTG ctg, CNO cno, BLCK *pblck)
{
AssertThis(0);
AssertNilOrPo(pblck, 0);
long icrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
if (pvNil != pblck)
pblck->Free();
return fFalse;
}
if (pvNil != pblck)
_GetBlck(icrp, pblck);
return fTrue;
}
/***************************************************************************
Map the (ctg, cno) to a pflo.
***************************************************************************/
bool CFL::FFindFlo(CTG ctg, CNO cno, PFLO pflo)
{
AssertThis(0);
AssertVarMem(pflo);
long icrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
TrashVar(pflo);
return fFalse;
}
_GetFlo(icrp, pflo);
return fTrue;
}
/***************************************************************************
Reads the chunk (if it exists) into an hq. Returns false if the
chunk doesn't exist or something failed, in which case *phq is set
to hqNil. Doesn't unpack the data if it's packed.
***************************************************************************/
bool CFL::FReadHq(CTG ctg, CNO cno, HQ *phq)
{
AssertThis(0);
AssertVarMem(phq);
BLCK blck;
*phq = hqNil; //in case FFind fails
return FFind(ctg, cno, &blck) && blck.FReadHq(phq, fTrue);
}
/***************************************************************************
Make sure the packed flag is set or clear according to fPacked.
This doesn't affect the data at all.
***************************************************************************/
void CFL::SetPacked(CTG ctg, CNO cno, bool fPacked)
{
AssertThis(0);
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
return;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
Assert(qcrp->cki.ctg == ctg && qcrp->cki.cno == cno, 0);
if (FPure(qcrp->Grfcrp(fcrpPacked)) != FPure(fPacked))
{
if (fPacked)
qcrp->SetGrfcrp(fcrpPacked);
else
qcrp->ClearGrfcrp(fcrpPacked);
_FSetRti(ctg, cno, rtiNil);
}
}
/***************************************************************************
Return the value of the packed flag.
***************************************************************************/
bool CFL::FPacked(CTG ctg, CNO cno)
{
AssertThis(0);
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
return fFalse;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
Assert(qcrp->cki.ctg == ctg && qcrp->cki.cno == cno, 0);
return FPure(qcrp->Grfcrp(fcrpPacked));
}
/***************************************************************************
If the data for the chunk is packed, unpack it.
***************************************************************************/
bool CFL::FUnpackData(CTG ctg, CNO cno)
{
AssertThis(0);
BLCK blck;
if (!FPacked(ctg, cno))
return fTrue;
AssertDo(FFind(ctg, cno, &blck), 0);
if (!blck.FUnpackData())
return fFalse;
return FPutBlck(&blck, ctg, cno);
}
/***************************************************************************
If the data isn't already packed, pack it.
***************************************************************************/
bool CFL::FPackData(CTG ctg, CNO cno)
{
AssertThis(0);
BLCK blck;
if (FPacked(ctg, cno))
return fTrue;
AssertDo(FFind(ctg, cno, &blck), 0);
if (!blck.FPackData())
return fFalse;
return FPutBlck(&blck, ctg, cno);
}
/***************************************************************************
Create the extra file. Note: the extra file doesn't have a CFP -
just raw data.
***************************************************************************/
bool CFL::_FCreateExtra(void)
{
AssertBaseThis(0);
Assert(_cstoExtra.pfil == pvNil, 0);
Assert(_cstoExtra.pglfsm == pvNil, 0);
if (pvNil == (_cstoExtra.pfil = FIL::PfilCreateTemp()))
return fFalse;
_cstoExtra.fpMac = 0;
return fTrue;
}
/***************************************************************************
Find a place to put a block the given size.
***************************************************************************/
bool CFL::_FAllocFlo(long cb, PFLO pflo, bool fForceOnExtra)
{
AssertBaseThis(0);
AssertIn(cb, 0, kcbMax);
AssertVarMem(pflo);
CSTO *pcsto;
long cfsm, ifsm;
FSM *qfsm;
if (cb > kcbMaxCrp)
{
Bug("Requested FLO too big");
return fFalse;
}
if ((fForceOnExtra || _fAddToExtra) && cb > 0)
{
pcsto = &_cstoExtra;
if (pcsto->pfil == pvNil && !_FCreateExtra())
{
TrashVar(pflo);
return fFalse;
}
}
else
{
Assert(!_fFreeMapNotRead, "free map not read yet");
pcsto = &_csto;
}
// set the file and cb - just need to find an fp
AssertPo(pcsto->pfil, 0);
pflo->pfil = pcsto->pfil;
pflo->cb = cb;
if (cb <= 0)
{
pflo->fp = 0;
pflo->cb = 0; //for safety
return fTrue;
}
// look for a free spot in the free space map
if (pcsto->pglfsm != pvNil && (cfsm = pcsto->pglfsm->IvMac()) > 0)
{
qfsm = (FSM *)pcsto->pglfsm->QvGet(0);
for (ifsm = 0; ifsm < cfsm; ifsm++, qfsm++)
{
if (qfsm->cb >= cb)
{
// can put it here
pflo->fp = qfsm->fp;
if (qfsm->cb == cb)
pcsto->pglfsm->Delete(ifsm);
else
{
qfsm->cb -= cb;
qfsm->fp += cb;
}
return fTrue;
}
}
}
// put it at the end of the file
if (pcsto->fpMac + cb > pcsto->pfil->FpMac() &&
!pcsto->pfil->FSetFpMac(pcsto->fpMac + cb))
{
TrashVar(pflo);
return fFalse;
}
pflo->fp = pcsto->fpMac;
pcsto->fpMac += cb;
return fTrue;
}
/***************************************************************************
Look for the (ctg, cno) pair. Fills *picrp with where it should be.
Returns whether or not it was found. Assumes the _pggcrp is sorted
by (ctg, cno). Does a binary search.
***************************************************************************/
bool CFL::_FFindCtgCno(CTG ctg, CNO cno, long *picrp)
{
//WARNING: this is called by CFL::AssertValid, so be careful about
//asserting stuff in here
AssertBaseThis(0);
AssertVarMem(picrp);
AssertPo(_pggcrp, 0);
long ccrp, icrpMin, icrpLim, icrp;
CKI cki;
if ((ccrp = _pggcrp->IvMac()) == 0)
{
*picrp = 0;
return fFalse;
}
for (icrpMin = 0, icrpLim = ccrp; icrpMin < icrpLim; )
{
icrp = (icrpMin + icrpLim) / 2;
cki = ((CRP *)_pggcrp->QvFixedGet(icrp))->cki;
if (cki.ctg < ctg)
icrpMin = icrp + 1;
else if (cki.ctg > ctg)
icrpLim = icrp;
else if (cki.cno < cno)
icrpMin = icrp + 1;
else if (cki.cno > cno)
icrpLim = icrp;
else
{
*picrp = icrp;
return fTrue;
}
}
*picrp = icrpMin;
return fFalse;
}
/***************************************************************************
Find an unused cno for the given ctg. Fill in *picrp and *pcno.
***************************************************************************/
void CFL::_GetUniqueCno(CTG ctg, long *picrp, CNO *pcno)
{
AssertBaseThis(0);
AssertVarMem(picrp);
AssertVarMem(pcno);
long icrp, ccrp;
CNO cno;
CRP *qcrp;
if (!_FFindCtgCno(ctg, 0, picrp))
{
*pcno = 0;
return;
}
// 0 already exists so do a linear search for the first useable slot
ccrp = _pggcrp->IvMac();
for (icrp = *picrp + 1, cno = 1; icrp < ccrp; icrp++, cno++)
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (qcrp->cki.ctg != ctg || qcrp->cki.cno != cno)
break;
}
*picrp = icrp;
*pcno = cno;
}
/***************************************************************************
Add a new chunk.
***************************************************************************/
bool CFL::FAdd(long cb, CTG ctg, CNO *pcno, PBLCK pblck)
{
AssertThis(0);
AssertIn(cb, 0, kcbMax);
AssertVarMem(pcno);
AssertNilOrPo(pblck, 0);
long icrp;
_GetUniqueCno(ctg, &icrp, pcno);
if (!_FAdd(cb, ctg, *pcno, icrp, pblck))
{
TrashVar(pcno);
AssertThis(0);
return fFalse;
}
AssertThis(0);
return fTrue;
}
/***************************************************************************
Add a new chunk and write the pv to it.
***************************************************************************/
bool CFL::FAddPv(void *pv, long cb, CTG ctg, CNO *pcno)
{
AssertThis(0);
AssertVarMem(pcno);
AssertIn(cb, 0, kcbMax);
AssertPvCb(pv, cb);
BLCK blck;
if (!FAdd(cb, ctg, pcno, &blck))
return fFalse;
if (!blck.FWrite(pv))
{
Delete(ctg, *pcno);
TrashVar(pcno);
return fFalse;
}
return fTrue;
}
/***************************************************************************
Add a new chunk and write the hq to it.
***************************************************************************/
bool CFL::FAddHq(HQ hq, CTG ctg, CNO *pcno)
{
AssertThis(0);
AssertVarMem(pcno);
AssertHq(hq);
BLCK blck;
long cb = CbOfHq(hq);
if (!FAdd(cb, ctg, pcno, &blck))
return fFalse;
if (!blck.FWriteHq(hq, 0))
{
Delete(ctg, *pcno);
TrashVar(pcno);
return fFalse;
}
return fTrue;
}
/***************************************************************************
Add a new chunk and write the block to it.
***************************************************************************/
bool CFL::FAddBlck(PBLCK pblckSrc, CTG ctg, CNO *pcno)
{
AssertThis(0);
AssertPo(pblckSrc, 0);
AssertVarMem(pcno);
BLCK blck;
long cb = pblckSrc->Cb(fTrue);
if (!FAdd(cb, ctg, pcno, &blck))
return fFalse;
if (!pblckSrc->FWriteToBlck(&blck, fTrue))
{
Delete(ctg, *pcno);
TrashVar(pcno);
return fFalse;
}
if (pblckSrc->FPacked())
SetPacked(ctg, *pcno, fTrue);
return fTrue;
}
/***************************************************************************
Adds the chunk and makes it a child of (ctgPar, cnoPar). The loner flag
of the new chunk will be clear.
***************************************************************************/
bool CFL::FAddChild(CTG ctgPar, CNO cnoPar, CHID chid,
long cb, CTG ctg, CNO *pcno, PBLCK pblck)
{
AssertThis(0);
AssertVarMem(pcno);
AssertNilOrPo(pblck, 0);
if (!FAdd(cb, ctg, pcno, pblck))
return fFalse;
if (!FAdoptChild(ctgPar, cnoPar, ctg, *pcno, chid, fTrue))
{
Delete(ctg, *pcno);
TrashVar(pcno);
if (pvNil != pblck)
pblck->Free();
return fFalse;
}
return fTrue;
}
/***************************************************************************
Adds the chunk and makes it a child of (ctgPar, cnoPar). The child's
loner flag will be clear.
***************************************************************************/
bool CFL::FAddChildPv(CTG ctgPar, CNO cnoPar, CHID chid,
void *pv, long cb, CTG ctg, CNO *pcno)
{
if (!FAddPv(pv, cb, ctg, pcno))
return fFalse;
if (!FAdoptChild(ctgPar, cnoPar, ctg, *pcno, chid, fTrue))
{
Delete(ctg, *pcno);
TrashVar(pcno);
return fFalse;
}
return fTrue;
}
/***************************************************************************
Adds the chunk and makes it a child of (ctgPar, cnoPar). The child's
loner flag will be clear.
***************************************************************************/
bool CFL::FAddChildHq(CTG ctgPar, CNO cnoPar, CHID chid,
HQ hq, CTG ctg, CNO *pcno)
{
if (!FAddHq(hq, ctg, pcno))
return fFalse;
if (!FAdoptChild(ctgPar, cnoPar, ctg, *pcno, chid, fTrue))
{
Delete(ctg, *pcno);
TrashVar(pcno);
return fFalse;
}
return fTrue;
}
/***************************************************************************
Low level add. Sets the loner flag.
***************************************************************************/
bool CFL::_FAdd(long cb, CTG ctg, CNO cno, long icrp, PBLCK pblck)
{
AssertBaseThis(0);
AssertIn(cb, 0, kcbMax);
AssertNilOrPo(pblck, 0);
CRP *qcrp;
FLO flo;
if (!_pggcrp->FInsert(icrp, 0))
{
if (pvNil != pblck)
pblck->Free();
return fFalse;
}
if (!_FAllocFlo(cb, &flo))
{
_pggcrp->Delete(icrp);
AssertThis(0);
if (pvNil != pblck)
pblck->Free();
return fFalse;
}
Assert(flo.pfil == _csto.pfil || flo.pfil == _cstoExtra.pfil, 0);
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
ClearPb(qcrp, size(CRP));
qcrp->cki.ctg = ctg;
qcrp->cki.cno = cno;
qcrp->fp = flo.fp;
qcrp->SetCb(flo.cb);
if (flo.pfil == _cstoExtra.pfil)
qcrp->SetGrfcrp(fcrpOnExtra);
qcrp->SetGrfcrp(fcrpLoner);
if (pvNil != pblck)
pblck->Set(&flo);
AssertThis(0);
return fTrue;
}
/***************************************************************************
Replace or create a chunk of a particular cno.
***************************************************************************/
bool CFL::FPut(long cb, CTG ctg, CNO cno, PBLCK pblck)
{
AssertThis(0);
AssertNilOrPo(pblck, 0);
return _FPut(cb, ctg, cno, pblck, pvNil, pvNil);
}
/***************************************************************************
Replace or create a chunk with the given cno and put the data in it.
***************************************************************************/
bool CFL::FPutPv(void *pv, long cb, CTG ctg, CNO cno)
{
AssertThis(0);
AssertIn(cb, 0, kcbMax);
AssertPvCb(pv, cb);
return _FPut(cb, ctg, cno, pvNil, pvNil, pv);
}
/***************************************************************************
Replace or create a chunk with the given cno and put the hq's
data in it.
***************************************************************************/
bool CFL::FPutHq(HQ hq, CTG ctg, CNO cno)
{
AssertThis(0);
AssertHq(hq);
bool fRet;
HQ hqT = hq;
BLCK blckSrc(&hq);
fRet = _FPut(blckSrc.Cb(), ctg, cno, pvNil, &blckSrc, pvNil);
AssertDo(hqT == blckSrc.HqFree(),
"blckSrc.HqFree() returned a differnt hq!");
return fRet;
}
/***************************************************************************
Replace or create a chunk with the given cno and put the block's
data in it. Set the packed flag as in the block.
***************************************************************************/
bool CFL::FPutBlck(PBLCK pblckSrc, CTG ctg, CNO cno)
{
AssertThis(0);
AssertPo(pblckSrc, 0);
if (!_FPut(pblckSrc->Cb(fTrue), ctg, cno, pvNil, pblckSrc, pvNil))
return pvNil;
SetPacked(ctg, cno, pblckSrc->FPacked());
return fTrue;
}
/***************************************************************************
Low level put. Writes data from pblckSrc or pv (or neither). If the
chunk doesn't already exist, this has the same affect as doing an add.
If it does exist, this doesn't change the fcrpLoner flag or anything
else except the data. If pblck isn't nil, this sets it to the location
of the new data. Doesn't change the packed flag.
***************************************************************************/
bool CFL::_FPut(long cb, CTG ctg, CNO cno, PBLCK pblck, PBLCK pblckSrc,
void *pv)
{
AssertBaseThis(0);
AssertIn(cb, 0, kcbMax);
AssertNilOrPo(pblck, 0);
AssertNilOrPo(pblckSrc, 0);
long icrp;
CRP *qcrp;
FLO flo;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
BLCK blck;
if (pvNil == pblck)
pblck = &blck;
if (!_FAdd(cb, ctg, cno, icrp, pblck))
return fFalse;
if (pv != pvNil)
{
Assert(pvNil == pblckSrc, 0);
if (!pblck->FWrite(pv))
goto LFailNew;
}
else if (pvNil != pblckSrc && !pblckSrc->FWriteToBlck(pblck, fTrue))
{
LFailNew:
Delete(ctg, cno);
pblck->Free();
AssertThis(0);
return fFalse;
}
AssertThis(0);
return fTrue;
}
if (!_FAllocFlo(cb, &flo))
{
AssertThis(0);
return fFalse;
}
Assert(flo.pfil == _csto.pfil || flo.pfil == _cstoExtra.pfil, 0);
if (pvNil != pblck)
pblck->Set(&flo);
if (pv != pvNil)
{
Assert(pvNil == pblckSrc, 0);
if (!flo.FWrite(pv))
goto LFailOld;
}
else if (pvNil != pblckSrc && !pblckSrc->FWriteToFlo(&flo, fTrue))
{
LFailOld:
_FreeFpCb(_fAddToExtra, flo.fp, flo.cb);
if (pvNil != pblck)
pblck->Free();
AssertThis(0);
return fFalse;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
Assert(qcrp->cki.ctg == ctg, 0);
Assert(qcrp->cki.cno == cno, 0);
_FreeFpCb(qcrp->Grfcrp(fcrpOnExtra), qcrp->fp, qcrp->Cb());
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
qcrp->fp = flo.fp;
qcrp->SetCb(flo.cb);
if (flo.pfil == _cstoExtra.pfil)
qcrp->SetGrfcrp(fcrpOnExtra);
else
qcrp->ClearGrfcrp(fcrpOnExtra);
_FSetRti(ctg, cno, rtiNil);
AssertThis(0);
return fTrue;
}
/***************************************************************************
Swaps the data for the two chunks. This allows a "safe save" of
individual chunks (create a temp chunk, write the data, swap the data,
delete the temp chunk). Doesn't affect child/parent relationships
or the loner flag.
***************************************************************************/
void CFL::SwapData(CTG ctg1, CNO cno1, CTG ctg2, CNO cno2)
{
AssertThis(0);
long icrp1, icrp2;
CRP *qcrp1, *qcrp2;
FP fp;
long cb;
ulong grfcrpT;
const ulong kgrfcrpMask = fcrpOnExtra | fcrpPacked | fcrpForest;
if (!_FFindCtgCno(ctg1, cno1, &icrp1) ||
!_FFindCtgCno(ctg2, cno2, &icrp2))
{
Bug("can't find the chunks");
return;
}
qcrp1 = (CRP *)_pggcrp->QvFixedGet(icrp1);
qcrp2 = (CRP *)_pggcrp->QvFixedGet(icrp2);
fp = qcrp1->fp;
qcrp1->fp = qcrp2->fp;
qcrp2->fp = fp;
cb = qcrp1->Cb();
qcrp1->SetCb(qcrp2->Cb());
qcrp2->SetCb(cb);
// swap the bits of grfcrp in grfcrpMask
grfcrpT = qcrp1->Grfcrp(kgrfcrpMask);
qcrp1->AssignGrfcrp(qcrp2->Grfcrp(kgrfcrpMask), kgrfcrpMask);
qcrp2->AssignGrfcrp(grfcrpT, kgrfcrpMask);
_FSetRti(ctg1, cno1, rtiNil);
_FSetRti(ctg2, cno2, rtiNil);
AssertThis(fcflFull);
}
/***************************************************************************
Swaps the children for the two chunks. This allows a "safe save" of
chunk graphs (create a temp chunk, write the data, swap the data,
swap the children, delete the temp chunk). Doesn't affect the data
or the loner flag.
***************************************************************************/
void CFL::SwapChildren(CTG ctg1, CNO cno1, CTG ctg2, CNO cno2)
{
AssertThis(0);
long icrp1, icrp2;
CRP *qcrp1, *qcrp2;
long cb1, cb2;
if (!_FFindCtgCno(ctg1, cno1, &icrp1) ||
!_FFindCtgCno(ctg2, cno2, &icrp2))
{
Bug("can't find the chunks");
return;
}
// Swap the child lists.
qcrp1 = (CRP *)_pggcrp->QvFixedGet(icrp1);
qcrp2 = (CRP *)_pggcrp->QvFixedGet(icrp2);
cb1 = LwMul(qcrp1->ckid, size(KID));
cb2 = LwMul(qcrp2->ckid, size(KID));
SwapVars(&qcrp1->ckid, &qcrp2->ckid);
// These FMoveRgb calls won't fail, because no padding is necessary for
// child entries. (FMoveRgb can fail only if the number of bytes being
// moved is not a multiple of size(long)).
if (0 < cb1)
AssertDo(_pggcrp->FMoveRgb(icrp1, 0, icrp2, cb2, cb1), 0);
if (0 < cb2)
AssertDo(_pggcrp->FMoveRgb(icrp2, 0, icrp1, 0, cb2), 0);
AssertThis(fcflFull);
}
/***************************************************************************
Move the chunk from (ctg, cno) to (ctgNew, cnoNew). Asserts that there
is not already a chunk labelled (ctgNew, cnoNew). If the chunk has
parents, updates the parent links to point to (ctgNew, cnoNew).
Adjusting the links is slow.
***************************************************************************/
void CFL::Move(CTG ctg, CNO cno, CTG ctgNew, CNO cnoNew)
{
AssertThis(0);
long icrpCur, icrpTarget;
CRP *qcrp;
long ccrpRef;
long rti;
if (ctg == ctgNew && cno == cnoNew)
return;
if (!_FFindCtgCno(ctg, cno, &icrpCur) ||
_FFindCtgCno(ctgNew, cnoNew, &icrpTarget))
{
Bug("bad move request");
return;
}
rti = _Rti(ctg, cno);
if (rtiNil != rti)
_FSetRti(ctg, cno, rtiNil);
qcrp = (CRP *)_pggcrp->QvFixedGet(icrpCur);
ccrpRef = qcrp->ccrpRef;
qcrp->cki.ctg = ctgNew;
qcrp->cki.cno = cnoNew;
_pggcrp->Move(icrpCur, icrpTarget);
if (ccrpRef > 0)
{
// chunk has some parents
CRP crp;
long icrp, ikid, ikidNew;
KID *qkid, *qrgkid;
//In debug, increment ccrpRef so we'll traverse the entire
//index. In ship, we'll stop once we changed ccrpRef KIDs.
Debug( ccrpRef++; )
for (icrp = _pggcrp->IvMac(); icrp-- > 0 && ccrpRef > 0; )
{
_pggcrp->GetFixed(icrp, &crp);
qkid = (KID *)_pggcrp->QvGet(icrp);
for (ikid = 0; ikid < crp.ckid; )
{
if (qkid->cki.ctg != ctg || qkid->cki.cno != cno)
{
ikid++;
qkid++;
continue;
}
//replace this kid
AssertDo(!_FFindChild(icrp, ctgNew, cnoNew, qkid->chid,
&ikidNew), "already a child");
//refresh the qkid and qrgkid pointers
qkid = (qrgkid = (KID *)_pggcrp->QvGet(icrp)) + ikid;
qkid->cki.ctg = ctgNew;
qkid->cki.cno = cnoNew;
MoveElement(qrgkid, size(KID), ikid, ikidNew);
ccrpRef--;
}
}
Assert(ccrpRef == 1, "corrupt chunky index");
//in ship, ccrpRef should be 0 here
}
if (rtiNil != rti && ctgNew == ctg)
_FSetRti(ctgNew, cnoNew, rti);
AssertThis(fcflFull);
}
/***************************************************************************
Delete the given chunk. Handles deleting child chunks that are
no longer referenced. If the chunk has the loner flag set, this clears
it. If the chunk has no parents, the chunk is also physically deleted
from the CFL.
***************************************************************************/
void CFL::Delete(CTG ctg, CNO cno)
{
AssertThis(0);
long icrp;
CGE cge;
ulong grfcgeIn, grfcge;
KID kid;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
return;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
Assert(qcrp->Grfcrp(fcrpLoner) || qcrp->ccrpRef == 0,
"can't directly delete a child chunk");
qcrp->ClearGrfcrp(fcrpLoner);
if (qcrp->ccrpRef > 0)
return;
cge.Init(this, ctg, cno);
for (grfcgeIn = fcgeNil; cge.FNextKid(&kid, pvNil, &grfcge, grfcgeIn); )
{
grfcgeIn = fcgeSkipToSib;
if (!_FFindCtgCno(kid.cki.ctg, kid.cki.cno, &icrp))
{
Bug("MIA");
continue;
}
if (grfcge & fcgePre)
{
if (!(grfcge & fcgeRoot) && !_FDecRefCount(icrp))
continue;
grfcgeIn = fcgeNil;
}
if (grfcge & fcgePost)
{
// actually delete the node
if (grfcge & fcgeError)
{
Warn("memory failure in CFL::Delete - adjusting ref counts");
//memory failure - adjust the ref counts of this chunk's
//children, but don't try to delete them
long ikid, icrpChild;
KID kid;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
for (ikid = qcrp->ckid; ikid-- != 0; )
{
_pggcrp->GetRgb(icrp, _BvKid(ikid), size(kid), &kid);
if (!_FFindCtgCno(kid.cki.ctg, kid.cki.cno, &icrpChild))
{
Bug("child not there");
continue;
}
_FDecRefCount(icrpChild);
}
}
_DeleteCore(icrp);
}
}
AssertThis(fcflFull);
}
/***************************************************************************
Make sure the loner flag is set or clear according to fLoner. If
fLoner is false and (ctg, cno) is not currently the child of anything,
it will be deleted.
***************************************************************************/
void CFL::SetLoner(CTG ctg, CNO cno, bool fLoner)
{
AssertThis(0);
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
return;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (fLoner)
qcrp->SetGrfcrp(fcrpLoner);
else if (qcrp->Grfcrp(fcrpLoner))
Delete(ctg, cno);
}
/***************************************************************************
Return the value of the loner flag.
***************************************************************************/
bool CFL::FLoner(CTG ctg, CNO cno)
{
AssertThis(0);
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
return fFalse;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
return FPure(qcrp->Grfcrp(fcrpLoner));
}
/***************************************************************************
Returns the number of parents of this chunk
***************************************************************************/
long CFL::CckiRef(CTG ctg, CNO cno)
{
AssertThis(0);
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
return 0;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
return qcrp->ccrpRef;
}
/***************************************************************************
Determines if (ctgSub, cnoSub) is in the chunk subgraph of (ctg, cno).
Returns tMaybe on error.
***************************************************************************/
bool CFL::TIsDescendent(CTG ctg, CNO cno, CTG ctgSub, CNO cnoSub)
{
AssertThis(0);
CGE cge;
KID kid;
ulong grfcge;
if (CckiRef(ctgSub, cnoSub) == 0)
return tNo;
cge.Init(this, ctg, cno);
while (cge.FNextKid(&kid, pvNil, &grfcge, fcgeNil))
{
if (kid.cki.ctg == ctgSub && kid.cki.cno == cnoSub)
return tYes;
if (grfcge & fcgeError)
return tMaybe;
}
return tNo;
}
/***************************************************************************
Delete the given child chunk. Handles deleting child chunks that are
no longer referenced and don't have the fcrpLoner flag set.
***************************************************************************/
void CFL::DeleteChild(CTG ctgPar, CNO cnoPar, CTG ctgChild, CNO cnoChild,
CHID chid)
{
AssertThis(0);
long icrpPar, icrpChild, ikid;
CRP *qcrp;
if (!_FFindCtgCno(ctgPar, cnoPar, &icrpPar) ||
!_FFindCtgCno(ctgChild, cnoChild, &icrpChild))
{
Bug("chunk not there");
return;
}
if (!_FFindChild(icrpPar, ctgChild, cnoChild, chid, &ikid))
{
Bug("not a child");
return;
}
// remove the reference
qcrp = (CRP *)_pggcrp->QvFixedGet(icrpPar);
qcrp->ckid--;
_pggcrp->DeleteRgb(icrpPar, _BvKid(ikid), size(KID));
// now decrement the ref count and nuke it if the ref count is zero
if (_FDecRefCount(icrpChild))
{
// delete the chunk
Delete(ctgChild, cnoChild);
}
AssertThis(fcflFull);
}
/***************************************************************************
Decrements the reference count on the chunk. Return true if the ref
count becomes zero (after decrementing) and fcrpLoner is not set.
***************************************************************************/
bool CFL::_FDecRefCount(long icrp)
{
AssertBaseThis(0);
CRP *qcrp;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (qcrp->ccrpRef <= 0)
{
Bug("ref count wrong");
return fFalse;
}
return --(qcrp->ccrpRef) == 0 && !qcrp->Grfcrp(fcrpLoner);
}
/***************************************************************************
Remove entry icrp from _pggcrp and add the file space to the free map.
***************************************************************************/
void CFL::_DeleteCore(long icrp)
{
AssertBaseThis(0);
CRP *qcrp;
CKI cki;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
cki = qcrp->cki;
_FreeFpCb(qcrp->Grfcrp(fcrpOnExtra), qcrp->fp, qcrp->Cb());
_FSetRti(cki.ctg, cki.cno, rtiNil);
_pggcrp->Delete(icrp);
}
/***************************************************************************
Add the (fp, cb) to the free map.
***************************************************************************/
void CFL::_FreeFpCb(bool fOnExtra, FP fp, long cb)
{
AssertBaseThis(0);
Assert(cb > 0 || cb == 0 && fp == 0, "bad cb");
Assert(fp >= 0 && (fOnExtra || fp >= size(CFP) ||
cb == 0 || _fInvalidMainFile), "bad fp");
PGL pglfsm;
long ifsm, ifsmMin, ifsmLim;
FSM fsm, fsmT;
CSTO *pcsto;
// no space allocated to the chunk
if (cb == 0)
return;
// if the free map hasn't been read yet, read it now
if (!fOnExtra && _fFreeMapNotRead)
_ReadFreeMap();
pcsto = fOnExtra ? &_cstoExtra : &_csto;
pglfsm = pcsto->pglfsm;
AssertPo(pcsto->pfil, 0);
Assert(fp + cb <= pcsto->fpMac, "bad (fp,cb)");
if (fp + cb >= pcsto->fpMac)
{
// it's at the end of the file, just change fpMac and
// compact the free map if possible
pcsto->fpMac = fp;
if (pglfsm == pvNil || (ifsm = pglfsm->IvMac()) == 0)
return;
ifsm--;
pglfsm->Get(ifsm, &fsm);
if (fsm.fp + fsm.cb >= pcsto->fpMac)
{
// fsm extends to the new end of the file, so delete the fsm
// and adjust fpMac
Assert(fsm.fp + fsm.cb == pcsto->fpMac, "bad fsm?");
pglfsm->Delete(ifsm);
pcsto->fpMac = fsm.fp;
}
return;
}
// Chunk is not at the end of the file. We need to add it
// to the free map.
if (pglfsm == pvNil &&
(pglfsm = pcsto->pglfsm = GL::PglNew(size(FSM), 1)) == pvNil)
{
// can't create the free map, just drop the space
return;
}
for (ifsmMin = 0, ifsmLim = pglfsm->IvMac(); ifsmMin < ifsmLim; )
{
ifsm = (ifsmMin + ifsmLim) / 2;
pglfsm->Get(ifsm, &fsm);
Assert(fp + cb <= fsm.fp || fp >= fsm.fp + fsm.cb,
"freeing space that overlaps free space");
if (fsm.fp < fp)
ifsmMin = ifsm + 1;
else
ifsmLim = ifsm;
}
ifsmLim = pglfsm->IvMac();
if (ifsmMin > 0)
{
// check for adjacency to previous free block
pglfsm->Get(ifsmMin - 1, &fsm);
Assert(fsm.fp < fp, "bad ifsmMin");
if (fsm.fp + fsm.cb >= fp)
{
// extend the previous free block
Assert(fsm.fp + fsm.cb == fp, "overlap");
fsm.cb = fp + cb - fsm.fp;
if (ifsmMin < ifsmLim)
{
// check for adjacency to next free block
pglfsm->Get(ifsmMin, &fsmT);
if (fsmT.fp <= fsm.fp + fsm.cb)
{
// merge the two
Assert(fsmT.fp == fsm.fp + fsm.cb, "overlap");
fsm.cb = fsmT.fp + fsmT.cb - fsm.fp;
pglfsm->Delete(ifsmMin);
}
}
pglfsm->Put(ifsmMin - 1, &fsm);
return;
}
}
if (ifsmMin < ifsmLim)
{
pglfsm->Get(ifsmMin, &fsm);
Assert(fsm.fp > fp, "bad ifsmMin");
if (fsm.fp <= fp + cb)
{
Assert(fsm.fp == fp + cb, "overlap");
fsm.cb = fsm.fp + fsm.cb - fp;
fsm.fp = fp;
pglfsm->Put(ifsmMin, &fsm);
return;
}
}
fsm.fp = fp;
fsm.cb = cb;
//if it fails, we lose some space - so what
pglfsm->FInsert(ifsmMin, &fsm);
}
/***************************************************************************
Set the name of the chunk.
***************************************************************************/
bool CFL::FSetName(CTG ctg, CNO cno, PSTN pstn)
{
AssertThis(0);
AssertNilOrPo(pstn, 0);
long icrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
return fFalse;
}
if (!_FSetName(icrp, pstn))
return fFalse;
_FSetRti(ctg, cno, rtiNil);
AssertThis(0);
return fTrue;
}
/***************************************************************************
Set the name of the chunk at the given index.
***************************************************************************/
bool CFL::_FSetName(long icrp, PSTN pstn)
{
AssertBaseThis(0);
AssertIn(icrp, 0, _pggcrp->IvMac());
AssertNilOrPo(pstn, 0);
long cbVar, cbOld, cbNew;
CRP *qcrp;
long bvRgch;
if (pstn != pvNil && pstn->Cch() > 0)
cbNew = pstn->CbData();
else
cbNew = 0;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp, &cbVar);
bvRgch = qcrp->BvRgch();
cbOld = cbVar - bvRgch;
AssertIn(cbOld, 0, kcbMaxDataStn + 1);
if (cbOld > cbNew)
_pggcrp->DeleteRgb(icrp, bvRgch, cbOld - cbNew);
else if (cbOld < cbNew && !_pggcrp->FInsertRgb(icrp, bvRgch,
cbNew - cbOld, pvNil))
{
return fFalse;
}
if (cbNew > 0)
{
pstn->GetData(PvAddBv(_pggcrp->PvLock(icrp), bvRgch));
_pggcrp->Unlock();
}
return fTrue;
}
/***************************************************************************
Retrieve the name of the chunk. Returns whether the string is
non-empty.
***************************************************************************/
bool CFL::FGetName(CTG ctg, CNO cno, PSTN pstn)
{
AssertThis(0);
AssertPo(pstn, 0);
long icrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not there");
pstn->SetNil();
return fFalse;
}
return _FGetName(icrp, pstn);
}
/***************************************************************************
Retrieve the name of the chunk at the given index. Returns whether
the string is non-empty.
***************************************************************************/
bool CFL::_FGetName(long icrp, PSTN pstn)
{
AssertBaseThis(0);
AssertIn(icrp, 0, _pggcrp->IvMac());
AssertPo(pstn, 0);
long cbRgch, bvRgch;
long cbVar;
CRP *qcrp;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp, &cbVar);
cbRgch = qcrp->CbRgch(cbVar);
AssertIn(cbRgch, 0, kcbMaxDataStn + 1);
if (cbRgch <= 0)
{
pstn->SetNil();
return fFalse;
}
bvRgch = qcrp->BvRgch();
if (!pstn->FSetData(PvAddBv(_pggcrp->PvLock(icrp), bvRgch), cbRgch))
pstn->SetNil();
_pggcrp->Unlock();
return pstn->Cch() > 0;
}
/***************************************************************************
Make a node a child of another node. If fClearLoner is set, the loner
flag of the child is cleared.
***************************************************************************/
bool CFL::FAdoptChild(CTG ctgPar, CNO cnoPar, CTG ctgChild, CNO cnoChild,
CHID chid, bool fClearLoner)
{
AssertThis(0);
long icrpPar;
long ikid;
if (!_FFindCtgCno(ctgPar, cnoPar, &icrpPar))
{
Bug("parent not there");
return fFalse;
}
if (_FFindChild(icrpPar, ctgChild, cnoChild, chid, &ikid))
return fTrue; //already a child
if (!_FAdoptChild(icrpPar, ikid, ctgChild, cnoChild, chid, fClearLoner))
{
AssertThis(0);
return fFalse;
}
AssertThis(0);
return fTrue;
}
/***************************************************************************
Make (ctgChild, cnoChild) a child of icrpPar.
***************************************************************************/
bool CFL::_FAdoptChild(long icrpPar, long ikid, CTG ctgChild, CNO cnoChild,
CHID chid, bool fClearLoner)
{
AssertBaseThis(0);
AssertIn(icrpPar, 0, _pggcrp->IvMac());
long icrp;
CRP *qcrp;
KID kid;
if (!_FFindCtgCno(ctgChild, cnoChild, &icrp))
{
Bug("child not there");
return fFalse;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrpPar);
AssertIn(ikid, 0, (long)qcrp->ckid + 1);
if (tNo != TIsDescendent(ctgChild, cnoChild, qcrp->cki.ctg, qcrp->cki.cno))
{
Warn("Performing this adoption may cause a cycle");
return fFalse;
}
kid.cki.ctg = ctgChild;
kid.cki.cno = cnoChild;
kid.chid = chid;
if (!_pggcrp->FInsertRgb(icrpPar, _BvKid(ikid), size(KID), &kid))
{
AssertThis(0);
return fFalse;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrpPar);
if (qcrp->ckid >= kckidMax)
goto LOverFlow;
qcrp->ckid++;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (qcrp->ccrpRef >= kckidMax)
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrpPar);
qcrp->ckid--;
LOverFlow:
_pggcrp->DeleteRgb(icrpPar, _BvKid(ikid), size(KID));
AssertThis(0);
return fFalse;
}
qcrp->ccrpRef++;
if (fClearLoner)
qcrp->ClearGrfcrp(fcrpLoner);
AssertThis(0);
return fTrue;
}
/***************************************************************************
Changes the chid value of the given child link. Assert that
(ctgChild, cnoChild, chidOld) is a child of (ctgPar, cnoPar) and that
(ctgChild, cnoChild, chidNew) is not currently a child.
***************************************************************************/
void CFL::ChangeChid(CTG ctgPar, CNO cnoPar, CTG ctgChild, CNO cnoChild,
CHID chidOld, CHID chidNew)
{
AssertThis(0);
long icrp, ikidOld, ikidNew;
KID *qkid, *qrgkid;
if (chidOld == chidNew)
return;
if (!_FFindCtgCno(ctgPar, cnoPar, &icrp))
{
Bug("parent not there");
return;
}
if (!_FFindChild(icrp, ctgChild, cnoChild, chidOld, &ikidOld) ||
_FFindChild(icrp, ctgChild, cnoChild, chidNew, &ikidNew))
{
Bug("src not a child or dest already a child");
return;
}
qkid = (qrgkid = (KID *)_pggcrp->QvGet(icrp)) + ikidOld;
qkid->chid = chidNew;
MoveElement(qrgkid, size(KID), ikidOld, ikidNew);
AssertThis(0);
}
/***************************************************************************
Return the total number of chunks.
***************************************************************************/
long CFL::Ccki(void)
{
AssertThis(0);
return _pggcrp->IvMac();
}
/***************************************************************************
Finds the icki'th chunk. If there is such a chunk (icki isn't too big),
fills in *pcki and *pckid and returns true. Otherwise, returns fFalse.
***************************************************************************/
bool CFL::FGetCki(long icki, CKI *pcki, long *pckid, PBLCK pblck)
{
AssertThis(0);
AssertNilOrVarMem(pcki);
AssertNilOrVarMem(pckid);
AssertNilOrPo(pblck, 0);
AssertIn(icki, 0, kcbMax);
CRP *qcrp;
if (!FIn(icki, 0, _pggcrp->IvMac()))
{
TrashVar(pcki);
TrashVar(pckid);
if (pvNil != pblck)
pblck->Free();
return fFalse;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icki);
if (pvNil != pcki)
*pcki = qcrp->cki;
if (pvNil != pckid)
*pckid = qcrp->ckid;
if (pvNil != pblck)
_GetBlck(icki, pblck);
return fTrue;
}
/***************************************************************************
Finds the icki corresponding to the given (ctg, cno). If the (ctg, cno)
is not in the CFL, fills *picki with where it would be.
***************************************************************************/
bool CFL::FGetIcki(CTG ctg, CNO cno, long *picki)
{
AssertThis(0);
AssertVarMem(picki);
return _FFindCtgCno(ctg, cno, picki);
}
/***************************************************************************
Return the total number of chunks of the given type in the file.
***************************************************************************/
long CFL::CckiCtg(CTG ctg)
{
AssertThis(0);
long icrpMac;
long icrpMin;
long icrpLim;
if ((icrpMac = _pggcrp->IvMac()) == 0)
return 0;
_FFindCtgCno(ctg, 0, &icrpMin);
if (ctg + 1 < ctg)
{
// ctg is the largest possible ctg!
return icrpMac - icrpMin;
}
_FFindCtgCno(ctg + 1, 0, &icrpLim);
return icrpLim - icrpMin;
}
/***************************************************************************
Finds the icki'th chunk of type ctg. If there is such a chunk,
fills in *pcki and returns true. Otherwise, returns fFalse.
***************************************************************************/
bool CFL::FGetCkiCtg(CTG ctg, long icki, CKI *pcki, long *pckid, PBLCK pblck)
{
AssertThis(0);
AssertIn(icki, 0, kcbMax);
AssertNilOrVarMem(pcki);
AssertNilOrVarMem(pckid);
AssertNilOrPo(pblck, 0);
CRP *qcrp;
long icrpMac;
long icrp;
if (!FIn(icki, 0, (icrpMac = _pggcrp->IvMac())))
goto LFail;
_FFindCtgCno(ctg, 0, &icrp);
icrp += icki;
if (!FIn(icrp, 0, icrpMac))
goto LFail;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (qcrp->cki.ctg != ctg)
{
LFail:
TrashVar(pcki);
TrashVar(pckid);
if (pvNil != pblck)
pblck->Free();
return fFalse;
}
if (pvNil != pcki)
*pcki = qcrp->cki;
if (pvNil != pckid)
*pckid = qcrp->ckid;
if (pvNil != pblck)
_GetBlck(icki, pblck);
return fTrue;
}
/***************************************************************************
Return the number of children of the given chunk.
***************************************************************************/
long CFL::Ckid(CTG ctg, CNO cno)
{
AssertThis(0);
CRP *qcrp;
long icrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not found");
return 0;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
return qcrp->ckid;
}
/***************************************************************************
If ikid is less than number of children of the given chunk,
fill *pkid and return true. Otherwise, return false.
***************************************************************************/
bool CFL::FGetKid(CTG ctg, CNO cno, long ikid, KID *pkid)
{
AssertThis(0);
AssertVarMem(pkid);
AssertIn(ikid, 0, kcbMax);
CRP *qcrp;
long icrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
{
Bug("chunk not found");
TrashVar(pkid);
return fFalse;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (!FIn(ikid, 0, qcrp->ckid))
{
TrashVar(pkid);
return fFalse;
}
_pggcrp->GetRgb(icrp, _BvKid(ikid), size(KID), pkid);
Assert(_FFindCtgCno(pkid->cki.ctg, pkid->cki.cno, &icrp),
"child not there");
Assert(pkid->cki.ctg != ctg || pkid->cki.cno != cno,
"chunk is child of itself!");
return fTrue;
}
/***************************************************************************
Look for a child of (ctgPar, cnoPar) with the given chid value. If one
is found, fill in *pkid and return true; else return false. Kid's are
sorted by (chid, ctg, cno).
***************************************************************************/
bool CFL::FGetKidChid(CTG ctgPar, CNO cnoPar, CHID chid, KID *pkid)
{
AssertThis(0);
AssertVarMem(pkid);
return _FFindChidCtg(ctgPar, cnoPar, chid, (CTG)0, pkid);
}
/***************************************************************************
Look for a child of (ctgPar, cnoPar) with the given chid and ctg value.
If one is found, fill in *pkid and return true; else return false.
Kid's are sorted by (chid, ctg, cno).
***************************************************************************/
bool CFL::FGetKidChidCtg(CTG ctgPar, CNO cnoPar, CHID chid, CTG ctg, KID *pkid)
{
AssertThis(0);
AssertVarMem(pkid);
//the kid returned from _FFindChidCtg should have ctg >= the given ctg,
//but not necessarily equal
if (!_FFindChidCtg(ctgPar, cnoPar, chid, ctg, pkid) ||
pkid->cki.ctg != ctg)
{
TrashVar(pkid);
return fFalse;
}
return fTrue;
}
/***************************************************************************
Find the first child with the given chid and with ctg >= the given ctg.
Returns true iff there is such a child and fills in the *pkid.
***************************************************************************/
bool CFL::_FFindChidCtg(CTG ctgPar, CNO cnoPar, CHID chid, CTG ctg, KID *pkid)
{
AssertThis(0);
AssertVarMem(pkid);
long ikidMin, ikidLim, ikid;
CRP *qcrp;
KID *qrgkid, *qkid;
long ckid, icrp;
if (!_FFindCtgCno(ctgPar, cnoPar, &icrp))
{
Bug("chunk not found");
goto LFail;
}
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if ((ckid = qcrp->ckid) <= 0)
{
Assert(0 == ckid, "bad crp");
goto LFail;
}
qrgkid = (KID *)_pggcrp->QvGet(icrp);
for (ikidMin = 0, ikidLim = ckid; ikidMin < ikidLim; )
{
ikid = (ikidMin + ikidLim) / 2;
qkid = &qrgkid[ikid];
if (qkid->chid < chid)
ikidMin = ikid + 1;
else if (qkid->chid > chid)
ikidLim = ikid;
else if (qkid->cki.ctg < ctg)
ikidMin = ikid + 1;
else
ikidLim = ikid;
}
if (ikidMin < qcrp->ckid)
{
qkid = &qrgkid[ikidMin];
if (qkid->chid == chid && qkid->cki.ctg >= ctg)
{
*pkid = *qkid;
return fTrue;
}
}
LFail:
TrashVar(pkid);
return fFalse;
}
/***************************************************************************
Finds the ikid value associated with the given child (ctg, cno, chid) of
the given chunk. If the (ctg, cno, chid) is not a child of
(ctgPar, cnoPar), fills *pikid with where it would be if it were.
***************************************************************************/
bool CFL::FGetIkid(CTG ctgPar, CNO cnoPar, CTG ctg, CNO cno, CHID chid,
long *pikid)
{
AssertThis(0);
AssertVarMem(pikid);
long icrp;
if (!_FFindCtgCno(ctgPar, cnoPar, &icrp))
{
Bug("chunk not found");
*pikid = 0;
return fFalse;
}
return _FFindChild(icrp, ctg, cno, chid, pikid);
}
/***************************************************************************
If (ctgChild, cnoChild, chid) is a child of icrpPar, return true and put
the index in *pikid. If not set *pikid to where to insert it. Kids are
sorted by (chid, ctg, cno).
***************************************************************************/
bool CFL::_FFindChild(long icrpPar, CTG ctgChild, CNO cnoChild, CHID chid,
long *pikid)
{
AssertBaseThis(0);
AssertVarMem(pikid);
AssertIn(icrpPar, 0, _pggcrp->IvMac());
long ikidMin, ikidLim, ikid, ckid;
CRP *qcrp;
KID *qrgkid, *qkid;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrpPar);
if ((ckid = qcrp->ckid) <= 0)
{
Assert(0 == ckid, "bad crp");
*pikid = 0;
return fFalse;
}
qrgkid = (KID *)_pggcrp->QvGet(icrpPar);
for (ikidMin = 0, ikidLim = ckid; ikidMin < ikidLim; )
{
ikid = (ikidMin + ikidLim) / 2;
qkid = &qrgkid[ikid];
if (qkid->chid < chid)
ikidMin = ikid + 1;
else if (qkid->chid > chid)
ikidLim = ikid;
else if (qkid->cki.ctg < ctgChild)
ikidMin = ikid + 1;
else if (qkid->cki.ctg > ctgChild)
ikidLim = ikid;
else if (qkid->cki.cno < cnoChild)
ikidMin = ikid + 1;
else if (qkid->cki.cno > cnoChild)
ikidLim = ikid;
else
{
*pikid = ikid;
return fTrue;
}
}
*pikid = ikidMin;
return fFalse;
}
// cno map entry
struct CNOM
{
CTG ctg;
CNO cnoSrc;
CNO cnoDst;
};
bool _FFindCnom(PGL pglcnom, CTG ctg, CNO cno, CNOM *pcnom = pvNil,
long *picnom = pvNil);
bool _FAddCnom(PGL *ppglcnom, CNOM *pcnom);
/***************************************************************************
Look for a cnom for the given (ctg, cno). Whether or not it exists,
fill *picnom with where it would go in the pglcnom.
***************************************************************************/
bool _FFindCnom(PGL pglcnom, CTG ctg, CNO cno, CNOM *pcnom, long *picnom)
{
AssertNilOrPo(pglcnom, 0);
AssertNilOrVarMem(pcnom);
AssertNilOrVarMem(picnom);
long ivMin, ivLim, iv;
CNOM cnom;
if (pvNil == pglcnom)
{
TrashVar(pcnom);
if (pvNil != picnom)
*picnom = 0;
return fFalse;
}
for (ivMin = 0, ivLim = pglcnom->IvMac(); ivMin < ivLim; )
{
iv = (ivMin + ivLim) / 2;
pglcnom->Get(iv, &cnom);
if (cnom.ctg < ctg)
ivMin = iv + 1;
else if (cnom.ctg > ctg)
ivLim = iv;
else if (cnom.cnoSrc < cno)
ivMin = iv + 1;
else if (cnom.cnoSrc > cno)
ivLim = iv;
else
{
if (pvNil != pcnom)
*pcnom = cnom;
if (pvNil != picnom)
*picnom = iv;
return fTrue;
}
}
TrashVar(pcnom);
if (pvNil != picnom)
*picnom = ivMin;
return fFalse;
}
/***************************************************************************
Add a cnom to the *ppglcnom. Allocated *ppglcnom if it is nil.
***************************************************************************/
bool _FAddCnom(PGL *ppglcnom, CNOM *pcnom)
{
AssertVarMem(ppglcnom);
AssertNilOrPo(*ppglcnom, 0);
AssertVarMem(pcnom);
long icnom;
if (pvNil == *ppglcnom && pvNil == (*ppglcnom = GL::PglNew(size(CNOM))))
return fFalse;
AssertDo(!_FFindCnom(*ppglcnom, pcnom->ctg, pcnom->cnoSrc, pvNil, &icnom),
"why is this cnom already in the gl?");
return (*ppglcnom)->FInsert(icnom, pcnom);
}
/***************************************************************************
Copy a chunk (ctgSrc, cnoSrc) from this chunky file to pcflDst.
The new cno is put in *pcno. The destination chunk is marked as a
loner. If possible, the cno in the destination file will be the same
as the cno in the source file. If fClone is set, no chunk sharing will
occur. Otherwise, this does intelligent chunk sharing.
***************************************************************************/
bool CFL::_FCopy(CTG ctgSrc, CNO cnoSrc, PCFL pcflDst, CNO *pcnoDst,
bool fClone)
{
AssertThis(fcflFull);
AssertPo(pcflDst, fcflFull);
AssertVarMem(pcnoDst);
long icrpSrc, icrpDst;
long rtiSrc;
CGE cge;
KID kid;
CKI ckiPar;
BLCK blckSrc;
ulong grfcge, grfcgeIn;
CNOM cnom, cnomPar;
STN stn;
CRP *qcrp;
bool fFreeDstOnFailure = fFalse;
PGL pglcnom = pvNil;
bool fRet = fFalse;
if (!_FFindCtgCno(ctgSrc, cnoSrc, &icrpSrc))
{
Bug("chunk not found");
TrashVar(pcnoDst);
return fFalse;
}
if (!fClone && this == pcflDst)
{
SetLoner(ctgSrc, cnoSrc, fTrue);
*pcnoDst = cnoSrc;
return fTrue;
}
// copy chunks to the destination CFL
cge.Init(this, ctgSrc, cnoSrc);
grfcgeIn = fcgeNil;
while (cge.FNextKid(&kid, &ckiPar, &grfcge, grfcgeIn))
{
grfcgeIn = fcgeNil;
if (grfcge & fcgeError)
goto LFail;
// do pre-order handling only
if (!(grfcge & fcgePre))
continue;
if (_FFindCnom(pglcnom, kid.cki.ctg, kid.cki.cno, &cnom))
{
// chunk has already been copied - just link it to the parent
// and skip to its sibling
Assert(!(grfcge & fcgeRoot),
"how can the root already have been copied?");
grfcgeIn = fcgeSkipToSib;
}
else if (rtiNil != (rtiSrc = _Rti(kid.cki.ctg, kid.cki.cno)) &&
!fClone &&
_FFindMatch(kid.cki.ctg, kid.cki.cno, pcflDst, &cnom.cnoDst))
{
// chunk and its subgraph exists in the destination, just link it
// to the parent and skip to its sibling
grfcgeIn = fcgeSkipToSib;
}
else
{
// must copy the chunk
// assign the source chunk an rti (if it doesn't have one)
if (rtiNil == rtiSrc &&
_FSetRti(kid.cki.ctg, kid.cki.cno, _rtiLast + 1))
{
rtiSrc = ++_rtiLast;
}
cnom.ctg = kid.cki.ctg;
cnom.cnoSrc = kid.cki.cno;
// find the source icrp
AssertDo(_FFindCtgCno(kid.cki.ctg, kid.cki.cno, &icrpSrc), 0);
// get the source blck
_GetBlck(icrpSrc, &blckSrc);
// allocate the dst chunk and copy the data - use the source cno
// if possible
if (this != pcflDst && !pcflDst->FFind(kid.cki.ctg, kid.cki.cno))
{
// can preserve cno
cnom.cnoDst = kid.cki.cno;
if (!pcflDst->FPutBlck(&blckSrc, kid.cki.ctg, cnom.cnoDst))
goto LFail;
}
else
{
if (!pcflDst->FAddBlck(&blckSrc, kid.cki.ctg, &cnom.cnoDst))
goto LFail;
if (this == pcflDst)
{
AssertDo(
_FFindCtgCno(kid.cki.ctg, kid.cki.cno, &icrpSrc), 0);
}
}
AssertDo(pcflDst->_FFindCtgCno(kid.cki.ctg, cnom.cnoDst, &icrpDst),
"_FFindCtgCno doesn't work");
// make sure the forest flags match
qcrp = (CRP *)_pggcrp->QvFixedGet(icrpSrc);
if (qcrp->Grfcrp(fcrpForest))
{
qcrp = (CRP *)pcflDst->_pggcrp->QvFixedGet(icrpDst);
qcrp->SetGrfcrp(fcrpForest);
}
// set the dst name and rti to the src name and rti
if (_FGetName(icrpSrc, &stn) && !pcflDst->_FSetName(icrpDst, &stn))
{
pcflDst->Delete(kid.cki.ctg, cnom.cnoDst);
goto LFail;
}
if (rtiNil != rtiSrc)
pcflDst->_FSetRti(kid.cki.ctg, cnom.cnoDst, rtiSrc);
if (!_FAddCnom(&pglcnom, &cnom))
goto LFail;
fFreeDstOnFailure = fTrue;
}
// if it's the root, it has no parent and we need to set *pcnoDst
if (grfcge & fcgeRoot)
{
*pcnoDst = cnom.cnoDst;
continue;
}
// get the source parent's cnom
AssertDo(_FFindCnom(pglcnom, ckiPar.ctg, ckiPar.cno, &cnomPar), 0);
// make sure the dst child is a child of the dst parent
if (!pcflDst->FAdoptChild(ckiPar.ctg, cnomPar.cnoDst,
kid.cki.ctg, cnom.cnoDst, kid.chid, grfcgeIn == fcgeNil))
{
if (grfcgeIn == fcgeNil)
pcflDst->Delete(kid.cki.ctg, cnom.cnoDst);
goto LFail;
}
}
fRet = fTrue;
pcflDst->SetLoner(ctgSrc, *pcnoDst, fTrue);
LFail:
AssertThis(fcflFull);
AssertPo(pcflDst, fcflFull);
ReleasePpo(&pglcnom);
if (!fRet && fFreeDstOnFailure)
pcflDst->Delete(ctgSrc, *pcnoDst);
TrashVarIf(!fRet, pcnoDst);
return fRet;
}
/***************************************************************************
See if there is a subgraph of pcflDst matching the subgraph at
(ctgSrc, cnoSrc). Subgraphs match if there is one-to-one correspondence
of nodes and arcs of the two subgraphs and the rti's of corresponding
nodes are equal.
***************************************************************************/
bool CFL::_FFindMatch(CTG ctgSrc, CNO cnoSrc, PCFL pcflDst, CNO *pcnoDst)
{
AssertBaseThis(0);
AssertPo(pcflDst, 0);
AssertVarMem(pcnoDst);
long rtiSrc, rtiKid;
long ckidSrc;
CNO cnoMin, cnoDst;
CGE cgeSrc, cgeDst;
KID kidSrc, kidDst;
CKI ckiParSrc, ckiParDst;
ulong grfcgeSrc, grfcgeDst;
bool fKidSrc, fKidDst;
if (this == pcflDst || rtiNil == (rtiSrc = _Rti(ctgSrc, cnoSrc)))
return fFalse;
ckidSrc = Ckid(ctgSrc, cnoSrc);
for (cnoDst = cnoMin = 0; ; cnoMin = cnoDst + 1)
{
// get the next chunk with the same rti
if (cnoDst == (CNO)(-1) ||
!pcflDst->_FFindCtgRti(ctgSrc, rtiSrc, cnoMin, &cnoDst))
{
return fFalse;
}
if (pcflDst->Ckid(ctgSrc, cnoDst) != ckidSrc)
continue;
cgeSrc.Init(this, ctgSrc, cnoSrc);
cgeDst.Init(pcflDst, ctgSrc, cnoDst);
for (;;)
{
// get the next element of the the source graph
fKidSrc = cgeSrc.FNextKid(&kidSrc, &ckiParSrc, &grfcgeSrc, fcgeNil);
// if the source chunk doesn't have an rti, there's no hope
if (fKidSrc &&
rtiNil == (rtiKid = _Rti(kidSrc.cki.ctg, kidSrc.cki.cno)))
{
return fFalse;
}
// get the next element of the destination graph
fKidDst = cgeDst.FNextKid(&kidDst, &ckiParDst, &grfcgeDst, fcgeNil);
if (FPure(fKidSrc) != FPure(fKidDst))
{
// the two graphs have different numbers of nodes, so they
// don't match
break;
}
if (!fKidSrc)
{
// we're finished with the enumeration and everything matched
*pcnoDst = cnoDst;
return fTrue;
}
if ((grfcgeSrc | grfcgeDst) & fcgeError)
return fFalse;
if ((grfcgeSrc ^ grfcgeDst) & (fcgePre | fcgePost | fcgeRoot) ||
kidSrc.cki.ctg != kidDst.cki.ctg ||
!(grfcgeSrc & fcgeRoot) && kidSrc.chid != kidDst.chid ||
rtiKid != pcflDst->_Rti(kidDst.cki.ctg, kidDst.cki.cno) ||
Ckid(kidSrc.cki.ctg, kidSrc.cki.cno) !=
pcflDst->Ckid(kidDst.cki.ctg, kidDst.cki.cno))
{
// children don't match
break;
}
}
}
}
/***************************************************************************
Looks for a chunk of the given type with assigned rti and cno at least
cnoMin.
***************************************************************************/
bool CFL::_FFindCtgRti(CTG ctg, long rti, CNO cnoMin, CNO *pcno)
{
AssertBaseThis(0);
AssertNilOrVarMem(pcno);
#ifdef CHUNK_BIG_INDEX
long icrp, ccrp;
CRP *qcrp;
_FFindCtgCno(ctg, cnoMin, &icrp);
ccrp = Ccki();
for ( ; icrp < ccrp; icrp++)
{
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp);
if (qcrp->cki.ctg != ctg)
break; //done
if (qcrp->rti == rti)
{
// found one
if (pcno != pvNil)
*pcno = qcrp->cki.cno;
return fTrue;
}
}
#else //!CHUNK_BIG_INDEX
long irtie, crtie;
RTIE rtie;
if (pvNil != _pglrtie && 0 < (crtie = _pglrtie->IvMac()))
{
_FFindRtie(ctg, cnoMin, pvNil, &irtie);
for ( ; irtie < crtie; irtie++)
{
_pglrtie->Get(irtie, &rtie);
if (rtie.ctg != ctg)
break; //done
if (rtie.rti == rti)
{
// found one
if (pcno != pvNil)
*pcno = rtie.cno;
return fTrue;
}
}
}
#endif //!CHUNK_BIG_INDEX
TrashVar(pcno);
return fFalse;
}
/***************************************************************************
Copy a chunk (ctgSrc, cnoSrc) from this chunky file to pcflDst.
The new cno is put in *pcno. The destination chunk is marked as a
loner. If possible, the cno in the destination file will be the same
as the cno in the source file. This does intelligent chunk sharing.
***************************************************************************/
bool CFL::FCopy(CTG ctgSrc, CNO cnoSrc, PCFL pcflDst, CNO *pcnoDst)
{
return _FCopy(ctgSrc, cnoSrc, pcflDst, pcnoDst, fFalse);
}
/***************************************************************************
Clone a chunk subgraph from this chunky file to pcflDst. This will
make a copy of the the chunk and its descendents without using any
previously existing chunks in the destination. The new cno is put in
*pcno. The destination chunk is marked as a loner. If possible, the
cno in the destination file will be the same as the cno in the
source file.
***************************************************************************/
bool CFL::FClone(CTG ctgSrc, CNO cnoSrc, PCFL pcflDst, CNO *pcnoDst)
{
return _FCopy(ctgSrc, cnoSrc, pcflDst, pcnoDst, fTrue);
}
/***************************************************************************
Return the run time id of the given chunk.
***************************************************************************/
long CFL::_Rti(CTG ctg, CNO cno)
{
AssertBaseThis(0);
#ifdef CHUNK_BIG_INDEX
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
return rtiNil;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp, &cbVar);
return qcrp->rti;
#else //!CHUNK_BIG_INDEX
RTIE rtie;
if (!_FFindRtie(ctg, cno, &rtie))
return rtiNil;
return rtie.rti;
#endif //!CHUNK_BIG_INDEX
}
/***************************************************************************
Set the run time id of the given chunk.
***************************************************************************/
bool CFL::_FSetRti(CTG ctg, CNO cno, long rti)
{
AssertBaseThis(0);
#ifdef CHUNK_BIG_INDEX
long icrp;
CRP *qcrp;
if (!_FFindCtgCno(ctg, cno, &icrp))
return fFalse;
qcrp = (CRP *)_pggcrp->QvFixedGet(icrp, &cbVar);
qcrp->rti = rti;
return fTrue;
#else //!CHUNK_BIG_INDEX
RTIE rtie;
long irtie;
if (_FFindRtie(ctg, cno, &rtie, &irtie))
{
if (rtiNil == rti)
_pglrtie->Delete(irtie);
else
{
rtie.rti = rti;
_pglrtie->Put(irtie, &rtie);
}
return fTrue;
}
if (rtiNil == rti)
return fTrue;
rtie.ctg = ctg;
rtie.cno = cno;
rtie.rti = rti;
if (pvNil == _pglrtie && pvNil == (_pglrtie = GL::PglNew(size(RTIE), 1)))
return fFalse;
return _pglrtie->FInsert(irtie, &rtie);
#endif //!CHUNK_BIG_INDEX
}
#ifndef CHUNK_BIG_INDEX
/***************************************************************************
Look for an RTIE entry for the given (ctg, cno).
***************************************************************************/
bool CFL::_FFindRtie(CTG ctg, CNO cno, RTIE *prtie, long *pirtie)
{
AssertBaseThis(0);
AssertNilOrVarMem(prtie);
AssertNilOrVarMem(pirtie);
long ivMin, ivLim, iv;
RTIE rtie;
if (pvNil == _pglrtie)
{
if (pvNil != pirtie)
*pirtie = 0;
TrashVar(prtie);
return fFalse;
}
for (ivMin = 0, ivLim = _pglrtie->IvMac(); ivMin < ivLim; )
{
iv = (ivMin + ivLim) / 2;
_pglrtie->Get(iv, &rtie);
if (rtie.ctg < ctg)
ivMin = iv + 1;
else if (rtie.ctg > ctg)
ivLim = iv;
else if (rtie.cno < cno)
ivMin = iv + 1;
else if (rtie.cno > cno)
ivLim = iv;
else
{
if (pvNil != prtie)
*prtie = rtie;
if (pvNil != pirtie)
*pirtie = iv;
return fTrue;;
}
}
if (pvNil != pirtie)
*pirtie = ivMin;
TrashVar(prtie);
return fFalse;
}
#endif //!CHUNK_BIG_INDEX
/***************************************************************************
Constructor for chunk graph enumerator.
***************************************************************************/
CGE::CGE(void)
{
AssertThisMem();
_es = esDone;
_pgldps = pvNil;
AssertThis(0);
}
/***************************************************************************
Destructor for chunk graph enumerator.
***************************************************************************/
CGE::~CGE(void)
{
AssertThis(0);
ReleasePpo(&_pgldps);
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of the cge
***************************************************************************/
void CGE::AssertValid(ulong grf)
{
CGE_PAR::AssertValid(0);
AssertIn(_es, esStart, esDone + 1);
if (FIn(_es, esStart, esDone))
{
AssertPo(_pcfl, 0);
AssertNilOrPo(_pgldps, 0);
}
else
Assert(_pgldps == pvNil, "_pgldps not nil");
}
/***************************************************************************
Mark memory used by the cge.
***************************************************************************/
void CGE::MarkMem(void)
{
AssertThis(0);
CGE_PAR::MarkMem();
MarkMemObj(_pgldps);
}
#endif //DEBUG
/***************************************************************************
Start a new enumeration.
***************************************************************************/
void CGE::Init(PCFL pcfl, CTG ctg, CNO cno)
{
AssertThis(0);
AssertPo(pcfl, 0);
ReleasePpo(&_pgldps);
_es = esStart;
_pcfl = pcfl;
_dps.kid.cki.ctg = ctg;
_dps.kid.cki.cno = cno;
TrashVar(&_dps.kid.chid);
AssertThis(0);
}
/***************************************************************************
Fetch the next node in the graph enumeration. Generally, parent nodes
are returned twice (once with fcgePre and again with fcgePost). Nodes
without children are returned only once (with both fcgePre and fcgePost
set). The new node is put in *pkid, and the node's parent (if the node
is not the root of the enumeration) is put in *pckiPar. pckiPar may
be nil.
If (grfcgeIn & fcgeSkipToSib), skips all children and the upward touch
of the last node returned.
The *pgrfcgeOut value can contain any combination of: fcgePre, fcgePost,
fcgeError, fcgeRoot. These have the following meanings:
fcgePre: *pkid is valid; haven't traversed the node's children yet
fcgePost: *pkid is valid; have already traversed the children (or
there aren't any children)
fcgeError: a memory error occurred; may be set in conjunction with
other flags
fcgeRoot: *pkid is valid (except the chid value); *pckiPar is
invalid; the node is the root of the enumeration
***************************************************************************/
bool CGE::FNextKid(KID *pkid, CKI *pckiPar, ulong *pgrfcgeOut, ulong grfcgeIn)
{
AssertThis(0);
AssertVarMem(pkid);
AssertNilOrVarMem(pckiPar);
AssertVarMem(pgrfcgeOut);
*pgrfcgeOut = fcgeNil;
switch (_es)
{
case esStart:
// starting the enumeration
// hit the node on the way down
*pgrfcgeOut |= fcgePre | fcgeRoot;
if (_pcfl->Ckid(_dps.kid.cki.ctg, _dps.kid.cki.cno) == 0)
goto LPost;
*pkid = _dps.kid;
_dps.ikid = 0;
_es = esGo;
break;
case esGo:
if ((grfcgeIn & fcgeSkipToSib) &&
(_pgldps == pvNil || !_pgldps->FPop(&_dps)))
{
goto LDone;
}
// fall through
case esGoNoSkip:
if (!_pcfl->FGetKid(_dps.kid.cki.ctg, _dps.kid.cki.cno,
_dps.ikid++, pkid))
{
LPost:
// no more children, hit the node on the way up
*pgrfcgeOut |= fcgePost;
*pkid = _dps.kid;
if (_pgldps == pvNil || !_pgldps->FPop(&_dps))
{
// this is the root
*pgrfcgeOut |= fcgeRoot;
_es = esDone;
ReleasePpo(&_pgldps);
}
else
{
_es = esGoNoSkip;
if (pckiPar != pvNil)
*pckiPar = _dps.kid.cki;
}
break;
}
// hit the child
if (pckiPar != pvNil)
*pckiPar = _dps.kid.cki;
if (_pcfl->Ckid(pkid->cki.ctg, pkid->cki.cno) > 0)
{
// child has children, need to push the dps
if (_pgldps == pvNil &&
(_pgldps = GL::PglNew(size(DPS), 10)) == pvNil ||
!_pgldps->FPush(&_dps))
{
// mem failure, pretend it has no children
*pgrfcgeOut |= fcgeError;
goto LNoChildren;
}
_dps.kid = *pkid;
_dps.ikid = 0;
*pgrfcgeOut |= fcgePre;
_es = esGo;
}
else
{
LNoChildren:
// child doesn't have children, just handle it
*pgrfcgeOut |= fcgePost | fcgePre;
_es = esGoNoSkip;
}
break;
default:
LDone:
_es = esDone;
ReleasePpo(&_pgldps);
TrashVar(pkid);
TrashVar(pckiPar);
TrashVar(pgrfcgeOut);
return fFalse;
}
TrashVarIf((*pgrfcgeOut & fcgeRoot), pckiPar);
TrashVarIf((*pgrfcgeOut & fcgeRoot), &pkid->chid);
return fTrue;
}