libraries/clogger/src/MiscServer.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Sat, 31 Jul 2010 19:07:57 +0100
changeset 23 092bcc217d9d
parent 0 7f656887cf89
permissions -rw-r--r--
Tidied iocli exports, build macro tweaks. Removed 4 overloads of CCommandBase::RunCommand[L] that are no longer used at all, and changed one more to not be exported as it's only used internally to iocli.dll. fixed builds on platforms that don't support btrace or any form of tracing.

// MiscServer.cpp
// 
// Copyright (c) 2006 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "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:
// Accenture - Initial contribution
//

#include "CloggerServer.h"
#include <fshell/common.mmh>
#include <fshell/ltkutils.h>

#ifdef FSHELL_EZLIB_SUPPORT
#include <EZCompressor.h>
//#include <EZDecompressor.h>
#include <EZlib.h>
#include <EZGzip.h>
#endif

_LIT8(KDiskFull, "\r\n" MINTAGSTART "Log truncated due to disk full\r\n");

void CLogWriter::WriteBuf(TInt aBuf)
	{
	ASSERT(!IsActive() && iEnabled);
		{
		iBuf = aBuf;
		iFile.Write(iServer.GetBuf(aBuf), iStatus);
		SetActive();
		}
	}

TBool CLogWriter::IsBusyWriting()
	{
	return IsActive();
	}

void CLogWriter::RunL()
	{
	if (iStatus == KErrDiskFull)
		{
		//TODO Make this occur before the disk is actually completely empty?
		TInt filePos = 0;
		TInt err = iFile.Seek(ESeekCurrent, filePos);
		if (err == KErrNone && filePos > KDiskFull().Length()) // ensure the file has enough room in it for our message
			{
			// Rewind the log file and overwrite the tail of it with a warning to say the disk is full
			filePos = filePos - KDiskFull().Length();
			err = iFile.Seek(ESeekStart, filePos);
			if (err == KErrNone)
				{
				iFile.Write(KDiskFull, iStatus);
				SetActive();
				return;
				}
			}
		}
	iServer.CompletedWritingBuf(this, iBuf);
	}

void CLogWriter::DoCancel()
	{
	// There's no way to cancel a file write (or any semantic sense to do so) so by doing nothing we get the SyncWaitForCompletion behaviour we wanted
	}

CLogWriter::~CLogWriter()
	{
	// Don't cancel, CloggerServer is supposed to guarantee we won't be deleted when still writing
	//Cancel();
	}

void CLogWriter::CloseWriter()
	{
	delete this;
	}

void CLogWriter::SetEnabled(TBool aEnabled)
	{
	if (!iFile.SubSessionHandle())
		{
		// If the log file is not open, we can't enable ourselves even if the client asks us to
		aEnabled = EFalse;
		}
	iEnabled = aEnabled;
	}

CRDebugWriter::CRDebugWriter(CCloggerServer& aServer)
	: iServer(aServer)
	{}

void CRDebugWriter::WriteBufL(const TDesC8& aBuf)
	{
	TInt maxLen = 256;

	// RDebug::Print only accepts 256 chars on the kernel side, so split the write up
	TInt pos = 0;
	while (pos < aBuf.Length())
		{
		TPtrC8 frag = aBuf.Mid(pos, Min(maxLen, aBuf.Length() - pos));
		//Exec::DebugPrint((TAny*)&frag, 1); // This is quicker than calling RDebugPrint - cuts down on a memory copy
		// ^ Except that you can't do it from non-euser code...
		LtkUtils::RawPrint(frag);
		pos += frag.Length();
		}
	}

void CRDebugWriter::CloseWriter()
	{
	delete this;
	}

///////////////

TInt CSyncWriterWrapper::ThreadFunction(TAny* aSelf)
	{
	CTrapCleanup* cleanup=CTrapCleanup::New();
	if (!cleanup) return KErrNoMemory;
	
	CSyncWriterWrapper* self = static_cast<CSyncWriterWrapper*>(aSelf);
	for (;;)
		{
		User::WaitForRequest(self->iThreadStatus);
		//User::After(1); //DEBUG TO TEST SLOW WRITERS
		if (self->iThreadStatus != KErrNone)
			{
			break;
			}

		const TDesC8& buf = self->iServer.GetBuf(self->iBuf);

		TRAPD(err, self->iWriter.WriteBufL(buf));

		self->iThreadStatus = KRequestPending;
		TRequestStatus* stat = &self->iStatus;
		self->iMainThread.RequestComplete(stat, err);
		}

	delete cleanup;
	return self->iThreadStatus.Int();
	}

void CSyncWriterWrapper::WriteBuf(TInt aBuf)
	{
	ASSERT(!IsActive() && iEnabled);
		{
		iBuf = aBuf;
		iStatus = KRequestPending;
		SetActive();
		TRequestStatus* stat = &iThreadStatus;
		iWorkerThread.RequestComplete(stat, KErrNone);
		}
	}

TBool CSyncWriterWrapper::IsBusyWriting()
	{
	return IsActive();
	}

void CSyncWriterWrapper::RunL()
	{
	iServer.CompletedWritingBuf(this, iBuf);
	}

void CSyncWriterWrapper::DoCancel()
	{
	// There's no way to cancel a sync writer
	}

CSyncWriterWrapper::~CSyncWriterWrapper()
	{
	// Don't cancel, CloggerServer is supposed to guarantee we won't be deleted when still writing
	//Cancel();
	if (iWorkerThread.Handle())
		{
		TRequestStatus* stat = &iThreadStatus;
		iWorkerThread.RequestComplete(stat, KErrCancel);
		TRequestStatus rv;
		iWorkerThread.Rendezvous(rv);
		User::WaitForRequest(rv);
		iWorkerThread.Close();
		}
	iMainThread.Close();
	if (iOwnWriter)
		{
		iWriter.CloseWriter();
		}
	}

void CSyncWriterWrapper::CloseWriter()
	{
	delete this;
	}

void CSyncWriterWrapper::SetEnabled(TBool aEnabled)
	{
	iEnabled = aEnabled;
	}

CSyncWriterWrapper::CSyncWriterWrapper(CCloggerServer& aServer, MSyncWriter& aWriter)
	: CActive(EPriorityHigh), // It's more important to keep writing than to handle clientserver calls
	iServer(aServer), iWriter(aWriter), iBuf(-1), iEnabled(EFalse)
	{
	CActiveScheduler::Add(this);
	}

CSyncWriterWrapper* CSyncWriterWrapper::NewL(CCloggerServer& aServer, MSyncWriter& aWriter, TInt aWriterId)
	{
	CSyncWriterWrapper* self = new(ELeave) CSyncWriterWrapper(aServer, aWriter);
	CleanupStack::PushL(self);
	self->ConstructL(aWriterId);
	CleanupStack::Pop(self);
	self->iOwnWriter = ETrue;
	return self;
	}

void CSyncWriterWrapper::ConstructL(TInt aWriterId)
	{
	User::LeaveIfError(iMainThread.Open(RThread().Id()));
	_LIT(KName, "SyncWriter%i");
	TBuf<16> name;
	name.Format(KName, aWriterId);
	User::LeaveIfError(iWorkerThread.Create(name, &ThreadFunction, 2048, NULL, this));
	if (aWriterId >= 0)
		{
		iWorkerThread.SetPriority(EPriorityMore); // To ensure buffers aren't being filled without us having a chance to empty them
		}
	else
		{
		iWorkerThread.SetPriority(EPriorityMuchLess); // We use the sync writer wrapper around the background compress of rotated log files, and that operation should be low priority
		}
	iWorkerThread.Resume();
	}

//////////

CLogCompressor* CLogCompressor::NewLC(CCloggerServer& aServer)
	{
	CLogCompressor* self = new(ELeave) CLogCompressor(aServer);
	CleanupStack::PushL(self);
	User::LeaveIfError(self->iFs.Connect());
	self->iFs.ShareAuto();
	return self;
	}

CLogCompressor::CLogCompressor(CCloggerServer& aServer)
	: iServer(aServer)
	{
	}

CLogCompressor::~CLogCompressor()
	{
	}

void CLogCompressor::WriteBufL(const TDesC8& /*aBuf*/)
	{
	TDes& filename = iServer.GetFilenameToRotate();

	HBufC* compressedFile = HBufC::NewLC(filename.Length()+3);
	_LIT(KFmt, "%S.gz");
	compressedFile->Des().Format(KFmt, &filename);

	RFile input;

	//_LIT(KInfo,"Compressing file %S to %S\n");
	//console->Printf(KInfo,&inputFile,compressedFile);

	User::LeaveIfError(input.Open(iFs, filename, EFileStream | EFileRead | EFileShareAny));
	CleanupClosePushL(input);

	TRAPD(err, DoGzipL(input, *compressedFile));

	if (err)
		{
		// If the compress failed, delete any fragment (don't care if this fails)
		iFs.Delete(*compressedFile);
		}

	//_LIT(KHoorah,"Hoorah");
	//console->Printf(KHoorah);

	CleanupStack::PopAndDestroy(&input);
	if (err == KErrNone)
		{
		// Don't delete the original unless the compress succeeded!
		err = iFs.Delete(filename);
		}
	if (err == KErrNone)
		{
		// We've sucessfully compressed our file, so update filename to reflect that
		filename = *compressedFile;
		}
	// Don't do anything if the delete fails - the file(s) will be cleaned up when the max number of rotated logs is exceeded
	CleanupStack::PopAndDestroy(compressedFile);
	}

void CLogCompressor::CloseWriter()
	{
	delete this;
	}

void CLogCompressor::DoGzipL(RFile& aInput, const TDesC& aOutput)
	{
#ifdef FSHELL_EZLIB_SUPPORT
	const TInt bufferSize = 0x8000; // 32 KB
	CEZFileToGZip* com = CEZFileToGZip::NewLC(iFs, aOutput, aInput, bufferSize);
	while (com->DeflateL()){/*do nothing*/}
	CleanupStack::PopAndDestroy(com);
#else
	User::Leave(KErrNotSupported);
#endif
	}

////

CMessageQueueWriter* CMessageQueueWriter::NewL()
	{
	CMessageQueueWriter* self = new(ELeave) CMessageQueueWriter;
	TInt err = self->iQ.CreateGlobal(_L("Clogger.LogMessageQueue"), 4);

	if (err)
		{
		DISOWN(self);
		User::Leave(err);
		}
	return self;
	}

void CMessageQueueWriter::WriteBufL(const TDesC8& aBuf)
	{
	TBuf8<128> buf;
	TPtrC8 lineFrag(aBuf);
	while (lineFrag.Length())
		{
		TInt spaceFree = buf.MaxLength() - buf.Length();
		buf.Append(lineFrag.Left(spaceFree));
		TInt err = iQ.Send(buf);
		if (err == KErrOverflow)
			{
			// Drop a frame
			TBuf8<128> buf2;
			iQ.Receive(buf2);
			// And resend
			err = iQ.Send(buf);
			}

		lineFrag.Set(lineFrag.Mid(Min(spaceFree, lineFrag.Length())));
		buf.Zero();
		}
	}

void CMessageQueueWriter::CloseWriter()
	{
	delete this;
	}

CMessageQueueWriter::CMessageQueueWriter()
	{
	}

CMessageQueueWriter::~CMessageQueueWriter()
	{
	iQ.Close();
	}

CDebugRouterClient* CDebugRouterClient::NewL(CCloggerServer& aServer)
	{
	CDebugRouterClient* self = new(ELeave) CDebugRouterClient(aServer);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

void CDebugRouterClient::ConstructL()
	{
	TInt err = RCloggerDebugRouter::LoadDriver();
	if (err != KErrAlreadyExists)
		{
		User::LeaveIfError(err);
		}
	User::LeaveIfError(iDebugRouter.Open());
	}

void CDebugRouterClient::OpenChunkL()
	{
	if (!iSharedChunk.Handle())
		{
		User::LeaveIfError(iDebugRouter.OpenChunk(iSharedChunk));
		iTempBuf.CreateL(2048);
		}
	}

CDebugRouterClient::CDebugRouterClient(CCloggerServer& aServer)
: CActive(CActive::EPriorityStandard + 1), iServer(aServer) // Priority just higher than the server object - it's slightly more important we keep the device driver buffer serviced than we handle normal logging requests
	{
	CActiveScheduler::Add(this);
	}

CDebugRouterClient::~CDebugRouterClient()
	{
	Cancel();
	if (iDebugRouter.Handle())
		{
		iDebugRouter.EnableDebugRouting(RCloggerDebugRouter::EDisable); // I don't really know whether to call this before or after the cancel
		iDebugRouter.Close();
		}
	RCloggerDebugRouter::CloseDriver();
	iSharedChunk.Close();
	iTempBuf.Close();
	}

TPtrC8 Read(TDes8& aTempBuf, TPtrC8& aData, TInt aLength, TPtrC8& aOverflowData)
	{
	if (aLength <= aData.Length())
		{
		// Can read it from this buffer
		TPtrC8 res(aData.Left(aLength));
		aData.Set(aData.Mid(aLength));
		return res;
		}
	else /*if (aLength > aData.Length())*/
		{
		// Descriptor spans wrap point, so need to copy into temp buf
		aTempBuf.Copy(aData.Left(aTempBuf.MaxLength())); // If anyone's crazy enough to write a platsec diagnostic string longer than 2k, it gets truncated
		TInt overflowLen = aLength - aData.Length();
		aData.Set(aOverflowData); // Wrap aData
		aOverflowData.Set(TPtrC8());
		if (overflowLen > aData.Length())
			{
			ASSERT(EFalse); // Shouldn't happen
			// in urel, return everything we've got
			return aData;
			}
		aTempBuf.Append(aData.Left(overflowLen));
		aData.Set(aData.Mid(overflowLen));
		return TPtrC8(aTempBuf);
		}
	}

void CDebugRouterClient::RunL()
	{
	TUint chunkSize = iSharedChunk.Size();
	const TUint KDataStartOffset = sizeof(SDebugChunkHeader);
	SDebugChunkHeader* chunkHeader = (SDebugChunkHeader*)iSharedChunk.Base();
	TUint start = chunkHeader->iStartOffset;
	TUint end = chunkHeader->iEndOffset;
	TUint overflows = chunkHeader->iOverflows;

	TBool wrap = (start > end);
	TUint endLen = wrap ? chunkSize - start : end - start;
	TUint startLen = wrap ? end - KDataStartOffset : 0;

	TPtrC8 endData(iSharedChunk.Base() + start, endLen);
	TPtrC8 startData;
	if (wrap) startData.Set(iSharedChunk.Base() + KDataStartOffset, startLen);
	TPtrC8 data(endData);

	while (data.Length())
		{
		TPtrC8 header = Read(iTempBuf, data, sizeof(SCloggerTraceInfo), startData);
		if (header.Length() < (TInt)sizeof(SCloggerTraceInfo))
			{
			ASSERT(EFalse); // for udeb
			break; // Something's broken
			}
		SCloggerTraceInfo info;
		Mem::Copy(&info, header.Ptr(), sizeof(SCloggerTraceInfo));
		ASSERT(info.iTraceType == 'K' || info.iTraceType == 'U' || info.iTraceType == 'P');
		TPtrC8 msg = Read(iTempBuf, data, info.iLength, startData);
		iServer.LogKernMessage(info.iTraceType, info.iTickCount, info.iThreadId, msg);
		}
	if (overflows)
		{
		_LIT8(KErr, "RDebug::Print buffer overflowed, %u calls not logged");
		iServer.LogError(KErr, overflows);
		}
	// Zero the memory so it's easier to read in the crashlog
	memclr(iSharedChunk.Base() + start, endLen);
	if (startLen) memclr(iSharedChunk.Base() + KDataStartOffset, startLen);
	
	StartRouting(-1); // Magic number to indicate no need to call EnableDebugRouting again
	}

void CDebugRouterClient::DoCancel()
	{
	iDebugRouter.CancelReceive();
	//iDebugRouter.EnableDebugRouting(EFalse);
	// We only call Cancel() outside of our destructor when doing a FlushBuffers, in that case we'd like to make use of the device drivers buffering if possible, and not tell it to stop completely
	}

void CDebugRouterClient::StartRouting(TBool aConsumeLogs)
	{
	if (aConsumeLogs != -1)
		{
		RCloggerDebugRouter::TEnableOption enable = aConsumeLogs ? RCloggerDebugRouter::EEnableRoutingAndConsume : RCloggerDebugRouter::EEnableRouting;
		iDebugRouter.EnableDebugRouting(enable);
		}
	if (!IsActive())
		{
		iDebugRouter.ReceiveData(iStatus);
		SetActive();
		}
	}

void CDebugRouterClient::StopRouting()
	{
	Cancel();
	iDebugRouter.EnableDebugRouting(RCloggerDebugRouter::EDisable);
	}

TInt CDebugRouterClient::RegisterCrashDumpAreas(const TDesC8& aCrashDumpAreas)
	{
	return iDebugRouter.RegisterCrashDumpAreas(aCrashDumpAreas);
	}

TInt CDebugRouterClient::CreateKernChunkForClient(RThread* aClient, TInt aMaxSize, TInt aCommittedSize, RChunk& aOurChunk)
	{
	SCreateChunkParams params;
	params.iHandleOfOtherThread = aClient ? aClient->Handle() : 0;
	params.iMaxSize = aMaxSize;
	params.iCommittedSize = aCommittedSize;
	params.iChunkHandle = 0; // Not strictly necessary to set this
	params.iOtherThreadChunkHandle = 0; // Not strictly necessary to set this
	
	TInt err = iDebugRouter.CreateChunk(params);
	if (err == KErrNone)
		{
		aOurChunk.SetHandle(params.iChunkHandle);
		return aClient ? params.iOtherThreadChunkHandle : KErrNone;
		}
	return err;
	// Return either an error, or the handle for the other thread, or KErrNone if there was no error and we didn't specify another thread
	}

TInt CDebugRouterClient::AdjustChunk(RChunk& aChunk, TInt aNewSize)
	{
	return iDebugRouter.AdjustChunk(aChunk, aNewSize);
	}