persistentstorage/store/HTOOLS/PFSDUMP.CPP
changeset 0 08ec8eefde2f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/persistentstorage/store/HTOOLS/PFSDUMP.CPP	Fri Jan 22 11:06:30 2010 +0200
@@ -0,0 +1,861 @@
+// Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+//
+
+#include "PFSDUMP.H"
+
+// entry point
+
+int main(int argc,char *argv[])
+	{
+	if (TheProgram.ProcessCommandLine(argc,argv))
+		{
+		TheProgram.ExplainUsage();
+		return 1;
+		}
+	TheProgram.Run();
+	return 0;
+	}
+
+// Class Program
+
+class Program TheProgram;
+
+Program::Program()
+	: iFile(NULL),iOptions(0),iBin(&iBinBuf),iErrors(0)
+	{
+	cout << setfill('0');
+	}
+
+void Program::Information()
+	{
+	if (!Option(quiet))
+		cout << "\nEPOC PermanentFileStore Dump Utility   Version " << MajorVersion << '.' \
+			<< setw(2) << MinorVersion << "(build " << setw(3) << setfill('0') << Build \
+			<< ")\nCopyright (c) Symbian Software Limited 1997-2004. All rights reserved.\n\n" << flush;
+	cout << hex;
+	cerr << hex;
+	}
+
+void Program::ExplainUsage()
+	{
+	Information();
+	cout << "Usage:  PFSDUMP [options] storefile\n" \
+			" -v    verbose: dump contents\n" \
+			" -f    generate a frame dump\n" \
+			" -r<n> use nth previous store revision\n" \
+			" -c    format output for comparison (not for frame dumps)\n"
+			" -q    quiet: suppress logo\n" << flush;
+	}
+
+int Program::ProcessCommandLine(int argc,char ** argv)
+	{
+	if (argc<2)
+		return 1;
+
+	int options=0;
+	while (--argc>0)
+		{
+		istrstream arg(*++argv);
+		int c=arg.get();
+		if (c=='/' || c=='-')
+			{
+			while ((c=arg.get())!=EOF)
+				{
+				switch (c)
+					{
+				case 'c': case 'C':
+					options|=compare;
+					break;
+				case 'f': case 'F':
+					options|=frame;
+					break;
+				case 'q': case 'Q':
+					options|=quiet;
+					break;
+				case 'r': case 'R':
+					arg >> iTocRevision;
+					if (arg.fail() || iTocRevision<0)
+						return 1;
+					break;
+				case 'v': case 'V':
+					options|=verbose;
+					break;
+				default:			// unrecognised option
+					return 1;
+					}
+				}
+			}
+		else if (iFile!=NULL)
+			return 1;		// two filenames?
+		else
+			iFile=arg.str();
+		}
+	if (options&frame)
+		options&=~compare;
+	iOptions=options;
+	return iFile==NULL;
+	}
+
+void Program::Run()
+//
+// The main part of the program
+//
+	{
+	Information();
+	StoreFile store(iFile);
+	if (iErrors)
+		cerr << endl;
+	if (store.Empty())
+		{
+		cout << "Store is empty" << endl;
+		return;
+		}
+	if (Option(frame))
+		{
+		store.EmitHeader();
+		store.EmitFrames();
+		}
+	store.EmitToc();
+	if (!Option(frame))
+		store.EmitStreams();
+	}
+
+void Program::Abort(char const* aMessage)
+	{
+	cerr << aMessage << endl;
+	exit(3);
+	}
+
+void Program::Corrupt(char const* aMessage)
+//
+// terminate after detecting a fatal corruption error
+//
+	{
+	cerr << "\nfatal error: " << aMessage << "\ncannot continue\n" << flush;
+	exit(2);
+	}
+
+ostream& Program::Error()
+	{
+	++TheProgram.iErrors;
+	return cerr << "error: ";
+	}
+
+ostream& Program::Warning()
+	{
+	++TheProgram.iErrors;
+	return cerr << "warning: ";
+	}
+
+// Bin stream buffer
+
+binbuf::binbuf()
+	: streambuf()
+	{}
+
+int binbuf::overflow(int)
+	{return 0;}
+
+int binbuf::underflow()
+	{return EOF;}
+
+// Class StoreFile
+
+int StoreFile::OutWidth;
+
+StoreFile::StoreFile(char const* aFile)
+	{
+#ifdef __MSVCDOTNET__
+	iFile.open(aFile, ios::binary);
+#else //!__MSVCDOTNET__
+	iFile.open(aFile, ios::binary | ios::nocreate);
+#endif //__MSVCDOTNET__
+	if (!iFile)
+		Program::Abort("Unable to open file or file does not exist");
+//
+	unsigned long uid;
+	iFile.read((char*)&uid,sizeof(uid));
+	iFile.seekg(0,ios::end);
+	int size=iFile.tellg();
+	if (size<FrameDes::First || uid!=PermanentFileStoreUid)
+		Program::Abort("Not a permanent file store");
+//
+	iSize=size;
+	if (TheProgram.Option(Program::compare))
+		OutWidth=7;
+	else
+		{
+		int width=0;
+		while (size)
+			{
+			++width;
+			size>>=4;
+			}
+		OutWidth=width;
+		}
+//
+	cout << "Dumping " << aFile << "\n\n";
+//
+	iFile.seekg(Header::Offset,ios::beg);
+	iFile.read((char*)&iHeader,Header::Size);
+//
+	if (Empty())
+		return;
+	LoadFrames();
+	LoadToc();
+	}
+
+StoreFile::~StoreFile()
+	{
+	iFile.close();
+	}
+
+void StoreFile::LoadFrames()
+	{
+	FrameDes frame;
+	int offset=FrameDes::First;
+	int full=FrameDes::First+FrameDes::Interval;
+	int diff=FrameDes::First;
+
+	while (offset-FrameDes::Size<iSize)
+		{
+		if (offset==full)
+			{
+			full+=FrameDes::Interval;
+			diff+=FrameDes::Size;
+			}
+		if (offset>iSize)
+			{
+			Program::Warning() << "incomplete link at " << FramePos(offset-diff) << endl;
+			break;
+			}
+		iFile.seekg(offset-FrameDes::Size,ios::beg);
+		iFile >> frame;
+		iFrames.Add(FramePos(offset-diff),frame);
+		int length=frame.Length();
+		if (length==0)
+			{
+			if (full>iSize && offset>iSize)
+				Program::Warning() << "incomplete frame at " << FramePos(offset-diff) << endl;
+			offset=full;
+			}
+		else
+			{
+			int newoffset=offset+length+FrameDes::Size;
+			if (newoffset>=full || newoffset-FrameDes::Size>iSize)
+				{
+				Program::Error() << "bad link at " << FramePos(offset-diff) << ", skipping to next anchor link" << endl;
+				offset=full;
+				}
+			else
+				{
+				offset=newoffset;
+				if (full-offset<=FrameDes::Size)
+					offset=full;
+				}
+			}
+		}
+	iFrames.Complete();
+	}
+
+void StoreFile::LoadToc()
+	{
+	FramePos toc=iHeader.Toc();
+	Stream stream(iFrames,toc);
+	if (!stream.IsGood())
+		{
+		Program::Error() << "invalid toc address " << toc << endl;
+		return;
+		}
+	if (stream.Type()!=FrameDes::Toc)
+		{
+		Program::Error() << "toc address " << toc << ": refers to non-toc frame"<< endl;
+		return;
+		}
+
+// find the requested store revision
+	Frames::Iterator f=stream.Frame();
+	Frames::Iterator const first=iFrames.Begin();
+	for (int rev=TheProgram.TocRevision();rev;--rev)
+		{
+		do	{
+			if (--f<first)
+				Program::Abort("Store revision not found");
+			} while (f->iDes.Type()!=FrameDes::Toc);
+		}
+
+	iToc.Load(iFile,iFrames,f,iHeader.GetReloc());
+
+// verify the Toc stream references
+	Toc::Iterator const end=iToc.End();
+	for (Toc::Iterator iter=iToc.Begin();iter<end;++iter)
+		{
+		if (iter->iHandle.IsNull())
+			Program::Error() << "missing entry in toc-delta for index " << (1+iter-iToc.Begin()) << endl;
+		else if (!iter->iHandle.Avail() && iter->Pos().Pos()>=0)
+			{
+			f=iFrames.Find(iter->Pos());
+			if (f==NULL)
+				Program::Error() << "invalid stream reference in toc entry " << iter->iHandle << endl;
+			else if (iter->Pos().Pos()>=toc.Pos())
+				Program::Error() << "virtual stream reference in toc entry " << iter->iHandle << endl;
+			else if (f->iDes.Type()!=FrameDes::Data)
+				Program::Error() << "toc entry " << iter->iHandle << ": refers to non-data frame" << endl;
+			}
+		}
+	}
+
+void StoreFile::EmitHeader()
+	{
+	cout << iHeader;
+	}
+
+void StoreFile::EmitFrames()
+	{
+	int verbose=TheProgram.Option(Program::verbose);
+	Frames::Iterator const end=iFrames.End();
+	for (Frames::Iterator iter=iFrames.Begin();iter<end;++iter)
+		{
+		FramePos pos=iter->iPos;
+		cout << "Frame at " << pos << ": " << iter->iDes << endl;
+		if (!verbose)
+			continue;
+
+	// dump contents
+		int length=iter->iDes.Length();
+		if (length==0)
+			{	// full frame
+			if (iter+1==end)	// no more
+				continue;
+			length=iter[1].iPos.Pos()-pos.Pos();
+			}
+		HexDump hex;
+		iFile.seekg(FileOffset(pos).Offset(),ios::beg);
+		hex.Dump(iFile,length);
+		hex.Flush();
+		cout << endl;
+		}
+	cout << endl;
+	}
+
+void StoreFile::EmitToc()
+	{
+	cout << iToc;
+	}
+
+void StoreFile::EmitStreams()
+	{
+	int verbose=TheProgram.Option(Program::verbose);
+	cout << endl;
+	Toc::Iterator const end=iToc.End();
+	for (Toc::Iterator iter=iToc.Begin();iter<end;++iter)
+		{
+		if (!iter->iHandle.Avail())
+			{
+			cout << "Stream " << iter->iHandle;
+			if (iter->Pos().Pos()==-1)
+				{
+				cout << " is empty\n";
+				continue;
+				}
+			if (!TheProgram.Option(Program::compare))
+				cout << " at " << iter->Pos();
+			Stream stream(iFrames,iter->Pos());
+			if (!stream.IsGood() || stream.Type()!=FrameDes::Data)
+				{
+				cout << " is invalid\n";
+				continue;
+				}
+			int len=stream.Length();
+			cout << ", " << dec << len << hex << " byte" << (len==1 ? "\n" : "s\n");
+			if (!verbose)
+				continue;
+
+		// dump contents
+			HexDump hex;
+			Frames::Iterator f=stream.Frame();
+			do
+				{
+				FramePos pos=f->iPos;
+				int len=f++->iDes.Length();
+				if (len==0)
+					len=f->iPos.Pos()-pos.Pos();
+				iFile.seekg(FileOffset(pos).Offset(),ios::beg);
+				hex.Dump(iFile,len);
+				} while (f->iDes.Type()==FrameDes::Continuation);
+			hex.Flush();
+			cout << endl;
+			}
+		}
+	}
+
+// Class HexDump
+
+HexDump::HexDump()
+	: iPos(0),iByte(0)
+	{
+	Reset();
+	}
+
+HexDump::~HexDump()
+	{
+	Flush();
+	}
+
+void HexDump::Reset()
+	{
+	memset(iChar,' ',HexInterval+3);
+	iChar[HexInterval+3]=0;
+	iPtr=&iChar[2];
+	}
+
+void HexDump::Dump(istream& aStream,int aBytes)
+	{
+	while (--aBytes>=0 && !aStream.eof())
+		{
+		if (iByte==0)
+			cout << Margin << FileOffset(iPos) << ':';
+		int c=aStream.get();
+		cout << ' ' << setw(2) << c;
+		*iPtr++=char(isprint(c) ? c : '.');
+		if (++iByte==HexInterval/2)
+			{
+			cout << ' ';
+			++iPtr;
+			}
+		else if (iByte==HexInterval)
+			Flush();
+		}
+	}
+
+void HexDump::Flush()
+	{
+	if (iByte)
+		{
+		for (int ii=iByte;ii<HexInterval;++ii)
+			cout << "   ";
+		if (iByte<HexInterval/2)
+			cout << ' ';
+		cout << iChar << endl;
+		iPos+=iByte;
+		iByte=0;
+		Reset();
+		}
+	}
+
+// Toc
+
+Toc::Toc()
+	: iPos(0), iRep(NULL), iAvail(0)
+	{
+	memset(&iHeader,0,sizeof(iHeader));
+	}
+
+Toc::~Toc()
+	{
+	free(iRep);
+	}
+
+
+void Toc::Base(const char* aPtr,int aCount)
+	{
+	Entry* e=iRep;
+	for (int i=1;i<=aCount;++e,++i)
+		{
+		e->iHandle=i;	// set the index part
+		memcpy((char*)e+Entry::BaseRedundant,aPtr,Entry::BaseSize);
+		aPtr+=Entry::BaseSize;
+		}
+	}
+
+void Toc::Load(istream& aStream,Frames const& aFrames,Frames::Iterator aFrame,Header::Reloc const* aReloc)
+	{
+	iPos = aFrame->iPos;
+
+	Stream toc1(aFrame);
+	void* toc = toc1.Load(aStream);
+	const char* p = reinterpret_cast<char*>(toc);
+	memcpy(&iHeader,p,Head::Size);
+	p+=Head::Size;
+	int n = iHeader.iCount;
+	if (n < 0)
+		{
+		memset(&iHeader,0,Head::Size);
+		Program::Error() << "corrupt toc" << endl;
+		return;
+		}
+	iRep=static_cast<Entry*>(malloc(n*sizeof(Entry)));
+	if (iRep==NULL)
+		Program::Abort("Out of memory");
+
+	if (iHeader.IsDelta())
+		{
+		// verify the delta header
+		memcpy(&iDelta,p,DeltaHead::Size);
+		p+=DeltaHead::Size;
+		int dn = iDelta.iCount;
+		if (toc1.Length() != Head::Size + DeltaHead::Size + dn * Entry::DeltaSize)
+			{
+			memset(&iHeader,0,Head::Size);
+			Program::Error() << "incomplete toc" << endl;
+			return;
+			}
+		
+		// find the toc-base
+		FramePos tocbase(iDelta.iBase + Header::tocoffset);
+		if (aReloc && aReloc->iHandle.IsTocBase())
+			tocbase = aReloc->iPos;
+		Stream toc2(aFrames,tocbase);
+		if (!toc2.IsGood())
+			{
+			memset(&iHeader,0,Head::Size);
+			Program::Error() << "invalid toc-base address " << tocbase << endl;
+			return;
+			}
+		if (toc2.Type()!=FrameDes::Toc)
+			{
+			memset(&iHeader,0,Head::Size);
+			Program::Error() << "toc-base address " << tocbase << ": refers to non-toc frame"<< endl;
+			return;
+			}
+		
+		// validate and load the toc-base
+		void* tocb = toc2.Load(aStream);
+		const char* p2 = reinterpret_cast<char*>(tocb);
+		Head headbase;
+		memcpy(&headbase,p2,Head::Size);
+		p2+=Head::Size;
+		if (headbase.IsDelta())
+			{
+			memset(&iHeader,0,Head::Size);
+			Program::Error() << "toc-base is a toc-delta"<< endl;
+			return;
+			}
+		int bn = headbase.iCount;
+		if (bn > n)
+			{
+			memset(&iHeader,0,Head::Size);
+			Program::Error() << "toc-base is larger than toc"<< endl;
+			return;
+			}
+		Base(p2,bn);
+		free(tocb);
+
+		// validate and update with the toc-delta
+		int last = 0;
+		while (--dn>=0)
+			{
+			Entry e;
+			memcpy(&e,p,Entry::DeltaSize);
+			p+=Entry::DeltaSize;
+			int ix = e.iHandle.Index();
+			if (ix<=0 || ix > n)
+				{
+				memset(&iHeader,0,Head::Size);
+				Program::Error() << "toc-delta entry " << e.iHandle << " is outside toc"<< endl;
+				return;
+				}
+			if (ix <= last)
+				{
+				memset(&iHeader,0,Head::Size);
+				Program::Error() << "toc-delta entry " << e.iHandle << " is out of order"<< endl;
+				return;
+				}
+			iRep[ix-1] = e;
+			last = ix;
+			}
+		}
+	else
+		{
+		if (toc1.Length() != Head::Size + n * Entry::BaseSize)
+			{
+			memset(&iHeader,0,Head::Size);
+			Program::Error() << "incomplete toc" << endl;
+			return;
+			}
+		Base(p,n);
+		}
+	free(toc);
+
+	// apply the relocation
+	if (aReloc && !aReloc->iHandle.IsTocBase())
+		{
+		int ix=aReloc->iHandle.Index();
+		if (ix<=0 || ix>n)
+			Program::Corrupt("invalid index in relocation patch");
+
+		Entry& e=iRep[ix-1];
+		if (e.iHandle.Generation()!=aReloc->iHandle.Generation())
+			Program::Corrupt("incorrect generation in relocation patch");
+		e.iPos=aReloc->iPos.Pos();
+		}
+
+	// count the available entries
+	int avail=0;
+	for (int i=0;i<n;++i)
+		{
+		if (iRep[i].iHandle.Avail())
+			++avail;
+		}
+	iAvail=avail;
+
+// verify the available list
+	Handle link=iHeader.iAvail;
+	if (!link.IsNull())
+		{
+		int ix=link.Index();
+		if (!link.Avail() || ix<=0 || ix >iHeader.iCount)
+			{
+eAvail:		Program::Error() << "corrupt available link in toc header " << link << endl;
+			return;
+			}
+		Entry const* en=&(*this)[ix];
+		if (en->iHandle!=link)
+			goto eAvail;
+		for (;;)
+			{
+			if (--avail<0)
+				{
+				Program::Error() << "corrupt available list, possible circular reference" << endl;
+				return;
+				}
+			Handle next=en->Link();
+			if (next.IsNull())
+				break;
+			ix=next.Index();
+			if (!next.Avail() || ix<=0 || ix >iHeader.iCount)
+				{
+eLink:			Program::Error() << "corrupt link in toc entry " << link << endl;
+				return;
+				}
+			en=&(*this)[ix];
+			if (en->iHandle!=next)
+				goto eLink;
+			link=next;
+			}
+		}
+	if (avail!=0)
+		Program::Error() << "corrupt available list: free index leakage" << endl;
+	}
+
+ostream& operator<<(ostream& aStream,Toc const& aToc)
+	{
+	int all=TheProgram.Option(Program::frame);
+
+	Toc::Head const& head=aToc.iHeader;
+	if (TheProgram.Option(Program::compare))
+		aStream << "Toc: ";
+	else
+		aStream << "Toc at " << aToc.iPos << " with ";
+	aStream << dec << head.iCount << (head.iCount==1 ? " entry: " : " entries: ") \
+		<< head.iCount-aToc.iAvail  << " allocated, " << aToc.iAvail << " free\n" << hex;
+	if (!all)
+		aStream << "root is " << head.Root() << '\n';
+	else
+		{
+		aStream << "first available is " << head.iAvail << '\n';
+		Toc::Iterator const end=aToc.End();
+		for (Toc::Iterator iter=aToc.Begin();iter<end;++iter)
+			{
+			aStream << (iter->iHandle==head.Root() ? "* " : Margin) << iter->iHandle;
+			if (iter->iHandle.Avail())
+				aStream << " free -> " << iter->Link() << '\n';
+			else if (iter->Pos().Pos()==-1)
+				aStream << " empty\n";
+			else
+				aStream << " alloc at " << iter->Pos() << '\n';
+			}
+		}
+	return aStream << flush;
+	}
+
+// Class Stream
+
+int Stream::Length() const
+	{
+	int total=0;
+	Frames::Iterator f=iFrame;
+	do	{
+		int len=f->iDes.Length();
+		if (len==0)
+			len=f[1].iPos.Pos()-f[0].iPos.Pos();
+		total+=len;
+		} while ((++f)->iDes.Type()==FrameDes::Continuation);
+	return total;
+	}
+
+void* Stream::Load(istream& aStream) const
+	{
+	int size = Length();
+	void* data = malloc(size);
+	if (data==NULL)
+		Program::Abort("Out of memory");
+
+	char* read=reinterpret_cast<char*>(data);
+	Frames::Iterator f = iFrame;
+	do
+		{
+		FramePos pos=f->iPos;
+		int len=f++->iDes.Length();
+		if (len==0)
+			len=f->iPos.Pos()-pos.Pos();
+		aStream.seekg(FileOffset(pos).Offset(),ios::beg);
+		aStream.read(read,len);
+		read+=len;
+		} while (f->iDes.Type()==FrameDes::Continuation);
+
+	return data;
+	}
+
+// Class Frames
+
+Frames::Frames()
+	: iSize(0),iElements(0),iRep(NULL)
+	{}
+
+Frames::~Frames()
+	{
+	free(iRep);
+	}
+
+void Frames::Add(FramePos aPos,FrameDes aDes)
+	{
+	if (iElements==iSize)
+		{
+		iSize=iSize==0 ? 128 : iSize+iSize;
+		void* rep=realloc(iRep,iSize*sizeof(*iRep));
+		if (rep==NULL)
+			Program::Abort("Out of memory");
+		iRep=(Element*)rep;
+		}
+	Element& element=iRep[iElements++];
+	element.iPos=aPos;
+	element.iDes=aDes;
+	}
+
+void Frames::Complete()
+//
+// add a terminating entry
+//
+	{
+	Add(0,0);
+	--iElements;
+	}
+
+Frames::Iterator Frames::Find(FramePos aPos) const
+	{
+	return (Element const*)bsearch(&aPos,iRep,iElements,sizeof(*iRep),Compare);
+	}
+
+int Frames::Compare(void const* aLeft,void const* aRight)
+	{
+	int left=static_cast<FramePos const*>(aLeft)->Pos();
+	int right=static_cast<Element const*>(aRight)->iPos.Pos();
+	if (left<right)
+		return -1;
+	if (left>right)
+		return 1;
+	return 0;
+	}
+
+// Header
+
+FramePos Header::Toc() const
+	{
+	return tocoffset+(!Dirty() && iToc.iZero==0 ? iToc.iPos : iBackupToc>>backupshift);
+	}
+
+Header::Reloc const* Header::GetReloc() const
+	{
+	return (Dirty() || iToc.iZero==0) ? NULL : reinterpret_cast<Reloc const*>(&iReloc);
+	}
+
+ostream& operator<<(ostream& aStream,Header const& aHeader)
+	{
+	aStream << "Header is " << (aHeader.Dirty() ? "dirty" : "clean");
+	Header::Reloc const* reloc=aHeader.GetReloc();
+	if (reloc!=NULL)
+		{
+		aStream << "\npending relocation of ";
+		if (reloc->iHandle.IsTocBase())
+			aStream << "toc-base";
+		else
+			aStream << "stream " << StreamId(reloc->iHandle);
+		aStream << " to " << reloc->iPos;
+		}
+	return aStream << "\n\n" << flush;
+	}
+
+// FileOffset
+
+FileOffset::FileOffset(FramePos aPos)
+// calculate the file offset for a streampos
+	{
+	int pos=aPos.Pos();
+	int n=pos>>FrameDes::FullShift;
+	pos+=FrameDes::Size*n+FrameDes::First;
+	iValue=pos;
+	}
+
+FileOffset::operator FramePos() const
+	{
+	int pos=iValue-FrameDes::First;
+	int n=pos/FrameDes::Interval;
+	pos-=n*FrameDes::Size;
+	return FramePos(pos);
+	}
+
+ostream& operator<<(ostream& aStream,FileOffset anOffset)
+	{
+	return aStream << setw(StoreFile::OutWidth) << anOffset.iValue;
+	}
+
+// Handle
+
+ostream& operator<<(ostream& aStream,Handle aHandle)
+	{
+	if (aHandle.IsNull())
+		aStream << "Null";
+	else
+		aStream << setw(6) << aHandle.Index() << ':' << aHandle.Generation();
+	return aStream;
+	}
+
+// FramePos
+
+ostream& operator<<(ostream& aStream,FramePos aPos)
+	{
+	return aStream << setw(StoreFile::OutWidth) << aPos.iValue << '[' << FileOffset(aPos) << ']';
+	}
+
+// FrameDes
+
+istream& operator>>(istream& aStream,FrameDes& aFrame)
+	{
+	return aStream.read((char*)&aFrame,FrameDes::Size);
+	}
+
+ostream& operator<<(ostream& aStream,FrameDes aFrame)
+	{
+	static char const* FrameType[]={"free","data","toc","continuation"};
+	aStream << FrameType[aFrame.Type()] << " (";
+	int length=aFrame.Length();
+	if (length==0)
+		aStream << "full";
+	else
+		aStream << dec << length << hex;
+	return aStream << ')';
+	}
+