Pulled in from FCL: input, base64, fshell thread pool
authorTom Sutcliffe <thomas.sutcliffe@accenture.com>
Thu, 28 Oct 2010 16:54:54 +0100
changeset 78 b3ffff030d5c
parent 77 8df58d8c99e8
child 79 dae66483be2b
Pulled in from FCL: input, base64, fshell thread pool Also: * fed console size fixes * fzip smoketest * CBtraceAppStart
build/common/common.mmh
commands/activeview/activeview.mmp
commands/base64/base64.cif
commands/base64/base64.cpp
commands/base64/base64.mmp
commands/btservices/btservices.mmp
commands/cat/cat.mmp
commands/drvinfo/drvinfo.cif
commands/fed/inc/screenmngr.h
commands/fed/src/fed.cpp
commands/fed/src/mainfshell.cpp
commands/fed/src/screenmngr.cpp
commands/fed/src/textview.cpp
commands/fzip/fzip.cif
commands/fzip/fzip.cpp
commands/glinfo/glinfo.mmp
commands/group/bld.inf
commands/group/fshell_commands.iby
commands/input/input.cif
commands/input/input.cpp
commands/input/input.mmp
commands/kerninfo/kerninfo.mmp
commands/leak/leak.mmp
commands/pubsub/pubsub.mmp
commands/snake/snake.cpp
commands/tail/tail.mmp
commands/uidinfo/uidinfo.mmp
core/builtins/source.cif
core/src/command_constructors.cpp
core/src/command_constructors.h
core/src/command_factory.cpp
core/src/command_factory.h
core/src/command_wrappers.cpp
core/src/command_wrappers.h
core/src/commands.cpp
core/src/commands.h
core/src/error.h
core/src/fshell.cpp
core/src/fshell.h
core/src/fshell.mmp
core/src/lexer.cpp
core/src/lexer.h
core/src/line_completer.cpp
core/src/parser.cpp
core/src/parser.h
core/src/pipe_line.cpp
core/src/worker_thread.cpp
core/src/worker_thread.h
documentation/change_history.pod
documentation/cif_syntax.pod
libraries/btrace_parser/bwins/btrace_parseru.def
libraries/btrace_parser/eabi/btrace_parseru.def
libraries/btrace_parser/inc/btrace_parser.h
libraries/btrace_parser/inc/btrace_parser_defs.h
libraries/btrace_parser/src/btrace_appresponse.cpp
libraries/btrace_parser/src/btrace_appstart.cpp
libraries/btrace_parser/src/btrace_parser.mmp
libraries/iosrv/client/env.cpp
libraries/iosrv/server/file.cpp
--- a/build/common/common.mmh	Tue Oct 26 15:36:30 2010 +0100
+++ b/build/common/common.mmh	Thu Oct 28 16:54:54 2010 +0100
@@ -571,6 +571,8 @@
 #define FSHELL_UID_VT100USBCONS               0x10286F8A
 #define FSHELL_UID_WIN32CONS                  0x10286F8B
 #define FSHELL_UID_SHOWDEBUG                  0x10286F8D
+#define FSHELL_UID_INPUT                      0x10286B7D
+#define FSHELL_UID_BASE64                     0x10286B7E
 
 #else // Not FSHELL_PROTECTED_UIDS
 
@@ -725,6 +727,8 @@
 #define FSHELL_UID_VT100USBCONS               0xE0286F8A
 #define FSHELL_UID_WIN32CONS                  0xE0286F8B
 #define FSHELL_UID_SHOWDEBUG                  0xE0286F8D
+#define FSHELL_UID_INPUT                      0xE0286B7D
+#define FSHELL_UID_BASE64                     0xE0286B7E
 
 #endif // FSHELL_PROTECTED_UIDS
 
--- a/commands/activeview/activeview.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/activeview/activeview.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -25,5 +25,5 @@
 library         euser.lib
 library         iocli.lib
 library         viewcli.lib
-library         qr3.lib
+library         QR3.lib
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/base64/base64.cif	Thu Oct 28 16:54:54 2010 +0100
@@ -0,0 +1,84 @@
+# base64.cif
+# 
+# Copyright (c) 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
+#
+
+==name base64
+
+==short-description
+
+Encode to or decode from Base64.
+
+==long-description
+
+Turns binary data into an ASCII encoded form and vice versa.
+
+
+==argument enum operation
+
+==enum-value decode
+
+Read lines from C<STDIN> until F<KErrEof> or a blank line is read. Decode the read data and write the binary equivalent to the named file.
+
+==enum-value encode
+
+Read binary data from the named file, and write the encoded equivalent as lines to C<STDOUT>.
+
+
+==argument filename filename
+
+The name of the file to write when decoding. The name of the file to read when encoding.
+
+
+==option bool v verbose
+
+Enable verbose output.
+
+==option bool o overwrite
+
+When decoding, overwrite the output file if it already exists.
+
+
+==smoke-test
+
+exists base64_temp && rm -rf base64_temp
+mkdir base64_temp
+cd base64_temp
+
+echo -n 'A' > a.txt
+base64 encode a.txt | base64 decode a2.txt
+compare a.txt a2.txt || $Error
+
+echo -n 'AB' > ab.txt
+base64 encode ab.txt | base64 decode ab2.txt
+compare ab.txt ab2.txt || $Error
+
+echo -n 'ABC' > abc.txt
+base64 encode abc.txt | base64 decode abc2.txt
+compare abc.txt abc2.txt || $Error
+
+echo -n 'ABCD' > abcd.txt
+base64 encode abcd.txt | base64 decode abcd2.txt
+compare abcd.txt abcd2.txt || $Error
+
+echo -n 'ABCDE' > abcde.txt
+base64 encode abcde.txt | base64 decode abcde2.txt
+compare abcde.txt abcde2.txt || $Error
+
+# Assume fshell.cif is in same location as base64.cif (it's quite a bit bigger which is why we use it in preference)
+base64 encode $SCRIPT_PATH\fshell.cif | base64 decode fshell2.cif
+compare $SCRIPT_PATH\fshell.cif fshell2.cif || $Error
+
+cd ..
+rm -rf base64_temp
+
+==copyright
+
+Copyright (c) 2010 Accenture. All rights reserved.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/base64/base64.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -0,0 +1,433 @@
+// base64.cpp
+//
+// Copyright (c) 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 <fshell/ioutils.h>
+#include <fshell/common.mmh>
+
+using namespace IoUtils;
+
+const TInt KBlockSize = 512;
+const TInt KLineLength = 76;
+const TUint8 KBase64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+const TUint8 KPadCharacter = '=';
+
+const TUint8 KInvBase64[] =
+	{
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x3e,
+	0x0,
+	0x0,
+	0x0,
+	0x3f,
+	0x34,
+	0x35,
+	0x36,
+	0x37,
+	0x38,
+	0x39,
+	0x3a,
+	0x3b,
+	0x3c,
+	0x3d,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x1,
+	0x2,
+	0x3,
+	0x4,
+	0x5,
+	0x6,
+	0x7,
+	0x8,
+	0x9,
+	0xa,
+	0xb,
+	0xc,
+	0xd,
+	0xe,
+	0xf,
+	0x10,
+	0x11,
+	0x12,
+	0x13,
+	0x14,
+	0x15,
+	0x16,
+	0x17,
+	0x18,
+	0x19,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x0,
+	0x1a,
+	0x1b,
+	0x1c,
+	0x1d,
+	0x1e,
+	0x1f,
+	0x20,
+	0x21,
+	0x22,
+	0x23,
+	0x24,
+	0x25,
+	0x26,
+	0x27,
+	0x28,
+	0x29,
+	0x2a,
+	0x2b,
+	0x2c,
+	0x2d,
+	0x2e,
+	0x2f,
+	0x30,
+	0x31,
+	0x32,
+	0x33
+	};
+
+_LIT(KNewLine, "\r\n");
+_LIT(KCr, "\r");
+_LIT(KLf, "\n");
+
+
+class CCmdBase64 : public CCommandBase
+	{
+public:
+	static CCommandBase* NewLC();
+	~CCmdBase64();
+private:
+	CCmdBase64();
+	void DecodeL();
+	void EncodeL();
+private: // From CCommandBase.
+	virtual const TDesC& Name() const;
+	virtual void DoRunL();
+	virtual void ArgumentsL(RCommandArgumentList& aArguments);
+	virtual void OptionsL(RCommandOptionList& aOptions);
+private:
+	enum 
+		{
+		EDecode,
+		EEncode
+		} iOperation;
+	TFileName2 iFileName;
+	TBool iVerbose;
+	TBool iOverwrite;
+	};
+
+EXE_BOILER_PLATE(CCmdBase64)
+
+CCommandBase* CCmdBase64::NewLC()
+	{
+	CCmdBase64* self = new(ELeave) CCmdBase64();
+	CleanupStack::PushL(self);
+	self->BaseConstructL();
+	return self;
+	}
+
+CCmdBase64::~CCmdBase64()
+	{
+	}
+
+CCmdBase64::CCmdBase64()
+	{
+	}
+
+void CCmdBase64::DecodeL()
+	{
+	if (!iOverwrite)
+		{
+		LeaveIfFileExists(iFileName);
+		}
+
+	User::LeaveIfError(Stdin().CaptureAllKeys()); // To iosrv buffering incoming data if we're not keeping up.
+	Stdin().SetReadModeL(RIoReadHandle::ELine);
+
+	RFile file;
+	LeaveIfErr(file.Replace(FsL(), iFileName, EFileWrite | EFileStream), _L("Unabled to open '%S' for writing"), &iFileName);
+	CleanupClosePushL(file);
+
+	TBuf<KLineLength + 2> lineBuf;
+	TBuf8<(KLineLength / 4) * 3> outputBuf;
+	TBool finished(EFalse);
+	TBool started(EFalse);
+	while (!finished)
+		{
+		TInt err = Stdin().Read(lineBuf);
+		if (err == KErrNone)
+			{
+			if (iVerbose)
+				{
+				Printf(_L("Read %d chars:\r\n'%S'\r\n"), lineBuf.Length(), &lineBuf);
+				}
+			if ((lineBuf == KNewLine) || (lineBuf == KCr) || (lineBuf == KLf))
+				{
+				if (started)
+					{
+					finished = ETrue;
+					}
+				}
+			else
+				{
+				if (lineBuf.Right(2) == KNewLine)
+					{
+					lineBuf.SetLength(lineBuf.Length() - 2);
+					}
+				if ((lineBuf.Right(1) == KCr) || (lineBuf.Right(1) == KLf))
+					{
+					lineBuf.SetLength(lineBuf.Length() - 1);
+					}
+				const TInt lineLength = lineBuf.Length();
+				if ((lineLength % 4) > 0)
+					{
+					LeaveIfErr(KErrArgument, _L("Invalid base 64 encoded line (not a multiple of 4 characters in length):\r\n%S\r\n"), &lineBuf);
+					}
+
+				started = ETrue;
+				outputBuf.Zero();
+
+				for (TInt i = 0; i < lineLength; i += 4)
+					{
+					TInt n = ((TInt)KInvBase64[lineBuf[i]] << 18) + ((TInt)KInvBase64[lineBuf[i + 1]] << 12) + ((TInt)KInvBase64[lineBuf[i + 2]] << 6) + (TInt)KInvBase64[lineBuf[i + 3]];
+
+					if (lineBuf[i + 2] == KPadCharacter)
+						{
+						// Two pad characters
+						outputBuf.Append((n >> 16) & 0x000000FF);
+						}
+					else if (lineBuf[i + 3] == KPadCharacter)
+						{
+						// One pad character
+						outputBuf.Append((n >> 16) & 0x000000FF);
+						outputBuf.Append((n >> 8) & 0x000000FF);
+						}
+					else
+						{
+						outputBuf.Append((n >> 16) & 0x000000FF);
+						outputBuf.Append((n >> 8) & 0x000000FF);
+						outputBuf.Append(n & 0x000000FF);
+						}
+					}
+
+				LeaveIfErr(file.Write(outputBuf), _L("Failed to write to '%S'"), &iFileName);
+				if (iVerbose)
+					{
+					Printf(_L("Wrote %d bytes to '%S'\r\n"), outputBuf.Length(), &iFileName);
+					}
+				}
+			}
+		else if (err == KErrEof)
+			{
+			finished = ETrue;
+			}
+		else
+			{
+			LeaveIfErr(err, _L("Couldn't read STDIN"));
+			}
+		}
+
+	CleanupStack::PopAndDestroy(&file);
+	}
+
+void CCmdBase64::EncodeL()
+	{
+	LeaveIfFileNotFound(iFileName);
+
+	RFile file;
+	User::LeaveIfError(file.Open(FsL(), iFileName, EFileRead | EFileStream));
+	CleanupClosePushL(file);
+
+	TBuf8<KBlockSize> inputBuf;
+	TBuf<KLineLength + 2> outputBuf;
+	TBool finished(EFalse);
+	while (!finished)
+		{
+		TPtr8 ptr((TUint8*)inputBuf.Ptr() + inputBuf.Length(), 0, inputBuf.MaxLength() - inputBuf.Length());
+		LeaveIfErr(file.Read(ptr), _L("Couldn't read from '%S'"), &iFileName);
+
+		if (ptr.Length() > 0)
+			{
+			inputBuf.SetLength(inputBuf.Length() + ptr.Length());
+			const TInt inputBufLength = inputBuf.Length();
+			const TInt excess = inputBufLength % 3;
+			const TInt bytesToProcess = inputBufLength - excess;
+
+			for (TInt i = 0; i < bytesToProcess; i += 3)
+				{
+				// Combine the next three bytes into a 24 bit number.
+				TInt n = ((TInt)inputBuf[i] << 16) + ((TInt)inputBuf[i + 1] << 8) + (TInt)inputBuf[i + 2];
+
+				// Split the 24-bit number into four 6-bit numbers.
+				TUint8 n0 = (TUint8)(n >> 18) & 0x3F;
+				TUint8 n1 = (TUint8)(n >> 12) & 0x3F;
+				TUint8 n2 = (TUint8)(n >> 6) & 0x3F;
+				TUint8 n3 = (TUint8)n & 0x3F;
+
+				// Buffer the base64 encoded equivalent.
+				outputBuf.Append(KBase64Chars[n0]);
+				outputBuf.Append(KBase64Chars[n1]);
+				outputBuf.Append(KBase64Chars[n2]);
+				outputBuf.Append(KBase64Chars[n3]);
+
+				// Flush output buffer if it's full.
+				if (outputBuf.Length() == KLineLength)
+					{
+					outputBuf.Append(KNewLine);
+					Write(outputBuf);
+					outputBuf.Zero();
+					}
+				}
+
+			inputBuf.Delete(0, inputBufLength - excess);
+			}
+		else
+			{
+			// Process what's left over in inputBuf from the previous successful read, padding as required.
+			const TInt inputBufLength = inputBuf.Length();
+			if (inputBufLength > 0)
+				{
+				TInt n = (TInt)inputBuf[0] << 16;
+				if (inputBufLength > 1)
+					{
+					n += (TInt)inputBuf[1] << 8;
+					if (inputBufLength > 2)
+						{
+						n += (TInt)inputBuf[2];
+						}
+					}
+
+				TUint8 n0 = (TUint8)(n >> 18) & 0x3F;
+				TUint8 n1 = (TUint8)(n >> 12) & 0x3F;
+				TUint8 n2 = (TUint8)(n >> 6) & 0x3F;
+				TUint8 n3 = (TUint8)n & 0x3F;
+
+				outputBuf.Append(KBase64Chars[n0]);
+				outputBuf.Append(KBase64Chars[n1]);
+				if (inputBufLength > 1)
+					{
+					outputBuf.Append(KBase64Chars[n2]);
+					if (inputBufLength > 2)
+						{
+						outputBuf.Append(KBase64Chars[n3]);
+						}
+					}
+
+				for (TInt i = inputBufLength; i < 3; ++i)
+					{
+					outputBuf.Append('=');
+					}
+				}
+
+			if (outputBuf.Length() > 0)
+				{
+				outputBuf.Append(KNewLine);
+				Write(outputBuf);
+				}
+
+			finished = ETrue;
+			}
+		}
+
+	CleanupStack::PopAndDestroy(&file);
+	}
+
+const TDesC& CCmdBase64::Name() const
+	{
+	_LIT(KName, "base64");	
+	return KName;
+	}
+
+void CCmdBase64::ArgumentsL(RCommandArgumentList& aArguments)
+	{
+	_LIT(KArgOperation, "operation");
+	aArguments.AppendEnumL((TInt&)iOperation, KArgOperation);
+
+	_LIT(KArgFilename, "filename");
+	aArguments.AppendFileNameL(iFileName, KArgFilename);
+	}
+
+void CCmdBase64::OptionsL(RCommandOptionList& aOptions)
+	{
+	_LIT(KOptVerbose, "verbose");
+	aOptions.AppendBoolL(iVerbose, KOptVerbose);
+
+	_LIT(KOptOverwrite, "overwrite");
+	aOptions.AppendBoolL(iOverwrite, KOptOverwrite);
+	}
+
+void CCmdBase64::DoRunL()
+	{
+	switch (iOperation)
+		{
+		case EDecode:
+			DecodeL();
+			break;
+		case EEncode:
+			EncodeL();
+			break;
+		default:
+			ASSERT(EFalse);
+		}
+	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/base64/base64.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -0,0 +1,27 @@
+// base64.mmp
+//
+// Copyright (c) 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 <fshell/common.mmh>
+
+target			fshell_base64.exe
+targettype		exe
+uid                     FSHELL_UID2_FSHELL_EXE FSHELL_UID_BASE64
+capability		FSHELL_CAP_MMP_NORMAL
+
+userinclude		.
+#include <fshell/fsh_system_include.mmh>
+sourcepath		.
+source			base64.cpp
+
+library			euser.lib
+library			efsrv.lib
+library			iocli.lib
--- a/commands/btservices/btservices.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/btservices/btservices.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -24,7 +24,7 @@
 sourcepath		.
 source			btservices.cpp BtServicesEng.cpp
 
-library			euser.lib bafl.lib charconv.lib
+library			euser.lib bafl.lib CHARCONV.lib
 library			iocli.lib
 LIBRARY			esock.lib sdpagent.lib sdpdatabase.lib btextnotifiers.lib btmanclient.lib bluetooth.lib btdevice.lib
 
--- a/commands/cat/cat.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/cat/cat.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -30,7 +30,7 @@
 library         euser.lib
 library         efsrv.lib
 library         iocli.lib
-library         charconv.lib
+library         CHARCONV.lib
 library         ltkutils.lib
 
 macro           EXE_BUILD
--- a/commands/drvinfo/drvinfo.cif	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/drvinfo/drvinfo.cif	Thu Oct 28 16:54:54 2010 +0100
@@ -14,7 +14,7 @@
 
 ==short-description
 
-Display information about the currently installed drives.
+Display information about the currently available drives.
 
 ==argument string drive_letter optional
 
--- a/commands/fed/inc/screenmngr.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/fed/inc/screenmngr.h	Thu Oct 28 16:54:54 2010 +0100
@@ -27,7 +27,7 @@
 	{
 public:
 	//Returns the TWindow occupied by the command window
-	const TWindow& GetCommandWindow() {return iCmdWnd;}
+	const TWindow& GetCommandWindow();
 	//Sets new coordinates of the screen available to this screen manager
 	void ResizeScreenL(const TWindow& aWindow);
 	//Resizes all Views to make space (if possible) for the new size of the command window.
--- a/commands/fed/src/fed.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/fed/src/fed.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -232,7 +232,7 @@
 			OpenDocumentL();
 			break;
 		case ECmdRefresh:
-			iScreenMngr.RefreshScreenL();
+			RedrawEverythingL();
 			break;
 		default:
 			return EFalse;
@@ -372,5 +372,9 @@
 
 void CFed::RedrawEverythingL()
 	{
-	ExecuteCommandL(ECmdRefresh);
+	// This is not very nice but the whole window management thing within fed needs gutting and simplifying
+	TSize size = iConsole.ScreenSize();
+	iScreenMngr.ResizeScreenL(TWindow(0, 0, size.iWidth, size.iHeight));
+	iCmdWindow->SetWindow(iScreenMngr.GetCommandWindow());
+	iScreenMngr.RefreshScreenL();
 	}
--- a/commands/fed/src/mainfshell.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/fed/src/mainfshell.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -112,12 +112,9 @@
 	iFed->StartL(argsBuf);
 	}
 
-void CCmdFed::StdinChange(TUint aChange)
+void CCmdFed::StdinChange(TUint /*aChange*/)
 	{
-	if (aChange & RIoReadHandle::EGainedForeground)
-		{
-		iFed->RedrawEverythingL();
-		}
+	iFed->RedrawEverythingL();
 	}
 
 void AssertionFail(const char* aAssertion, const char* aFile, TInt aLine)
--- a/commands/fed/src/screenmngr.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/fed/src/screenmngr.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -28,20 +28,14 @@
 	if(aWindow == iScreenWnd)
 		return;
 
-	//We need to leave one char on each side for the border around the console window, so the width/height is actually less by 2
+	// We need to leave one char on each side for the border around the console window, so the width/height is actually less by 2
+	// (*if* we're running as a pure CConsoleBase app under tshell. Doesn't apply when built as an fshell app, as KConsoleWidthCorrection is zero in that case).
 	iMainWnd.iX = iCmdWnd.iX = aWindow.iX;
 	iMainWnd.iWidth = iCmdWnd.iWidth = aWindow.iWidth + KConsoleWidthCorrection;
 	iMainWnd.iY = aWindow.iY;
 
 	//Based on proportions of windows on the old screen calculate proportions on the new screen
-	TInt newcmdh = 1;
-	if(iScreenWnd.iHeight)
-		{
-		newcmdh = ((iCmdWnd.iHeight*KRM) * (aWindow.iHeight*KRM)) / iScreenWnd.iHeight*KRM*KRM;
-		if(newcmdh >= aWindow.iHeight)
-			newcmdh = aWindow.iHeight/2;
-		}
-	iCmdWnd.iHeight = newcmdh > 0 ? newcmdh : 1;
+	iCmdWnd.iWidth = aWindow.iWidth;
 	iMainWnd.iHeight = aWindow.iHeight - iCmdWnd.iHeight + KConsoleWidthCorrection;
 	iCmdWnd.iY = aWindow.iY + iMainWnd.iHeight;
 	iScreenWnd = aWindow;
@@ -50,6 +44,11 @@
 		iCurrentView->ResizeL(iMainWnd);
 	}
 
+const TWindow& CScreenManager::GetCommandWindow()
+	{
+	return iCmdWnd;
+	}
+
 const TWindow& CScreenManager::ResizeCommandWindowL(TInt aHeight)
 	{
 	if(aHeight == iCmdWnd.iHeight)
--- a/commands/fed/src/textview.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/fed/src/textview.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -84,24 +84,26 @@
 	}
 
 void CTextView::ResizeL(const TWindow& aWindow)
-{
+	{
 	iActive = ETrue;
 	TWindow oldWindow = iWindow;
 	StoreWindow(aWindow);
+	iLine.Zero();
+	iLine.ReAllocL(iConsole.ScreenSize().iWidth);
 	DoResizeL(oldWindow);
-}
+	}
 
 void CTextView::RedrawL(const TWindow& aWindow)
-{
+	{
 	iActive = ETrue;
 	StoreWindow(aWindow);
 	DoRedrawL();
-}
+	}
 
 void CTextView::DeactivateL()
-{
+	{
 	iActive = EFalse;
-}
+	}
 
 //MSharedCacheClient
 void CTextView::InvalidateBuffer(TRequestStatus& aStatus)
--- a/commands/fzip/fzip.cif	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/fzip/fzip.cif	Thu Oct 28 16:54:54 2010 +0100
@@ -62,3 +62,29 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+export TESTDATA "This is some test data for fzip"
+echo -n "$TESTDATA" > test.txt
+rm test.txt.zip $Silent &| echo -n ""
+
+# Test zip
+fzip --file test.txt
+exists test.txt.zip || $Error
+rm test.txt
+
+# Test unzip
+fzip --unzip test.txt.zip
+exists test.txt || $Error
+cat -b test.txt | export -s RESULT
+var RESULT == "$TESTDATA" || $Error
+
+# Test that we don't overwrite files unless --overwrite is specified
+fzip --file test.txt $Silent &| var ? == "-11" || $Error
+fzip --file test.txt --overwrite
+
+fzip --unzip test.txt.zip $Silent &| var ? == "-11" || $Error
+fzip --unzip test.txt.zip --overwrite
+
+rm test.txt
+rm test.txt.zip
--- a/commands/fzip/fzip.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/fzip/fzip.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -374,8 +374,16 @@
 	if (aMember.Name()->Right(1) == _L("\\")) return; // It's a directory entry, nothing more to be done
 
 	// prep. the stream
-	RZipFileMemberReaderStream* readStream;
-	aZip.GetInputStreamL(&aMember, readStream);
+	RZipFileMemberReaderStream* readStream = NULL;
+	err = aZip.GetInputStreamL(&aMember, readStream);
+	if (err == CZipFile::KCompressionMethodNotSupported)
+		{
+		LeaveIfErr(KErrNotSupported, _L("Zip compression method not supported"));
+		}
+	else
+		{
+		LeaveIfErr(err, _L("Unable to get input stream"));
+		}
 	CleanupStack::PushL(readStream);
 
 	if (iOverwrite)
--- a/commands/glinfo/glinfo.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/glinfo/glinfo.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -27,9 +27,9 @@
 library         iocli.lib
 
 #ifdef FSHELL_EGL_SUPPORT
-library         libegl.lib
+library         libEGL.lib
 #endif
 
 #ifdef FSHELL_OPENVG_SUPPORT
-library         libopenvg.lib
+library         libOpenVG.lib
 #endif
--- a/commands/group/bld.inf	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/group/bld.inf	Thu Oct 28 16:54:54 2010 +0100
@@ -37,6 +37,8 @@
 ..\fed\group\fed.cif                     z:\resource\cif\fshell\fed.cif
 ..\snake\snake.cif                       z:\resource\cif\fshell\snake.cif
 ..\iniedit\iniedit.cif                   z:\resource\cif\fshell\iniedit.cif
+..\input\input.cif                       z:\resource\cif\fshell\input.cif
+..\base64\base64.cif                     z:\resource\cif\fshell\base64.cif
 
 PRJ_MMPFILES
 ..\cat\cat.mmp
@@ -60,6 +62,8 @@
 ..\fed\group\fed.mmp
 ..\snake\snake.mmp
 ..\iniedit\iniedit.mmp
+..\input\input.mmp
+..\base64\base64.mmp
 
 #ifdef FSHELL_CORE_SUPPORT_SWI
 PRJ_EXPORTS
--- a/commands/group/fshell_commands.iby	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/group/fshell_commands.iby	Thu Oct 28 16:54:54 2010 +0100
@@ -110,6 +110,12 @@
 FSHELL_EXECUTABLE_FILE(iniedit.exe)
 FSHELL_COMMAND_INFO_FILE(fshell,iniedit.cif)
 
+FSHELL_EXECUTABLE_FILE(fshell_input.exe)
+FSHELL_COMMAND_INFO_FILE(fshell,input.cif)
+
+FSHELL_EXECUTABLE_FILE(fshell_base64.exe)
+FSHELL_COMMAND_INFO_FILE(fshell,base64.cif)
+
 #ifdef FSHELL_CORE_SUPPORT_PATCHDATA
 FSHELL_EXECUTABLE_FILE(patchdata.exe)
 FSHELL_COMMAND_INFO_FILE(fshell,patchdata.cif)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/input/input.cif	Thu Oct 28 16:54:54 2010 +0100
@@ -0,0 +1,41 @@
+# input.cif
+# 
+# Copyright (c) 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
+#
+
+==name input
+
+==short-description
+
+Simulate user input events.
+
+==long-description
+
+A command that converts console key presses into arbitrary user input events, typically Symbian OS key events. Note, similar functionality is available via Autometric. However, the Autometric command syntax is intended for scripting as so is verbose. By contrast this command accepts single character inputs making it better suited to interactive use. Also, Autometric can only be used if the btrace buffer is free. This command doesn't use btrace and so doesn't have this limitation.
+ 
+==option bool s show
+
+Show the key mappings available for this platform.
+ 
+==option uint k scan-code
+
+Input a specific keypress as a scan-code and then exit.
+
+==option uint m modifiers
+
+Must be used in conjunction with C<--scan-code>. Specifies a set of modifiers to be used with the scan-code (as defined in F<e32keys.h>).
+
+==argument uint key optional
+
+Simulate a single event and then exit. If not specified, continually reads keys from the console. 'q' causes the command to exit.
+
+==copyright
+
+Copyright (c) 2010 Accenture. All rights reserved.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/input/input.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -0,0 +1,217 @@
+// input.cpp
+// 
+// Copyright (c) 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 <e32keys.h>
+#include <fshell/ioutils.h>
+#include <fshell/common.mmh>
+#include <fshell/ltkutils.h>
+
+using namespace IoUtils;
+
+
+class TKeyMapping
+	{
+public:
+	TUint16 iConsoleKey;
+	TUint16 iScanCode;
+	TUint16 iModifiers;
+#ifdef __WINS__
+	const TText* iLabel;
+#else
+	const wchar_t* iLabel;
+#endif
+	};
+
+const TKeyMapping KKeyMappings[] = 
+	{
+		{ '0', '0',	0, L"Zero" },
+		{ '1', '1',	0, L"One" },
+		{ '2', '2',	0, L"Two" },
+		{ '3', '3',	0, L"Three" },
+		{ '4', '4',	0, L"Four" },
+		{ '5', '5',	0, L"Five" },
+		{ '6', '6',	0, L"Six" },
+		{ '7', '7',	0, L"Seven" },
+		{ '8', '8',	0, L"Eight" },
+		{ '9', '9',	0, L"Nine" },
+		{ '*', '*',	0, L"Star" },
+		{ '#', '#',	0, L"Hash" },
+		{ EKeyUpArrow, EStdKeyUpArrow,	0, L"Up" },
+		{ EKeyDownArrow, EStdKeyDownArrow,	0, L"Down" },
+		{ EKeyLeftArrow, EStdKeyLeftArrow,	0, L"Left" },
+		{ EKeyRightArrow, EStdKeyRightArrow,	0, L"Right" },
+#ifdef LTK_PLATFORM_DCM
+		{ EKeyEnter, 167,	0, L"Enter" },
+		{ 'o', 164,	0, L"Menu" },
+		{ 'p', 165,	0, L"Camera" },
+		{ 'l', 228,	0, L"Mail" },
+		{ ';', 229,	0, L"IMode" },
+		{ ',', 196,	0, L"Call" },
+		{ '.', EStdKeyBackspace, 0, L"Clear" },
+		{ '/', 197,	0, L"End" },
+		{ 'm', 230,	0, L"Multi" },
+#endif
+	};
+const TInt KNumKeyMappings = sizeof(KKeyMappings) / sizeof(TKeyMapping);
+
+
+class CCmdInput : public CCommandBase
+	{
+public:
+	static CCommandBase* NewLC();
+	~CCmdInput();
+private:
+	CCmdInput();
+	void ShowKeyMappings();
+	void SimulateKeyL(TInt aConsoleKey);
+private: // From CCommandBase.
+	virtual const TDesC& Name() const;
+	virtual void DoRunL();
+	virtual void ArgumentsL(RCommandArgumentList& aArguments);
+	virtual void OptionsL(RCommandOptionList& aOptions);
+private:
+	TUint iConsoleKey;
+	TBool iShow;
+	TUint iScanCode;
+	TUint iModifiers;
+	};
+
+EXE_BOILER_PLATE(CCmdInput)
+
+CCommandBase* CCmdInput::NewLC()
+	{
+	CCmdInput* self = new(ELeave) CCmdInput();
+	CleanupStack::PushL(self);
+	self->BaseConstructL();
+	return self;
+	}
+
+CCmdInput::~CCmdInput()
+	{
+	}
+
+CCmdInput::CCmdInput()
+	{
+	}
+
+void CCmdInput::ShowKeyMappings()
+	{
+	IoUtils::CTextBuffer* buf = IoUtils::CTextBuffer::NewLC(0x100);
+
+	buf->AppendL(_L("Input key\tScan Code\tModifiers\tLabel\r\n"));
+	for (TInt i = 0; i < KNumKeyMappings; ++i)
+		{
+		const TKeyMapping& mapping = KKeyMappings[i];
+		if (mapping.iConsoleKey == EKeyUpArrow)
+			{
+			buf->AppendFormatL(_L("Up (0x%x)\t0x%x\t0x%x\t%s\r\n"), mapping.iConsoleKey, mapping.iScanCode, mapping.iModifiers, mapping.iLabel);
+			}
+		else if (mapping.iConsoleKey == EKeyDownArrow)
+			{
+			buf->AppendFormatL(_L("Down (0x%x)\t0x%x\t0x%x\t%s\r\n"), mapping.iConsoleKey, mapping.iScanCode, mapping.iModifiers, mapping.iLabel);
+			}
+		else if (mapping.iConsoleKey == EKeyLeftArrow)
+			{
+			buf->AppendFormatL(_L("Left (0x%x)\t0x%x\t0x%x\t%s\r\n"), mapping.iConsoleKey, mapping.iScanCode, mapping.iModifiers, mapping.iLabel);
+			}
+		else if (mapping.iConsoleKey == EKeyRightArrow)
+			{
+			buf->AppendFormatL(_L("Right (0x%x)\t0x%x\t0x%x\t%s\r\n"), mapping.iConsoleKey, mapping.iScanCode, mapping.iModifiers, mapping.iLabel);
+			}
+		else if (mapping.iConsoleKey == EKeyEnter)
+			{
+			buf->AppendFormatL(_L("Enter (0x%x)\t0x%x\t0x%x\t%s\r\n"), mapping.iConsoleKey, mapping.iScanCode, mapping.iModifiers, mapping.iLabel);
+			}
+		else
+			{
+			buf->AppendFormatL(_L("%c (0x%x)\t0x%x\t0x%x\t%s\r\n"), mapping.iConsoleKey, mapping.iConsoleKey, mapping.iScanCode, mapping.iModifiers, mapping.iLabel);
+			}
+		}
+
+	CTextFormatter* formatter = CTextFormatter::NewLC(Stdout());
+	formatter->TabulateL(0, 2, buf->Descriptor());
+	Write(formatter->Descriptor());
+
+	CleanupStack::PopAndDestroy(2, buf);
+	}
+
+void CCmdInput::SimulateKeyL(TInt aConsoleKey)
+	{
+	for (TInt i = 0; i < KNumKeyMappings; ++i)
+		{
+		const TKeyMapping& mapping = KKeyMappings[i];
+		if (aConsoleKey == mapping.iConsoleKey)
+			{
+			LtkUtils::InjectRawKeyEvent(mapping.iScanCode, mapping.iModifiers, 0);
+			break;
+			}
+		}
+	}
+
+const TDesC& CCmdInput::Name() const
+	{
+	_LIT(KName, "input");	
+	return KName;
+	}
+
+void CCmdInput::ArgumentsL(RCommandArgumentList& aArguments)
+	{
+	aArguments.AppendUintL(iConsoleKey, _L("key"));
+	}
+
+void CCmdInput::OptionsL(RCommandOptionList& aOptions)
+	{
+	aOptions.AppendBoolL(iShow, _L("show"));
+	aOptions.AppendUintL(iScanCode, _L("scan-code"));
+	aOptions.AppendUintL(iModifiers, _L("modifiers"));
+	}
+
+void CCmdInput::DoRunL()
+	{
+	if (iShow)
+		{
+		ShowKeyMappings();
+		}
+	else if (iOptions.IsPresent(&iScanCode))
+		{
+		LtkUtils::InjectRawKeyEvent(iScanCode, iModifiers, 0);
+		}
+	else
+		{
+		if (iArguments.IsPresent(&iConsoleKey))
+			{
+			SimulateKeyL(iConsoleKey);
+			}
+		else
+			{
+			RIoConsoleReadHandle& stdin = Stdin();
+			stdin.SetReadModeL(RIoReadHandle::EFull);
+			TBuf<1> buf;
+			TInt err = KErrNone;
+			while (err == KErrNone)
+				{
+				err = stdin.Read(buf);
+				if (err == KErrNone)
+					{
+					if (buf[0] == 'q')
+						{
+						break;
+						}
+					else
+						{
+						SimulateKeyL(buf[0]);
+						}
+					}
+				}
+			}
+		}
+	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/input/input.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -0,0 +1,27 @@
+// input.mmp
+// 
+// Copyright (c) 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 <fshell/common.mmh>
+
+target			fshell_input.exe
+targettype		exe
+uid				FSHELL_UID2_FSHELL_EXE FSHELL_UID_INPUT
+capability		FSHELL_CAP_MMP_NORMAL
+
+userinclude		.
+#include <fshell/fsh_system_include.mmh>
+sourcepath		.
+source			input.cpp
+
+library			euser.lib
+library			iocli.lib
+library			ltkutils.lib
--- a/commands/kerninfo/kerninfo.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/kerninfo/kerninfo.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -26,7 +26,7 @@
 
 library         euser.lib
 library         iocli.lib
-library         qr3.lib
+library         QR3.lib
 library         ltkutils.lib
 
 #ifdef FSHELL_SPCRE_SUPPORT
--- a/commands/leak/leak.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/leak/leak.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -31,7 +31,7 @@
 #endif
 
 #ifdef FSHELL_QR3_SUPPORT_LOGGINGALLOCATOR
-library			loggingallocator.lib
+library			LoggingAllocator.lib
 #endif
 
 macro           EXE_BUILD
--- a/commands/pubsub/pubsub.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/pubsub/pubsub.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -25,7 +25,7 @@
 
 library         euser.lib
 library         iocli.lib
-library         qr3.lib
+library         QR3.lib
 library         ltkutils.lib
 library         btrace_parser.lib
 FSHELL_TRACE_CONTROL_LIBRARY
--- a/commands/snake/snake.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/snake/snake.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -439,8 +439,10 @@
 void CCmdSnake::PlaceBait()
 	{
 	TBool ok;
+	TInt attempts = 0;
 	do
 		{
+		attempts++;
 		ok = ETrue;
 		iBait.iX = Math::Rand(iRandomSeed) % iBoardSize.iWidth;
 		iBait.iY = Math::Rand(iRandomSeed) % iBoardSize.iHeight;
@@ -453,6 +455,7 @@
 				if (iSnake[i] == iBait) ok = EFalse;
 				}
 			}
+		if (attempts >= iBoardSize.iWidth * iBoardSize.iHeight) return; // Snake has filled the entire board! Time to bail (the snake will run into itself right after we return here)
 		} while (!ok);
 	iCons.SetCursorPosAbs(iBait);
 	iCons.Write(iUnicode ? KUnicodeBait : KBait);
--- a/commands/tail/tail.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/tail/tail.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -30,6 +30,6 @@
 library         euser.lib
 library         efsrv.lib
 library         iocli.lib
-library         charconv.lib
+library         CHARCONV.lib
 
 macro           EXE_BUILD
--- a/commands/uidinfo/uidinfo.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/commands/uidinfo/uidinfo.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -25,4 +25,4 @@
 
 library			euser.lib
 library			iocli.lib
-library			ltkutils.lib qr3.lib
\ No newline at end of file
+library			ltkutils.lib QR3.lib
\ No newline at end of file
--- a/core/builtins/source.cif	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/builtins/source.cif	Thu Oct 28 16:54:54 2010 +0100
@@ -40,9 +40,9 @@
 
 L<debug|debug>
 
-==argument filename script_file_name
+==argument filename script_file_name optional
 
-The name of the script file to be run.
+The name of the script file to be run. If not specified (or specified with zero length, i.e. ''), the script will be read from C<stdin>.
 
 ==argument string script_args optional last
 
--- a/core/src/command_constructors.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/command_constructors.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -66,32 +66,32 @@
 // CThreadCommandConstructor.
 //
 
-CThreadCommandConstructor* CThreadCommandConstructor::NewLC(TCommandConstructor aConstructor, TUint aFlags)
+CThreadCommandConstructor* CThreadCommandConstructor::NewLC(TCommandConstructor aConstructor, TUint aFlags, MTaskRunner* aTaskRunner)
 	{
 	CCommandBase* command = (*aConstructor)();
-	CThreadCommandConstructor* self = CThreadCommandConstructor::NewLC(command->Name(), aConstructor, aFlags);
+	CThreadCommandConstructor* self = CThreadCommandConstructor::NewLC(command->Name(), aConstructor, aFlags, aTaskRunner);
 	CleanupStack::Pop(self);
 	CleanupStack::PopAndDestroy(command);
 	CleanupStack::PushL(self);
 	return self;
 	}
 
-CThreadCommandConstructor* CThreadCommandConstructor::NewLC(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aFlags)
+CThreadCommandConstructor* CThreadCommandConstructor::NewLC(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aFlags, MTaskRunner* aTaskRunner)
 	{
-	CThreadCommandConstructor* self = new(ELeave) CThreadCommandConstructor(aFlags, aConstructor);
+	CThreadCommandConstructor* self = new(ELeave) CThreadCommandConstructor(aFlags, aConstructor, aTaskRunner);
 	CleanupStack::PushL(self);
 	self->BaseConstructL(aCommandName);
 	return self;
 	}
 
-CThreadCommandConstructor::CThreadCommandConstructor(TUint aFlags, TCommandConstructor aConstructor)
-	: CCommandConstructorBase(ETypeThread), iFlags(aFlags), iConstructor(aConstructor)
+CThreadCommandConstructor::CThreadCommandConstructor(TUint aFlags, TCommandConstructor aConstructor, MTaskRunner* aTaskRunner)
+	: CCommandConstructorBase(ETypeThread), iFlags(aFlags), iConstructor(aConstructor), iTaskRunner(aTaskRunner)
 	{
 	}
 
 MCommand* CThreadCommandConstructor::ConstructCommandL()
 	{
-	return CThreadCommand::NewL(CommandName(), iConstructor, iFlags);
+	return CThreadCommand::NewL(CommandName(), iConstructor, iFlags, iTaskRunner);
 	}
 
 void CThreadCommandConstructor::AppendDescriptionL(RLtkBuf16& aBuf) const
--- a/core/src/command_constructors.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/command_constructors.h	Thu Oct 28 16:54:54 2010 +0100
@@ -18,6 +18,8 @@
 #include <fshell/ioutils.h>
 
 class MCommand;
+class MTaskRunner;
+
 namespace LtkUtils { class RLtkBuf16; }
 using LtkUtils::RLtkBuf16;
 
@@ -61,16 +63,17 @@
 class CThreadCommandConstructor : public CCommandConstructorBase
 	{
 public:
-	static CThreadCommandConstructor* NewLC(TCommandConstructor aConstructor, TUint aFlags);
-	static CThreadCommandConstructor* NewLC(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aFlags);
+	static CThreadCommandConstructor* NewLC(TCommandConstructor aConstructor, TUint aFlags, MTaskRunner* aTaskRunner);
+	static CThreadCommandConstructor* NewLC(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aFlags, MTaskRunner* aTaskRunner);
 private:
-	CThreadCommandConstructor(TUint aFlags, TCommandConstructor aConstructor);
+	CThreadCommandConstructor(TUint aFlags, TCommandConstructor aConstructor, MTaskRunner* aTaskRunner);
 private: // From CCommandConstructorBase.
 	virtual MCommand* ConstructCommandL();
 	virtual void AppendDescriptionL(RLtkBuf16& aBuf) const;
 private:
 	TUint iFlags;
 	TCommandConstructor iConstructor;
+	MTaskRunner* iTaskRunner;
 	};
 
 class CExeCommandConstructor : public CCommandConstructorBase
--- a/core/src/command_factory.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/command_factory.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -25,6 +25,7 @@
 #include "ymodem.h"
 #include "version.h"
 #include "ciftest.h"
+#include "worker_thread.h"
 
 //
 // Constants.
@@ -84,6 +85,7 @@
 	Cancel();
 	iCommands.ResetAndDestroy();
 	iLock.Close();
+	delete iThreadPool;
 	}
 
 TInt CompareCommandNames(const CCommandConstructorBase& aCommand1, const CCommandConstructorBase& aCommand2)
@@ -250,6 +252,7 @@
 	{
 	User::LeaveIfError(iLock.CreateLocal());
 	User::LeaveIfError(iFs.DriveList(iDriveList));
+	iThreadPool = CThreadPool::NewL();
 
 	AddThreadCommandL(CCmdExit::NewLC); // Note, this command should never execute as 'exit' has handled explicitly by CParser. It exists so that 'exit' appears in fshell's help list and also to support 'exit --help'.
 	AddThreadCommandL(CCmdHelp::NewLC, CThreadCommand::ESharedHeap);
@@ -429,14 +432,14 @@
 
 void CCommandFactory::AddThreadCommandL(TCommandConstructor aConstructor, TUint aFlags)
 	{
-	CCommandConstructorBase* constructor = CThreadCommandConstructor::NewLC(aConstructor, aFlags);
+	CCommandConstructorBase* constructor = CThreadCommandConstructor::NewLC(aConstructor, aFlags, iThreadPool);
 	AddCommandL(constructor);
 	CleanupStack::Pop(constructor);
 	}
 
 void CCommandFactory::AddThreadCommandL(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aAttributes, TUint aFlags)
 	{
-	CCommandConstructorBase* constructor = CThreadCommandConstructor::NewLC(aCommandName, aConstructor, aFlags);
+	CCommandConstructorBase* constructor = CThreadCommandConstructor::NewLC(aCommandName, aConstructor, aFlags, iThreadPool);
 	constructor->SetAttributes(aAttributes);
 	AddCommandL(constructor);
 	CleanupStack::Pop(constructor);
@@ -444,7 +447,7 @@
 
 void CCommandFactory::AddAliasCommandL(const TDesC& aAliasName, TCommandConstructor aConstructor, const TDesC* aAdditionalArguments, const TDesC* aReplacementArguments, TUint aAttributes, TUint aFlags)
 	{
-	CCommandConstructorBase* aliasedCommand = CThreadCommandConstructor::NewLC(aConstructor, aFlags);
+	CCommandConstructorBase* aliasedCommand = CThreadCommandConstructor::NewLC(aConstructor, aFlags, iThreadPool);
 	CCommandConstructorBase* constructor = CAliasCommandConstructor::NewLC(aAliasName, aliasedCommand, aAdditionalArguments, aReplacementArguments);
 	CleanupStack::Pop(2, aliasedCommand); // Now owned by "constructor".
 	CleanupStack::PushL(constructor);
--- a/core/src/command_factory.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/command_factory.h	Thu Oct 28 16:54:54 2010 +0100
@@ -15,7 +15,9 @@
 #define __COMMAND_FACTORY_H__
 
 #include <e32base.h>
-#include "command_constructors.h"
+#include "command_wrappers.h"
+class CCommandConstructorBase;
+class CThreadPool;
 #include "error.h"
 
 class RFs;
@@ -51,6 +53,7 @@
 	virtual void RunL();
 	virtual void DoCancel();
 	virtual TInt RunError(TInt aError);
+
 private:
 	RFs& iFs;
 	mutable RMutex iLock;
@@ -60,6 +63,7 @@
 	TBool iFailedToScanFileSystem;
 	TThreadId iFactoryThreadId; // The one the CCommandFactory active object lives in
 	RAllocator* iFactoryAllocator;
+	CThreadPool* iThreadPool;
 	};
 
 
--- a/core/src/command_wrappers.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/command_wrappers.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -11,20 +11,14 @@
 //
 
 #include "command_wrappers.h"
-
-
-//
-// Constants.
-//
-
-const TInt KMaxHeapSize = 1024*1024; // 1 MB
-
+#include "worker_thread.h"
 
 //
 // CCommandWrapperBase.
 //
 
 CCommandWrapperBase::CCommandWrapperBase()
+	: CActive(CActive::EPriorityStandard)
 	{
 	}
 
@@ -96,14 +90,23 @@
 	delete this;
 	}
 
+void CCommandWrapperBase::RunL()
+	{
+	// Optionally for use by subclasses
+	}
+
+void CCommandWrapperBase::DoCancel()
+	{
+	// Optionally for use by subclasses
+	}
 
 //
 // CThreadCommand.
 //
 
-CThreadCommand* CThreadCommand::NewL(const TDesC& aName, TCommandConstructor aCommandConstructor, TUint aFlags)
+CThreadCommand* CThreadCommand::NewL(const TDesC& aName, TCommandConstructor aCommandConstructor, TUint aFlags, MTaskRunner* aTaskRunner)
 	{
-	CThreadCommand* self = new(ELeave) CThreadCommand(aCommandConstructor, aFlags);
+	CThreadCommand* self = new(ELeave) CThreadCommand(aCommandConstructor, aFlags, aTaskRunner);
 	CleanupStack::PushL(self);
 	self->ConstructL(aName);
 	CleanupStack::Pop(self);
@@ -112,14 +115,15 @@
 
 CThreadCommand::~CThreadCommand()
 	{
-	delete iWatcher;
-	delete iArgs;
+	Cancel();
+	delete iCommandLine;
 	iThread.Close();
 	}
 
-CThreadCommand::CThreadCommand(TCommandConstructor aCommandConstructor, TUint aFlags)
-	: iFlags(aFlags), iCommandConstructor(aCommandConstructor)
+CThreadCommand::CThreadCommand(TCommandConstructor aCommandConstructor, TUint aFlags, MTaskRunner* aTaskRunner)
+	: iFlags(aFlags), iCommandConstructor(aCommandConstructor), iTaskRunner(aTaskRunner)
 	{
+	CActiveScheduler::Add(this);
 	iThread.SetHandle(0); // By default RThread refers to the current thread. This results in fshell's thread exiting if this object gets killed before it has managed to open a real thread handle.
 	if (iFlags & EUpdateEnvironment) iFlags |= ESharedHeap; // Update environment implies a shared heap, ever since we did away with the explict SwitchAllocator
 	}
@@ -127,66 +131,31 @@
 void CThreadCommand::ConstructL(const TDesC& aName)
 	{
 	BaseConstructL(aName);
-	iWatcher = CThreadWatcher::NewL();
 	}
 
-void CommandThreadStartL(CThreadCommand::TArgs& aArgs)
+void CThreadCommand::DoCommandThreadStartL(TAny* aSelf)
 	{
-	if (aArgs.iFlags & CThreadCommand::ESharedHeap)
-		{
-		// If we're sharing the main fshell heap, we have to play by the rules and not crash
-		User::SetCritical(User::EProcessCritical);
-		}
-
-	CActiveScheduler* scheduler = new(ELeave) CActiveScheduler;
-	CleanupStack::PushL(scheduler);
-	CActiveScheduler::Install(scheduler);
-
-	HBufC* commandLine = aArgs.iCommandLine.AllocLC();
+	CThreadCommand* self = static_cast<CThreadCommand*>(aSelf);
 
 	IoUtils::CEnvironment* env;
-	if (aArgs.iFlags & CThreadCommand::EUpdateEnvironment)
+	if (self->iFlags & CThreadCommand::EUpdateEnvironment)
 		{
-		env = aArgs.iEnv.CreateSharedEnvironmentL();
+		env = self->iSuppliedEnv->CreateSharedEnvironmentL();
 		}
 	else
 		{
 		// A straight-forward copy
-		env = IoUtils::CEnvironment::NewL(aArgs.iEnv);
+		env = IoUtils::CEnvironment::NewL(*self->iSuppliedEnv);
 		}
 	CleanupStack::PushL(env);
 
-	CCommandBase* command = (*aArgs.iCommandConstructor)();
-	RThread parentThread;
-	User::LeaveIfError(parentThread.Open(aArgs.iParentThreadId));
-	parentThread.RequestComplete(aArgs.iParentStatus, KErrNone);
-	parentThread.Close();
-
-	command->RunCommandL(commandLine, env);
-	CleanupStack::PopAndDestroy(4, scheduler); // env, command, commandline, scheduler
+	CCommandBase* command = (*self->iCommandConstructor)();
+	//RDebug::Print(_L("5. DoCommandThreadStartL rendezvousing for %S %S"), &self->CmndName(), self->iCommandLine);
+	RThread::Rendezvous(KErrNone);
+	command->RunCommandL(self->iCommandLine, env);
+	CleanupStack::PopAndDestroy(2, env); // command, env
 	}
 
-TInt CommandThreadStart(TAny* aPtr)
-	{
-	CThreadCommand::TArgs args = *(CThreadCommand::TArgs*)aPtr;
-	TBool sharedHeap = (args.iFlags & CThreadCommand::ESharedHeap);
-	if (!sharedHeap)
-		{
-		__UHEAP_MARK;
-		}
-	TInt err = KErrNoMemory;
-	CTrapCleanup* cleanup = CTrapCleanup::New();
-	if (cleanup)
-		{
-		TRAP(err, CommandThreadStartL(args));
-		delete cleanup;
-		}
-	if (!sharedHeap)
-		{
-		__UHEAP_MARKEND;
-		}
-	return err;
-	}
 
 void SetHandleOwnersL(TThreadId aThreadId, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr)
 	{
@@ -199,64 +168,36 @@
 	{
 	ASSERT(iObserver == NULL);
 
-	TRequestStatus status(KRequestPending);
-	iArgs = new TArgs(iFlags, aEnv, iCommandConstructor, aCommandLine, status);
-	if (iArgs == NULL)
+	MThreadedTask* thread = NULL;
+	TRAPD(err, thread = iTaskRunner->NewTaskInSeparateThreadL(CmndName(), iFlags & ESharedHeap, &DoCommandThreadStartL, this));
+	if (err) return err;
+
+	TRAP(err, SetHandleOwnersL(thread->GetThreadId(), CmndStdin(), CmndStdout(), CmndStderr()));
+
+	if (!err)
 		{
-		return KErrNoMemory;
+		iCommandLine = aCommandLine.Alloc();
+		if (!iCommandLine) err = KErrNoMemory;
 		}
 
-	TInt i = 0;
-	TName threadName;
-	TInt err = KErrNone;
-	do
+	if (!err)
 		{
-		const TDesC& name = CmndName();
-		threadName.Format(_L("%S_%02d"), &name, i++);
-		if (iFlags & ESharedHeap)
-			{
-			err = iThread.Create(threadName, CommandThreadStart, KDefaultStackSize, NULL, iArgs);
-			}
-		else
-			{
-			err = iThread.Create(threadName, CommandThreadStart, KDefaultStackSize, KMinHeapSize, KMaxHeapSize, iArgs);
-			}
-		}
-		while (err == KErrAlreadyExists);
-
-	if (err)
-		{
-		return err;
+		err = iThread.Open(thread->GetThreadId());
 		}
 
-	err = iWatcher->Logon(*this, iThread, aObserver);
-	if (err)
+	if (!err)
 		{
-		iThread.Kill(0);
-		iThread.Close();
-		return err;
+		iSuppliedEnv = &aEnv;
+		iObserver = &aObserver;
+		thread->ExecuteTask(iStatus);
+		SetActive();
+		}
+	else
+		{
+		thread->AbortTask();
 		}
 
-	TThreadId threadId = iThread.Id();
-	TRAP(err, SetHandleOwnersL(threadId, CmndStdin(), CmndStdout(), CmndStderr()));
-	if (err)
-		{
-		iThread.Kill(0);
-		iThread.Close();
-		return err;
-		}
-
-	iThread.Resume();
-	User::WaitForRequest(status, iWatcher->iStatus);
-	if (status == KRequestPending)
-		{
-		iThread.Close();
-		return iWatcher->iStatus.Int();
-		}
-
-	iWatcher->SetActive();
-	iObserver = &aObserver;
-	return KErrNone;
+	return err;
 	}
 
 void CThreadCommand::CmndForeground()
@@ -300,71 +241,14 @@
 	return iThread.ExitCategory();
 	}
 
-
-//
-// CThreadCommand::TArgs.
-//
-
-CThreadCommand::TArgs::TArgs(TUint aFlags, IoUtils::CEnvironment& aEnv, TCommandConstructor aCommandConstructor, const TDesC& aCommandLine, TRequestStatus& aParentStatus)
-	: iFlags(aFlags), iEnv(aEnv), iCommandConstructor(aCommandConstructor), iCommandLine(aCommandLine), iParentStatus(&aParentStatus), iParentThreadId(RThread().Id())
+void CThreadCommand::RunL()
 	{
-	}
-
-
-//
-// CThreadCommand::CThreadWatcher.
-//
-
-CThreadCommand::CThreadWatcher* CThreadCommand::CThreadWatcher::NewL()
-	{
-	return new(ELeave) CThreadWatcher();
-	}
-
-CThreadCommand::CThreadWatcher::~CThreadWatcher()
-	{
-	Cancel();
-	}
-
-CThreadCommand::CThreadWatcher::CThreadWatcher()
-	: CActive(CActive::EPriorityStandard)
-	{
-	CActiveScheduler::Add(this);
+	iObserver->HandleCommandComplete(*this, iStatus.Int());
 	}
 
-TInt CThreadCommand::CThreadWatcher::Logon(CThreadCommand& aCommand, RThread& aThread, MCommandObserver& aObserver)
+void CThreadCommand::DoCancel()
 	{
-	TInt ret = KErrNone;
-	aThread.Logon(iStatus);
-	if (iStatus != KRequestPending)
-		{
-		User::WaitForRequest(iStatus);
-		ret = iStatus.Int();
-		}
-	else
-		{
-		iCommand = &aCommand;
-		iThread = &aThread;
-		iObserver = &aObserver;
-		}
-	return ret;
-	}
-
-void CThreadCommand::CThreadWatcher::SetActive()
-	{
-	CActive::SetActive();
-	}
-
-void CThreadCommand::CThreadWatcher::RunL()
-	{
-	iObserver->HandleCommandComplete(*iCommand, iStatus.Int());
-	}
-
-void CThreadCommand::CThreadWatcher::DoCancel()
-	{
-	if (iThread)
-		{
-		iThread->LogonCancel(iStatus);
-		}
+	CmndKill(); // This is a bit drastic, but effective...
 	}
 
 
--- a/core/src/command_wrappers.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/command_wrappers.h	Thu Oct 28 16:54:54 2010 +0100
@@ -55,8 +55,7 @@
 	virtual void HandleCommandComplete(MCommand& aCommand, TInt aError) = 0;
 	};
 
-
-class CCommandWrapperBase : public CBase, public MCommand
+class CCommandWrapperBase : public CActive, public MCommand
 	{
 protected:
 	CCommandWrapperBase();
@@ -74,6 +73,10 @@
 	virtual void CmndDisown();
 private: // From MCommand.
 	virtual void CmndRelease();
+protected: // From CActive
+	void RunL();
+	void DoCancel();
+
 private:
 	HBufC* iName;	///< This is used by concrete classes as they see fit.
 	RIoReadHandle iStdin;
@@ -91,11 +94,14 @@
 		ESharedHeap = 0x00000002, // Any command that accesses gShell must have this set
 		};
 public:
-	static CThreadCommand* NewL(const TDesC& aName, TCommandConstructor aCommandConstructor, TUint aFlags);
+	static CThreadCommand* NewL(const TDesC& aName, TCommandConstructor aCommandConstructor, TUint aFlags, MTaskRunner* aTaskRunner);
 	~CThreadCommand();
 private:
-	CThreadCommand(TCommandConstructor aCommandConstructor, TUint aFlags);
+	CThreadCommand(TCommandConstructor aCommandConstructor, TUint aFlags, MTaskRunner* aTaskRunner);
 	void ConstructL(const TDesC& aName);
+	void RunL();
+	void DoCancel();
+	static void DoCommandThreadStartL(TAny* aSelf);
 private: // From MCommand.
 	virtual TInt CmndRun(const TDesC& aCommandLine, IoUtils::CEnvironment& aEnv, MCommandObserver& aObserver, RIoSession& aIoSession);
 	virtual void CmndForeground();
@@ -105,43 +111,14 @@
 	virtual TInt CmndResume();
 	virtual TExitType CmndExitType() const;
 	virtual TExitCategoryName CmndExitCategory() const;
-public:
-	class TArgs
-		{
-	public:
-		TArgs(TUint aFlags, CEnvironment& aEnv, TCommandConstructor aCommandConstructor, const TDesC& aCommandLine, TRequestStatus& aParentStatus);
-	public:
-		TUint iFlags;
-		CEnvironment& iEnv;
-		TCommandConstructor iCommandConstructor;
-		const TPtrC iCommandLine;
-		TRequestStatus* iParentStatus;
-		TThreadId iParentThreadId;
-		};
-	class CThreadWatcher : public CActive
-		{
-	public:
-		static CThreadWatcher* NewL();
-		~CThreadWatcher();
-		TInt Logon(CThreadCommand& aCommand, RThread& aThread, MCommandObserver& aObserver);
-		void SetActive();
-	private:
-		CThreadWatcher();
-	private: // From CActive.
-		virtual void RunL();
-		virtual void DoCancel();
-	private:
-		CThreadCommand* iCommand;
-		RThread* iThread;
-		MCommandObserver* iObserver;
-		};
 private:
 	TUint iFlags;
 	TCommandConstructor iCommandConstructor;
 	MCommandObserver* iObserver;
-	TArgs* iArgs;
 	RThread iThread;
-	CThreadWatcher* iWatcher;
+	MTaskRunner* iTaskRunner;
+	CEnvironment* iSuppliedEnv;
+	HBufC* iCommandLine;
 	};
 
 
--- a/core/src/commands.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/commands.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -3492,8 +3492,11 @@
 CCmdSource::~CCmdSource()
 	{
 	delete iArgs;
-	delete iScriptData;
 	delete iParser;
+	if (iCloseScriptHandle)
+		{
+		iScriptHandle.Close();
+		}
 	}
 
 CCmdSource::CCmdSource() : CCommandBase(EManualComplete)
@@ -3502,11 +3505,28 @@
 
 void CCmdSource::DoRunL()
 	{
-	TIoHandleSet ioHandles(IoSession(), Stdin(), Stdout(), Stderr());
-	TBool helpPrinted;
-	iScriptData = CShell::ReadScriptL(iFileName, iArgs, Env(), FsL(), ioHandles, helpPrinted);
-
-	if (helpPrinted)
+	TBool abort(EFalse);
+
+	if (iFileName.Length() > 0)
+		{
+		TIoHandleSet ioHandles(IoSession(), Stdin(), Stdout(), Stderr());
+		iScriptHandle = CShell::OpenScriptL(iFileName, iArgs, Env(), FsL(), ioHandles, abort);
+		iCloseScriptHandle = ETrue;
+		}
+	else
+		{
+		iScriptHandle = Stdin();
+		Env().SetLocalL(KScriptName);
+		Env().SetLocalL(KScriptPath);
+		Env().SetLocalL(KScriptLine);
+		Env().SetLocalL(_L("0"));
+		_LIT(KStdin, "STDIN");
+		Env().SetL(KScriptName, KStdin);
+		Env().SetL(KScriptPath, KNullDesC);
+		Env().SetL(_L("0"), KStdin);
+		}
+
+	if (abort)
 		{
 		Complete();
 		}
@@ -3517,7 +3537,7 @@
 			{
 			mode |= CParser::EKeepGoing;
 			}
-		iParser = CParser::NewL(mode, *iScriptData, IoSession(), Stdin(), Stdout(), Stderr(), Env(), gShell->CommandFactory(), this);
+		iParser = CParser::NewL(mode, iScriptHandle, IoSession(), Stdin(), Stdout(), Stderr(), Env(), gShell->CommandFactory(), this);
 		iParser->Start();
 		}
 	}
@@ -4040,9 +4060,9 @@
 
 CCmdDebug::~CCmdDebug()
 	{
-	delete iScriptData;
 	delete iParser;
 	delete iArgs;
+	iScriptHandle.Close();
 	}
 
 CCmdDebug::CCmdDebug() : CCommandBase(EManualComplete)
@@ -4053,7 +4073,7 @@
 	{
 	TIoHandleSet ioHandles(IoSession(), Stdin(), Stdout(), Stderr());
 	TBool helpPrinted;
-	iScriptData = CShell::ReadScriptL(iFileName, iArgs, Env(), FsL(), ioHandles, helpPrinted);
+	iScriptHandle = CShell::OpenScriptL(iFileName, iArgs, Env(), FsL(), ioHandles, helpPrinted);
 	if (helpPrinted)
 		{
 		Complete();
@@ -4065,7 +4085,7 @@
 			{
 			mode |= CParser::EKeepGoing;
 			}
-		iParser = CParser::NewL(mode, *iScriptData, IoSession(), Stdin(), Stdout(), Stderr(), Env(), gShell->CommandFactory(), this);
+		iParser = CParser::NewL(mode, iScriptHandle, IoSession(), Stdin(), Stdout(), Stderr(), Env(), gShell->CommandFactory(), this);
 		iParser->Start();
 		}
 	}
@@ -5993,7 +6013,7 @@
 
 	if (iErrorText)
 		{
-		LeaveIfErr(iErrorVal, _L("%S"), iErrorText);
+		LeaveIfErr(iErrorVal, *iErrorText);
 		}
 	else
 		{
@@ -6080,8 +6100,7 @@
 	{
 	if (!aFirstTime) Env().RemoveAll(); // Remove all only does locals
 
-	delete iScriptData;
-	iScriptData = NULL;
+	iScriptHandle.Close();
 	delete iParser;
 	iParser = NULL;
 
@@ -6105,7 +6124,7 @@
 
 		TIoHandleSet ioHandles(IoSession(), Stdin(), Stdout(), Stderr());
 		TBool helpPrinted;
-		iScriptData = CShell::ReadScriptL(iFileName, iArgs, Env(), FsL(), ioHandles, helpPrinted, &extraArgs);
+		iScriptHandle = CShell::OpenScriptL(iFileName, iArgs, Env(), FsL(), ioHandles, helpPrinted, &extraArgs);
 		CleanupStack::PopAndDestroy(&extraArgs);
 		if (helpPrinted)
 			{
@@ -6118,7 +6137,7 @@
 				{
 				mode |= CParser::EKeepGoing;
 				}
-			iParser = CParser::NewL(mode, *iScriptData, IoSession(), Stdin(), Stdout(), Stderr(), Env(), gShell->CommandFactory(), this);
+			iParser = CParser::NewL(mode, iScriptHandle, IoSession(), Stdin(), Stdout(), Stderr(), Env(), gShell->CommandFactory(), this);
 			iParser->Start();
 			}
 		}
@@ -6139,6 +6158,11 @@
 		}
 	LeaveIfErr(iDir.Open(FsL(), iDirName, KEntryAttNormal | KEntryAttDir), _L("Couldn't open directory '%S'"), &iDirName);
 
+	if (iFileName.Length() == 0)
+		{
+		LeaveIfErr(KErrArgument, _L("Invalid script file name"));
+		}
+
 	DoNextL(ETrue);
 	}
 
--- a/core/src/commands.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/commands.h	Thu Oct 28 16:54:54 2010 +0100
@@ -765,7 +765,8 @@
 	TFileName2 iFileName;
 	HBufC* iArgs;
 	TBool iKeepGoing;
-	HBufC* iScriptData;
+	TBool iCloseScriptHandle;
+	RIoReadHandle iScriptHandle;
 	CParser* iParser;
 	};
 
@@ -886,7 +887,7 @@
 	TFileName2 iFileName;
 	HBufC* iArgs;
 	TBool iKeepGoing;
-	HBufC* iScriptData;
+	RIoReadHandle iScriptHandle;
 	CParser* iParser;
 	};
 
--- a/core/src/error.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/error.h	Thu Oct 28 16:54:54 2010 +0100
@@ -32,9 +32,8 @@
 		EFailedToCreatePipeLine,
 		EFailedToConstructCommand,
 		EFailedToRunCommand,
-		EFailedToSetChildErrorVar,
+		EPipelineCompletionError,
 		ECommandError,
-		EFailedToSetScriptLineVar,
 		};
 public:
 	TError(RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv);
--- a/core/src/fshell.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/fshell.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -117,8 +117,7 @@
 			break;
 			}
 		case EFailedToCreatePipeLine:
-		case EFailedToSetChildErrorVar:
-		case EFailedToSetScriptLineVar:
+		case EPipelineCompletionError:
 		case EUnknown:
 		default:
 			{
@@ -200,8 +199,7 @@
 		CASE_RETURN_LIT(EFailedToCreatePipeLine);
 		CASE_RETURN_LIT(EFailedToConstructCommand);
 		CASE_RETURN_LIT(EFailedToRunCommand);
-		CASE_RETURN_LIT(EFailedToSetChildErrorVar);
-		CASE_RETURN_LIT(EFailedToSetScriptLineVar);
+		CASE_RETURN_LIT(EPipelineCompletionError);
 		CASE_RETURN_LIT(ECommandError);
 		DEFAULT_RETURN_LIT("*** REASON UNKNOWN ***");
 		}
@@ -296,6 +294,7 @@
 
 CShell::~CShell()
 	{
+	iScriptHandle.Close();
 	iJobsLock.Close();
 	iJobs.ResetAndDestroy();
 	delete iScriptArgs;
@@ -303,7 +302,6 @@
 	delete iLineCompleter;
 	delete iConsole;
 	delete iCommandFactory;
-	delete iScriptData;
 	delete iOneLiner;
 	delete iParser;
 	}
@@ -515,7 +513,7 @@
 		{
 		TIoHandleSet ioHandles(IoSession(), Stdin(), Stdout(), Stderr());
 		TBool helpPrinted;
-		iScriptData = ReadScriptL(iScriptName, iScriptArgs, Env(), FsL(), ioHandles, helpPrinted);
+		iScriptHandle = OpenScriptL(iScriptName, iScriptArgs, Env(), FsL(), ioHandles, helpPrinted);
 		if (helpPrinted)
 			{
 			Complete();
@@ -527,7 +525,7 @@
 				{
 				mode |= CParser::EKeepGoing;
 				}
-			iParser = CParser::NewL(mode, *iScriptData, IoSession(), Stdin(), Stdout(), Stderr(), Env(), *iCommandFactory, this);
+			iParser = CParser::NewL(mode, iScriptHandle, IoSession(), Stdin(), Stdout(), Stderr(), Env(), *iCommandFactory, this);
 			RProcess::Rendezvous(KErrNone);
 			iParser->Start();
 			}
@@ -641,8 +639,12 @@
 	return KErrNotFound;
 	}
 
-HBufC* CShell::ReadScriptL(const TDesC& aScriptName, const TDesC* aArguments, IoUtils::CEnvironment& aEnv, RFs& aFs, TIoHandleSet& aIoHandles, TBool& aHelpPrinted, RPointerArray<HBufC>* aAdditionalPrefixArguments)
+RIoReadHandle CShell::OpenScriptL(const TDesC& aScriptName, const TDesC* aArguments, IoUtils::CEnvironment& aEnv, RFs& aFs, TIoHandleSet& aIoHandles, TBool& aHelpPrinted, RPointerArray<HBufC>* aAdditionalPrefixArguments)
 	{
+	RIoReadHandle readHandle;
+	readHandle.CreateL(aIoHandles.IoSession());
+	CleanupClosePushL(readHandle);
+
 	TFileName2 scriptName(aScriptName);
 
 	// Check the scripts dirs in case it wasn't given as an absolute path (although iocli will have made it absolute relative to the pwd)
@@ -670,12 +672,12 @@
 			} 
 		}
 
-	RFile scriptFile;
+	RIoFile scriptFile;
 	TInt err;
 	TInt retries = 5;
 	do
 		{
-		err = scriptFile.Open(aFs, scriptName, EFileRead | EFileShareReadersOnly);
+		err = scriptFile.Create(aIoHandles.IoSession(), scriptName, RIoFile::ERead);
 		if ((err == KErrNone) || (err != KErrInUse))
 			{
 			break;
@@ -686,14 +688,9 @@
 		while (retries >= 0);
 	StaticLeaveIfErr(err, _L("Couldn't open script file %S"), &scriptName);
 	CleanupClosePushL(scriptFile);
-	TInt scriptFileSize;
-	User::LeaveIfError(scriptFile.Size(scriptFileSize));
-	HBufC8* scriptData = HBufC8::NewLC(scriptFileSize);
-	TPtr8 scriptDataPtr(scriptData->Des());
-	User::LeaveIfError(scriptFile.Read(scriptDataPtr));
-	HBufC* decodedScriptData = LtkUtils::DecodeUtf8L(*scriptData);
-	CleanupStack::PopAndDestroy(2, &scriptFile);
-	CleanupStack::PushL(decodedScriptData);
+	scriptFile.AttachL(readHandle, RIoEndPoint::EForeground);
+	CleanupStack::PopAndDestroy(&scriptFile);
+
 	aEnv.SetLocalL(KScriptName);
 	aEnv.SetLocalL(KScriptPath);
 	aEnv.SetLocalL(KScriptLine);
@@ -701,6 +698,7 @@
 	aEnv.SetL(KScriptName, scriptName.NameAndExt());
 	aEnv.SetL(KScriptPath, scriptName.DriveAndPath());
 	aEnv.SetL(_L("0"), scriptName);
+
 	CScriptCommand* scriptCommand = CScriptCommand::NewLC(scriptName, aIoHandles);
 	TRAP(err, scriptCommand->ParseCommandLineArgsL(aArguments ? *aArguments : KNullDesC(), aEnv, aAdditionalPrefixArguments));
 	if (err == KErrArgument || scriptCommand->ShouldDisplayHelp())
@@ -723,8 +721,8 @@
 		}
 	User::LeaveIfError(err); // Propagate error
 	CleanupStack::PopAndDestroy(scriptCommand);
-	CleanupStack::Pop(decodedScriptData);
-	return decodedScriptData;
+	CleanupStack::Pop(&readHandle);
+	return readHandle;
 	}
 
 void CShell::SetToForeground()
--- a/core/src/fshell.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/fshell.h	Thu Oct 28 16:54:54 2010 +0100
@@ -46,7 +46,7 @@
 	const RPointerArray<CJob>& Jobs() const;
 	CJob* Job(TInt aId);
 	TInt DisownJob(TInt aId);
-	static HBufC* ReadScriptL(const TDesC& aScriptName, const TDesC* aArguments, IoUtils::CEnvironment& aEnv, RFs& aFs, TIoHandleSet& aIoHandles, TBool& aHelpPrinted, RPointerArray<HBufC>* aAdditionalPrefixArguments=NULL);
+	static RIoReadHandle OpenScriptL(const TDesC& aScriptName, const TDesC* aArguments, IoUtils::CEnvironment& aEnv, RFs& aFs, TIoHandleSet& aIoHandles, TBool& aHelpPrinted, RPointerArray<HBufC>* aAdditionalPrefixArguments=NULL);
 private:
 	CShell();
 	void ConstructL();
@@ -79,7 +79,7 @@
 	CLineCompleter* iLineCompleter;
 	CCommandFactory* iCommandFactory;
 	RPointerArray<CJob> iJobs;
-	HBufC* iScriptData;
+	RIoReadHandle iScriptHandle;
 	CParser* iParser;
 	TInt iForegroundJobId;
 	TInt iNextJobId;
--- a/core/src/fshell.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/fshell.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -65,6 +65,7 @@
 source          lexer.cpp
 source          file_reader.cpp
 source          script_command.cpp
+source          worker_thread.cpp
 
 sourcepath      ..\builtins
 source          hello.cpp
--- a/core/src/lexer.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/lexer.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -12,6 +12,8 @@
 
 #include "lexer.h"
 
+const TInt KMaxReadLength = 512;
+
 
 //
 // TToken
@@ -52,6 +54,231 @@
 
 
 //
+// CLex - A cut down version of the TLex API that supports reading data incrementally from an RIoReadHandle.
+//
+
+class CLex : public CBase
+	{
+public:
+	static CLex* NewL();
+	~CLex();
+	void Set(const TDesC& aDes);
+	void Set(RIoReadHandle& aHandle);
+	void Purge();
+	void SkipToEnd();
+	TBool EosL();
+	void Mark();
+	void IncL(TInt aNumber);
+	TChar GetL();
+	TChar Peek() const;
+	TPtrC RemainderL(TInt aMinLength);
+	void UnGet();
+	TInt Offset() const;
+	TInt MarkedOffset() const;
+	TPtrC MarkedToken() const;
+	const TUint16* Ptr() const;
+private:
+	CLex();
+	void DoReadL();
+private:
+	TLex iLex;
+	TInt iMarkedOffset;
+	TPtrC iLexPtr;
+	RBuf iLexBuf;
+	TBuf<KMaxReadLength> iReadBuf;
+	RIoReadHandle iHandle;
+	TBool iUsingHandle;
+	TBool iEos;
+	};
+
+CLex* CLex::NewL()
+	{
+	return new(ELeave) CLex();
+	}
+
+CLex::CLex() : iLexPtr(NULL, 0)
+	{
+	}
+
+CLex::~CLex()
+	{
+	iLexBuf.Close();
+	}
+
+void CLex::Set(const TDesC& aDes)
+	{
+	iEos = EFalse;
+	iUsingHandle = EFalse;
+	iMarkedOffset = 0;
+	iLexPtr.Set(aDes);
+	iLex = iLexPtr;
+	}
+
+void CLex::Set(RIoReadHandle& aHandle)
+	{
+	iEos = EFalse;
+	iUsingHandle = ETrue;
+	iMarkedOffset = 0;
+	iHandle = aHandle;
+	iHandle.SetReadMode(RIoReadHandle::EOneOrMore);
+	iLexBuf.Zero();
+	iLex = iLexBuf;
+	}
+
+void CLex::Purge()
+	{
+	if (iUsingHandle)
+		{
+		iLexBuf.Delete(0, iLex.Offset());
+		iLex = iLexBuf;
+		}
+
+	iMarkedOffset = 0;
+	}
+
+void CLex::SkipToEnd()
+	{
+	iEos = ETrue;
+	}
+
+TBool CLex::EosL()
+	{
+	if (!iEos)
+		{
+		if (!iLex.Eos())
+			{
+			// If iLex still has data, then we're definately not at the end of the string.
+			// Do nothing. This test avoids us doing I/O handle reads before draining using the existing data.
+			}
+		else if (iUsingHandle)
+			{
+			DoReadL();
+			}
+		else
+			{
+			iEos = ETrue;
+			}
+		}
+	return iEos;
+	}
+
+void CLex::Mark()
+	{
+	iMarkedOffset = iLex.Offset();
+	}
+
+void CLex::IncL(TInt aNumber)
+	{
+	if (iUsingHandle)
+		{
+		while (!iEos && (iLex.Remainder().Length() < aNumber))
+			{
+			DoReadL();
+			}
+		}
+
+	iLex.Inc(aNumber);
+	}
+
+TChar CLex::GetL()
+	{
+	if (iUsingHandle && !iEos && (iLex.Remainder().Length() < 1))
+		{
+		DoReadL();
+		}
+	return iLex.Get();
+	}
+
+TChar CLex::Peek() const
+	{
+	return iLex.Peek();
+	}
+
+TPtrC CLex::RemainderL(TInt aMinLength)
+	{
+	if (iUsingHandle)
+		{
+		while (!iEos && (iLex.Remainder().Length() < aMinLength))
+			{
+			DoReadL();
+			}
+		}
+	return iLex.Remainder();
+	}
+
+void CLex::UnGet()
+	{
+	iLex.UnGet();
+	}
+
+TInt CLex::Offset() const
+	{
+	return iLex.Offset();
+	}
+
+TInt CLex::MarkedOffset() const
+	{
+	return iMarkedOffset;
+	}
+
+TPtrC CLex::MarkedToken() const
+	{
+	if (iUsingHandle)
+		{
+		return TPtrC(iReadBuf.Ptr() + iMarkedOffset, iLex.Offset() - iMarkedOffset);
+		}
+	else
+		{
+		return TPtrC(iLexPtr.Ptr() + iMarkedOffset, iLex.Offset() - iMarkedOffset);
+		}
+	}
+
+const TUint16* CLex::Ptr() const
+	{
+	if (iUsingHandle)
+		{
+		return iLexBuf.Ptr();
+		}
+	else
+		{
+		return iLexPtr.Ptr();
+		}
+
+	}
+
+void CLex::DoReadL()
+	{
+	ASSERT(iUsingHandle);
+
+	if (!iEos)
+		{
+		if (iReadBuf.Length() == 0) // iReadBuf may contain data if a realloc of iLexBuf failed previously.
+			{
+			TInt err = iHandle.Read(iReadBuf);
+			if (err == KErrEof)
+				{
+				iEos = ETrue;
+				}
+			else
+				{
+				User::LeaveIfError(err);
+				}
+			}
+
+		TInt offset = iLex.Offset();
+		if ((iLexBuf.MaxLength() - iLexBuf.Length()) < iReadBuf.Length())
+			{
+			iLexBuf.ReAllocL(iLexBuf.Length() + iReadBuf.Length());
+			}
+		iLexBuf.Append(iReadBuf);
+		iReadBuf.Zero();
+		iLex = iLexBuf;
+		iLex.Inc(offset);
+		}
+	}
+
+
+//
 // CReservedLookup
 //
 
@@ -84,6 +311,7 @@
 	void DefineTokenTypeL(TToken::TType aTokenType, const TDesC& aString);
 	void Reset();
 	TResult Lookup(const TDesC& aDes);
+	TInt Longest() const;
 private:
 	class TReserved
 		{
@@ -95,6 +323,7 @@
 		};
 private:
 	RArray<TReserved> iList;
+	TInt iLongest;
 	};
 
 CReservedLookup* CReservedLookup::NewL()
@@ -110,6 +339,10 @@
 void CReservedLookup::DefineTokenTypeL(TToken::TType aTokenType, const TDesC& aString)
 	{
 	User::LeaveIfError(iList.Append(TReserved(aTokenType, aString)));
+	if (aString.Length() > iLongest)
+		{
+		iLongest = aString.Length();
+		}
 	}
 
 CReservedLookup::TResult CReservedLookup::Lookup(const TDesC& aDes)
@@ -133,6 +366,11 @@
 	return result;
 	}
 
+TInt CReservedLookup::Longest() const
+	{
+	return iLongest;
+	}
+
 CReservedLookup::TReserved::TReserved(TToken::TType aType, const TDesC& aString)
 	: iType(aType), iString(aString)
 	{
@@ -180,6 +418,7 @@
 
 CLexer::~CLexer()
 	{
+	delete iLex;
 	delete iReservedLookup;
 	}
 
@@ -190,91 +429,112 @@
 
 void CLexer::Set(const TDesC& aDes, const TChar& aEscapeChar)
 	{
-	iLex = aDes;
+	iLex->Set(aDes);
+	iEscapeChar = aEscapeChar;
+	}
+
+void CLexer::Set(RIoReadHandle& aHandle, const TChar& aEscapeChar)
+	{
+	iLex->Set(aHandle);
 	iEscapeChar = aEscapeChar;
 	}
 
-TToken CLexer::NextToken()
+void CLexer::Purge()
+	{
+	iLex->Purge();
+	}
+
+void CLexer::SkipToEnd()
 	{
-	SkipWhiteSpace();
-	iLex.Mark();
+	iLex->SkipToEnd();
+	}
+
+TToken CLexer::NextTokenL()
+	{
+	SkipWhiteSpaceL();
+	iLex->Mark();
 	TToken::TType type(TToken::ENull);
 
-	while (!iLex.Eos())
+	while (!iLex->EosL())
 		{
 		type = TToken::EString;
-		TChar c = iLex.Get();
+		TChar c = iLex->GetL();
 		if (c == iEscapeChar)
 			{
-			iLex.Get();
+			iLex->GetL();
 			}
 		else if ((c == '\'') && (iBehaviour & EHandleSingleQuotes))
 			{
-			SkipSingleQuotedChars();
+			SkipSingleQuotedCharsL();
 			}
 		else if ((c == '\"') && (iBehaviour & EHandleDoubleQuotes))
 			{
-			SkipDoubleQuotedChars();
+			SkipDoubleQuotedCharsL();
 			}
 		else if ((c == '#') && (iBehaviour & EHandleComments))
 			{
-			if (iLex.MarkedToken().Length() > 1)
+			if (iLex->MarkedToken().Length() > 1)
 				{
-				iLex.UnGet();
+				iLex->UnGet();
 				break;
 				}
 			else
 				{
-				SkipComment();
-				if (iLex.Eos())
+				SkipCommentL();
+				if (iLex->EosL())
 					{
 					type = TToken::ENull;
 					break;
 					}
 				else
 					{
-					iLex.Mark();
+					iLex->Mark();
 					}
 				}
 			}
 		else if (c.IsSpace() && (c != '\n') && (c != '\r'))
 			{
-			iLex.UnGet();
+			iLex->UnGet();
 			break;
 			}
 		else
 			{
-			iLex.UnGet();
-			CReservedLookup::TResult result = iReservedLookup->Lookup(iLex.Remainder());
+			iLex->UnGet();
+			CReservedLookup::TResult result = iReservedLookup->Lookup(iLex->RemainderL(iReservedLookup->Longest()));
 			if (result.iResultType == CReservedLookup::TResult::EMatch)
 				{
-				if (iLex.MarkedToken().Length() > 0)
+				if (iLex->MarkedToken().Length() > 0)
 					{
 					break;
 					}
 				else
 					{
-					iLex.Inc(result.iTokenLength);
+					iLex->IncL(result.iTokenLength);
 					type = result.iTokenType;
 					break;
 					}
 				}
-			iLex.Get();
+			iLex->GetL();
 			}
 		}
 
-	return TToken(type, iLex.MarkedToken(), iLex.MarkedOffset());
+	return TToken(type, iLex->MarkedToken(), iLex->MarkedOffset());
 	}
 
 TInt CLexer::CurrentOffset() const
 	{
-	return iLex.Offset();
+	return iLex->Offset();
 	}
 
-TBool CLexer::More()
+TBool CLexer::MoreL()
 	{
-	SkipWhiteSpace();
-	return !iLex.Eos();
+	SkipWhiteSpaceL();
+	return !iLex->EosL();
+	}
+
+const TUint16* CLexer::Ptr() const
+	{
+	return iLex->Ptr();
 	}
 
 CLexer::CLexer(TUint aBehaviour)
@@ -284,18 +544,19 @@
 
 void CLexer::ConstructL()
 	{
+	iLex = CLex::NewL();
 	iReservedLookup = CReservedLookup::NewL();
 	}
 
-void CLexer::SkipSingleQuotedChars()
+void CLexer::SkipSingleQuotedCharsL()
 	{
-	while (!iLex.Eos())
+	while (!iLex->EosL())
 		{
-		TChar c = iLex.Get();
-		if ((c == iEscapeChar) && !iLex.Eos() && (iLex.Peek() == '\''))
+		TChar c = iLex->GetL();
+		if ((c == iEscapeChar) && !iLex->EosL() && (iLex->Peek() == '\''))
 			{
 			// Allow quoted single quote characters. Note, the is a departure from Bash behaviour, but is in line with Perl and is generally helpful.
-			iLex.Get();
+			iLex->GetL();
 			}
 		else if (c == '\'')
 			{
@@ -304,14 +565,14 @@
 		}
 	}
 
-void CLexer::SkipDoubleQuotedChars()
+void CLexer::SkipDoubleQuotedCharsL()
 	{
-	while (!iLex.Eos())
+	while (!iLex->EosL())
 		{
-		TChar c = iLex.Get();
-		if ((c == iEscapeChar) && !iLex.Eos())
+		TChar c = iLex->GetL();
+		if ((c == iEscapeChar) && !iLex->EosL())
 			{
-			iLex.Get();
+			iLex->GetL();
 			}
 		else if (c == '"')
 			{
@@ -320,27 +581,27 @@
 		}
 	}
 
-void CLexer::SkipComment()
+void CLexer::SkipCommentL()
 	{
-	while (!iLex.Eos())
+	while (!iLex->EosL())
 		{
-		TChar c = iLex.Get();
+		TChar c = iLex->GetL();
 		if ((c == '\n') || (c == '\r'))
 			{
-			iLex.UnGet();
+			iLex->UnGet();
 			break;
 			}
 		}
 	}
 
-void CLexer::SkipWhiteSpace()
+void CLexer::SkipWhiteSpaceL()
 	{
-	while (!iLex.Eos())
+	while (!iLex->EosL())
 		{
-		TChar c = iLex.Get();
+		TChar c = iLex->GetL();
 		if (!c.IsSpace() || (c == '\n') || (c == '\r'))
 			{
-			iLex.UnGet();
+			iLex->UnGet();
 			break;
 			}
 		}
--- a/core/src/lexer.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/lexer.h	Thu Oct 28 16:54:54 2010 +0100
@@ -14,7 +14,9 @@
 #define __LEXER_H__
 
 #include <e32base.h>
+#include <fshell/iocli.h>
 
+class CLex;
 class CReservedLookup;
 
 
@@ -72,20 +74,24 @@
 	~CLexer();
 	void DefineTokenTypeL(TToken::TType aTokenType, const TDesC& aString);
 	void Set(const TDesC& aDes, const TChar& aEscapeChar);
-	TToken NextToken();
+	void Set(RIoReadHandle& aHandle, const TChar& aEscapeChar);
+	void Purge();
+	void SkipToEnd();
+	TToken NextTokenL();
 	TInt CurrentOffset() const;
-	TBool More();
+	TBool MoreL();
+	const TUint16* Ptr() const;
 private:
 	CLexer(TUint aBehaviour);
 	void ConstructL();
-	void SkipSingleQuotedChars();
-	void SkipDoubleQuotedChars();
-	void SkipComment();
-	void SkipWhiteSpace();
+	void SkipSingleQuotedCharsL();
+	void SkipDoubleQuotedCharsL();
+	void SkipCommentL();
+	void SkipWhiteSpaceL();
 private:
 	TUint iBehaviour;
 	TChar iEscapeChar;
-	TLex iLex;
+	CLex* iLex;
 	CReservedLookup* iReservedLookup;
 	};
 
--- a/core/src/line_completer.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/line_completer.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -49,15 +49,15 @@
 void CLineCompleter::LcCompleteLineL(TConsoleLine& aLine, const TChar& aEscapeChar)
 	{
 	iLexer->Set(aLine.ContentsToCursor(), aEscapeChar);
-	TToken token(iLexer->NextToken());
+	TToken token(iLexer->NextTokenL());
 	TToken firstToken = token;
 	TToken prevToken(TToken::EString, KNullDesC, aLine.ContentsToCursor().Length());
 	TInt lastTokenIdx = 0;
-	while (iLexer->More())
+	while (iLexer->MoreL())
 		{
 		// More than one token - skip to last.
 		prevToken = token;
-		token = iLexer->NextToken();
+		token = iLexer->NextTokenL();
 		lastTokenIdx++;
 		}
 
--- a/core/src/parser.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/parser.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -54,9 +54,18 @@
 
 CParser* CParser::NewL(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber)
 	{
-	CParser* self = new(ELeave) CParser(aMode, aDes, aIoSession, aStdin, aStdout, aStderr, aEnv, aFactory, aObserver, aStartingLineNumber);
+	CParser* self = new(ELeave) CParser(aMode, aIoSession, aStdin, aStdout, aStderr, aEnv, aFactory, aObserver, aStartingLineNumber);
 	CleanupStack::PushL(self);
-	self->ConstructL();
+	self->ConstructL(&aDes, NULL);
+	CleanupStack::Pop();
+	return self;
+	}
+
+CParser* CParser::NewL(TUint aMode, RIoReadHandle& aSourceHandle, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver)
+	{
+	CParser* self = new(ELeave) CParser(aMode, aIoSession, aStdin, aStdout, aStderr, aEnv, aFactory, aObserver);
+	CleanupStack::PushL(self);
+	self->ConstructL(NULL, &aSourceHandle);
 	CleanupStack::Pop();
 	return self;
 	}
@@ -78,12 +87,12 @@
 		}
 	}
 
-CParser::CParser(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber)
-	: iMode(aMode), iData(aDes), iIoSession(aIoSession), iStdin(aStdin), iStdout(aStdout), iStderr(aStderr), iEnv(aEnv), iFactory(aFactory), iObserver(aObserver), iCompletionError(aStderr, aEnv), iNextLineNumber(aStartingLineNumber)
+CParser::CParser(TUint aMode, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber)
+	: iMode(aMode), iIoSession(aIoSession), iStdin(aStdin), iStdout(aStdout), iStderr(aStderr), iEnv(aEnv), iFactory(aFactory), iObserver(aObserver), iCompletionError(aStderr, aEnv), iNextLineNumber(aStartingLineNumber)
 	{
 	}
 
-void CParser::ConstructL()
+void CParser::ConstructL(const TDesC* aDes, RIoReadHandle* aSourceHandle)
 	{
 	if (iObserver)
 		{
@@ -100,7 +109,14 @@
 	iLexer1->DefineTokenTypeL(TToken::ENewLine, KNewLine3);
 	iLexer1->DefineTokenTypeL(TToken::ENewLine, KNewLine4);
 	iLexer1->DefineTokenTypeL(TToken::ESemicolon, KSemicolon);
-	iLexer1->Set(iData, iEnv.EscapeChar());
+	if (aDes)
+		{
+		iLexer1->Set(*aDes, iEnv.EscapeChar());
+		}
+	else
+		{
+		iLexer1->Set(*aSourceHandle, iEnv.EscapeChar());
+		}
 
 	iLexer2 = CLexer::NewL(CLexer::EHandleSingleQuotes | CLexer::EHandleDoubleQuotes);
 	iLexer2->DefineTokenTypeL(TToken::EPipe, KPipe);
@@ -350,10 +366,10 @@
 		}
 	else
 		{
-		if (aLexer.More())
+		if (aLexer.MoreL())
 			{
 			redirection->iType = ((aTokenType == TToken::ERedirectStdoutToFileAppend) || (aTokenType == TToken::ERedirectStderrToFileAppend)) ? RPipeSection::TRedirection::EFileAppend : RPipeSection::TRedirection::EFile;
-			TToken fileName(aLexer.NextToken());
+			TToken fileName(aLexer.NextTokenL());
 			redirection->SetFileNameL(aCwd, fileName.String());
 			}
 		else
@@ -412,14 +428,14 @@
 	CleanupClosePushL(pipeSection);
 	TInt offset = iLexer2->CurrentOffset();
 	TBool background(EFalse);
-	while (iLexer2->More())
+	while (iLexer2->MoreL())
 		{
-		TToken token(iLexer2->NextToken());
+		TToken token(iLexer2->NextTokenL());
 		switch (token.Type())
 			{
 			case TToken::EPipe:
 				{
-				pipeSection.iFullName.Set(iData.Ptr() + offset, iLexer2->CurrentOffset() - offset - token.String().Length());
+				pipeSection.iFullName.Set(iLexer2->Ptr() + offset, iLexer2->CurrentOffset() - offset - token.String().Length());
 				offset = iLexer2->CurrentOffset();
 				User::LeaveIfError(pipeSections.Append(pipeSection));
 				new(&pipeSection) RPipeSection;
@@ -485,7 +501,7 @@
 			}
 		else
 			{
-			pipeSection.iFullName.Set(iData.Ptr() + offset, iLexer2->CurrentOffset() - offset);
+			pipeSection.iFullName.Set(iLexer2->Ptr() + offset, iLexer2->CurrentOffset() - offset);
 			User::LeaveIfError(pipeSections.Append(pipeSection));
 			CleanupStack::Pop(&pipeSection);
 			if ((iMode & EDebug) && iObserver)
@@ -504,11 +520,11 @@
 				iForegroundPipeLine = CPipeLine::NewL(iIoSession, iStdin, iStdout, iStderr, iEnv, iFactory, pipeSections, background, this, iCompletionError);
 				}
 			CleanupStack::PopAndDestroy(&pipeSections);
-			if (aIsForeground && !iLexer1->More())
+			if (aIsForeground && !iLexer1->MoreL())
 				{
 				*aIsForeground = !background;
 				}
-			if (background && iLexer1->More())
+			if (background && iLexer1->MoreL())
 				{
 				iNextPipeLineCallBack->Call();
 				}
@@ -535,14 +551,16 @@
 	{
 	aReachedLineEnd = EFalse;
 	aCondition = ENone;
+
+	iLexer1->Purge();
 	TInt startOffset = iLexer1->CurrentOffset();
 	TInt endOffset = -1;
 
 	TBool foundSomething(EFalse);
-	while (iLexer1->More())
+	while (iLexer1->MoreL())
 		{
 		TBool finished(EFalse);
-		TToken token(iLexer1->NextToken());
+		TToken token(iLexer1->NextTokenL());
 
 		switch (token.Type())
 			{
@@ -616,7 +634,7 @@
 
 	if (foundSomething)
 		{
-		aData.Set(iData.Ptr() + startOffset, endOffset - startOffset);
+		aData.Set(iLexer1->Ptr() + startOffset, endOffset - startOffset);
 		}
 	else
 		{
@@ -636,7 +654,7 @@
 	aLexer.Set(*buf, escapeChar);
 	FOREVER
 		{
-		TToken token(aLexer.NextToken());
+		TToken token(aLexer.NextTokenL());
 		if (token.Type() == TToken::ENull)
 			{
 			break;
@@ -715,7 +733,7 @@
 	lexer1->Set(*buf, iEnv.EscapeChar());
 	FOREVER
 		{
-		TToken token(lexer1->NextToken());
+		TToken token(lexer1->NextTokenL());
 		if (token.Type() == TToken::ENull)
 			{
 			break;
@@ -756,35 +774,35 @@
 	return buf;
 	}
 
-TInt CParser::SkipLineRemainder()
+void CParser::SkipLineRemainderL()
 	{
-	while (iLexer1->More())
+	TRAPD(err, DoSkipLineRemainderL());
+	if (err)
 		{
-		TToken token(iLexer1->NextToken());
+		SkipToEnd();
+		User::Leave(err);
+		}
+	}
+
+void CParser::DoSkipLineRemainderL()
+	{
+	while (iLexer1->MoreL())
+		{
+		TToken token(iLexer1->NextTokenL());
 		if (token.Type() == TToken::ENewLine)
 			{
 			if (iMode & EExportLineNumbers)
 				{
-				// can we do something better with errors here?
-				TRAPD(err, iEnv.SetL(KScriptLine, iNextLineNumber++));
-				if (err!=KErrNone)
-					{
-					iCompletionError.Set(err, TError::EFailedToSetScriptLineVar);
-					return err;
-					}
+				iEnv.SetL(KScriptLine, iNextLineNumber++);
 				}
 			break;
 			}
 		}
-	return KErrNone;
 	}
 
 void CParser::SkipToEnd()
 	{
-	while (iLexer1->More())
-		{
-		iLexer1->NextToken();
-		}
+	iLexer1->SkipToEnd();
 	}
 
 TInt CParser::CompletionCallBack(TAny* aSelf)
@@ -809,13 +827,18 @@
 
 void CParser::HandlePipeLineComplete(CPipeLine& aPipeLine, const TError& aError)
 	{
-	TRAPD(err, iEnv.SetL(KChildError, aError.Error()));
+	TRAPD(err, HandlePipeLineCompleteL(aPipeLine, aError));
 	if (err)
 		{
-		iCompletionError.Set(err, TError::EFailedToSetChildErrorVar);
+		iCompletionError.Set(err, TError::EPipelineCompletionError);
 		iCompletionCallBack->CallBack();
 		return;
 		}
+	}
+
+void CParser::HandlePipeLineCompleteL(CPipeLine& aPipeLine, const TError& aError)
+	{
+	iEnv.SetL(KChildError, aError.Error());
 
 	if ((iMode & EDebug) && iObserver)
 		{
@@ -844,8 +867,7 @@
 					}
 				else if (aError.Error() != KErrNone)
 					{
-					TInt err = SkipLineRemainder();
-					if (err!=KErrNone) SkipToEnd();
+					SkipLineRemainderL();
 					}
 				break;
 				}
@@ -857,8 +879,7 @@
 					}
 				else if (aError.Error() == KErrNone)
 					{
-					TInt err = SkipLineRemainder();
-					if (err!=KErrNone) SkipToEnd();
+					SkipLineRemainderL();
 					}
 				break;
 				}
@@ -879,7 +900,7 @@
 		delete iForegroundPipeLine;
 		iForegroundPipeLine = NULL;
 
-		if (iLexer1->More())
+		if (iLexer1->MoreL())
 			{
 			iNextPipeLineCallBack->Call();
 			}
@@ -896,7 +917,7 @@
 		delete &aPipeLine;
 		}
 
-	if (iObserver && !iLexer1->More() && (iForegroundPipeLine == NULL) && (iBackgroundPipeLines.Count() == 0))
+	if (iObserver && !iLexer1->MoreL() && (iForegroundPipeLine == NULL) && (iBackgroundPipeLines.Count() == 0))
 		{
 		iCompletionCallBack->CallBack();
 		}
--- a/core/src/parser.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/parser.h	Thu Oct 28 16:54:54 2010 +0100
@@ -42,6 +42,7 @@
 		};
 public:
 	static CParser* NewL(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber = 1);
+	static CParser* NewL(TUint aMode, RIoReadHandle& aSourceHandle, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver);
 	~CParser();
 	void Start();
 	void Start(TBool& aIsForeground);
@@ -62,14 +63,17 @@
 		EAndOr
 		};
 private:
-	CParser(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber=1);
-	void ConstructL();
+	CParser(TUint aMode, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber=1);
+	void ConstructL(const TDesC* aDes, RIoReadHandle* aSourceHandle);
 	void CreateNextPipeLine(TBool* aIsForeground);
 	void CreateNextPipeLineL(TBool* aIsForeground);
 	void FindNextPipeLineL(TPtrC& aData, TCondition& aCondition, TBool& aReachedLineEnd);
 	HBufC* ExpandVariablesLC(const TDesC& aData);
-	TInt SkipLineRemainder();
+	void SkipLineRemainderL();
+	void DoSkipLineRemainderL();
 	void SkipToEnd();
+	TBool MoreSourceData() const;
+	void HandlePipeLineCompleteL(CPipeLine& aPipeLine, const TError& aError);
 	static TInt CompletionCallBack(TAny* aSelf);
 	static TInt NextCallBack(TAny* aSelf);
 	static TInt ExitCallBack(TAny* aSelf);
@@ -77,7 +81,6 @@
 	virtual void HandlePipeLineComplete(CPipeLine& aPipeLine, const TError& aError);
 private:
 	const TUint iMode;
-	const TPtrC iData;
 	TCondition iCondition;
 	RIoSession& iIoSession;
 	RIoReadHandle iStdin;
--- a/core/src/pipe_line.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/core/src/pipe_line.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -601,7 +601,7 @@
 				TOverflowTruncate overflow;
 				buf.AppendFormat(KFormat, &overflow, thisPipedCommand.iCommandName, aError);
 				iStderr.Write(buf);
-					if (aError >= 0)
+				if (aError >= 0)
 					{
 					// Terminate 0 shouldn't equate to a completionError of KErrNone
 					aError = KErrDied;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/worker_thread.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -0,0 +1,744 @@
+// worker_thread.cpp
+// 
+// Copyright (c) 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
+//
+
+/* 
+
+Some notes on how CThreadPool works:
+ * There are 2 types of CWorkerThread - ones that share a specific heap, and those that have their own heap.
+ * Tasks that need a shared heap can only be run using a CWorkerThread that is bound to that heap (one will be created if necessary)
+ * Tasks that don't need a shared heap can be run on any CWorkerThread that has its own heap.
+ * Excess CWorkerThreads are deleted after they've been idle for KCacheTime.
+ * CThreadPool uses an RFastLock on all operations that could be called from multiple threads.
+ * All member data of CThreadPool (including CWorkerThreads and their members) is kept on iThreadPoolAllocator, which was the heap
+   that the CThreadPool was created on. User::SwitchAllocator() is used where necessary to manipulate CThreadPool data from other threads
+   while the Lock is held
+ * When a task is queued, a non-busy CWorkerThread is found (or created).
+ * When a task is completed in a worker thread, it calls iParentThread.RequestComplete(iCompletionStatus, err). It then calls
+   CThreadPool::WorkerFinished() to set the CWorkerThread to be non-busy again.
+ * When a CWorkerThread is created it arranges (via SignalSelf()) to add a CThreadDeathWatcher to the main thread.
+   It follows that notifications about tasks failed because of thread death are always serviced by the main thread, which must therefore
+   remain responsive. (In the non-dead case, the completion notification is sent directly from the worker thread).
+
+*/
+
+#include "worker_thread.h"
+#include <e32debug.h>
+
+#define WT_LOG(args...)
+
+// These defines are for debugging only
+//#define WT_LOG(args...) RDebug::Print(args)
+// These probably don't even work any more - use with extreme caution!
+//#define NO_REUSE
+//#define IMMEDIATE_CLEANUP
+
+const TInt KCacheTime = 1000000; // 1 second
+
+// Thread death watchers run in the context of the thread pool main thread
+class CThreadDeathWatcher : public CActive
+	{
+public:
+	CThreadDeathWatcher(CWorkerThread* aWorker)
+		: CActive(CActive::EPriorityHigh), iWorker(aWorker)
+		{
+		}
+	~CThreadDeathWatcher()
+		{
+		WT_LOG(_L("Deleting thread death watcher for worker %d"), TUint(iWorker->GetThreadId()));
+		Cancel();
+		}
+	void StartWatching()
+		{
+		CActiveScheduler::Add(this);
+		iWorker->iWorkerThread.Logon(iStatus);
+		SetActive();
+		}
+	CWorkerThread* WorkerThread() const
+		{
+		return iWorker;
+		}
+private:
+	void DoCancel()
+		{
+		iWorker->iWorkerThread.LogonCancel(iStatus);
+		}
+	void RunL()
+		{
+		iWorker->SignalClientThatThreadHasDied();
+		iWorker->iParentPool->WorkerDied(iWorker);
+		// WorkerDied may cause this object to be deleted, so don't add anything that accesses member data below this point!
+		}
+
+private:
+	CWorkerThread* iWorker;
+	};
+
+//
+
+
+
+CThreadPool* CThreadPool::NewL()
+	{
+	CThreadPool* self = new(ELeave) CThreadPool();
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+CThreadPool::CThreadPool()
+	: CActive(CActive::EPriorityLow) // Cleanup doesn't need to be a priority
+	{
+	CActiveScheduler::Add(this);
+	}
+
+void CThreadPool::ConstructL()
+	{
+	User::LeaveIfError(iMainThread.Open(RThread().Id()));
+	iIdleTimer = CPeriodic::NewL(CActive::EPriorityLow-1); // Interactions around iPendingThreadLogons mean it's probably a good idea to have this priority below that of CThreadPool itself
+	iThreadPoolAllocator = &User::Allocator();
+	User::LeaveIfError(iLock.CreateLocal());
+	iThreads.ReserveL(2);
+	iThreadDeathWatchers.ReserveL(2);
+	// We don't create any workers by default
+	}
+
+CThreadPool::~CThreadPool()
+	{
+	WT_LOG(_L("Deleting thread pool. %d threads created during its lifetime, %d currently and %d watchers"), iCountThreadsCreated, iThreads.Count(), iThreadDeathWatchers.Count());
+	Cancel();
+	for (TInt i = 0; i < iThreads.Count(); i++)
+		{
+		iThreads[i]->Shutdown();
+		}
+	iThreadDeathWatchers.ResetAndDestroy();
+	iThreads.ResetAndDestroy();
+	iLock.Close();
+	delete iIdleTimer;
+	iMainThread.Close();
+	iPendingThreadLogons.Close();
+	}
+
+void CThreadPool::Lock()
+	{
+	WT_LOG(_L("Getting lock from thread %d"), TUint(RThread().Id()));
+	iLock.Wait();
+	WT_LOG(_L("Got lock from thread %d"), TUint(RThread().Id()));
+	}
+
+void CThreadPool::SwitchToThreadPoolHeap()
+	{
+	//ASSERT(iLock.IsHeld());
+	RAllocator* allocator = &User::Allocator();
+	if (allocator != iThreadPoolAllocator)
+		{
+		WT_LOG(_L("Thread %d switching from 0x%x to thread pool allocator 0x%x"), TUint(RThread().Id()), allocator, iThreadPoolAllocator);
+		iTempThreadAllocator = User::SwitchAllocator(iThreadPoolAllocator);
+		}
+	}
+
+void CThreadPool::RestoreHeap()
+	{
+	if (iTempThreadAllocator)
+		{
+		WT_LOG(_L("Thread %d restoring heap 0x%x"), TUint(RThread().Id()), iTempThreadAllocator);
+		User::SwitchAllocator(iTempThreadAllocator);
+		iTempThreadAllocator = NULL;
+		}
+	}
+
+void CThreadPool::Unlock()
+	{
+	RestoreHeap();
+	WT_LOG(_L("Releasing lock from thread %d"), TUint(RThread().Id()));
+	iLock.Signal();
+	}
+
+void CThreadPool::LockLC()
+	{
+	Lock();
+	CleanupStack::PushL(TCleanupItem(&DoUnlock, &iLock));
+	}
+
+#define _LOFF(p,T,f) ((T*)(((TUint8*)(p))-_FOFF(T,f))) // Why isn't this defined user-side?
+
+void CThreadPool::DoUnlock(TAny* aLock)
+	{
+	CThreadPool* self = _LOFF(aLock, CThreadPool, iLock);
+	self->Unlock();
+	}
+
+MThreadedTask* CThreadPool::NewTaskInSeparateThreadL(const TDesC& aThreadName, TBool aSharedHeap, MTaskRunner::TThreadFunctionL aThreadFunction, TAny* aThreadContext)
+	{
+	WT_LOG(_L("1. NewTaskInSeparateThreadL task %d (%S)"), iTaskCounter, &aThreadName);
+
+	LockLC();
+	// Hunt for a non-busy thread
+	CWorkerThread* foundThread = NULL;
+	RAllocator* requiredAllocator = aSharedHeap ? &User::Allocator() : NULL;
+#ifndef NO_REUSE // This code normally does run, except during debugging when NO_REUSE is defined
+	for (TInt i = 0; i < iThreads.Count(); i++)
+		{
+		CWorkerThread* thread = iThreads[i];
+		if (!thread->Busy() && thread->SharedAllocator() == requiredAllocator)
+			{
+			// If the worker thread is sharing an allocator, it must be sharing the *same* one as this current thread
+			ASSERT(thread->Running());
+			foundThread = thread;
+			break;
+			}
+		}
+#endif
+
+	if (foundThread == NULL)
+		{
+		SwitchToThreadPoolHeap();
+		iPendingThreadLogons.ReserveL(iPendingThreadLogons.Count() + 1); // So the Append below can't fail
+		iThreadDeathWatchers.ReserveL(iThreadDeathWatchers.Count() + 1);
+		iThreads.ReserveL(iThreads.Count() + 1);
+		foundThread = CWorkerThread::NewLC(this, requiredAllocator);
+		CThreadDeathWatcher* threadWatcher = new(ELeave) CThreadDeathWatcher(foundThread);
+		iCountThreadsCreated++;
+		WT_LOG(_L("Created new worker thread %d"), TUint(foundThread->GetThreadId()));
+		iThreads.Append(foundThread);
+		iPendingThreadLogons.Append(threadWatcher);
+		iThreadDeathWatchers.Append(threadWatcher);
+		CleanupStack::Pop(foundThread);	
+
+		SignalSelf(); // So the iPendingThreadLogons gets sorted out in context of main thread
+		RestoreHeap();
+		}
+
+	WT_LOG(_L("Using worker thread %d for task %d (%S)"), TUint(foundThread->GetThreadId()), iTaskCounter, &aThreadName);
+
+	User::LeaveIfError(foundThread->Setup(iTaskCounter, aThreadName, aThreadFunction, aThreadContext));
+	foundThread->SetBusy(ETrue);
+	iTaskCounter++;
+	CleanupStack::PopAndDestroy(&iLock);
+	return foundThread;
+	}
+
+void CThreadPool::WorkerDied(CWorkerThread* aWorker)
+	{
+	Lock();
+	// This is now always called in the main thread
+	ASSERT(RThread().Id() == iMainThread.Id());
+
+	for (TInt i = 0; i < iThreadDeathWatchers.Count(); i++)
+		{
+		if (iThreadDeathWatchers[i]->WorkerThread() == aWorker)
+			{
+			// Clean up the watcher first
+			delete iThreadDeathWatchers[i];
+			iThreadDeathWatchers.Remove(i);
+
+			// The indexes in both arrays are guaranteed to be the same
+			ASSERT(iThreads[i] == aWorker);
+			delete aWorker;
+			iThreads.Remove(i);
+
+			break;
+			}
+		}
+	Unlock();
+	}
+
+void CThreadPool::WorkerFinished(CWorkerThread* aWorker)
+	{
+	Lock();
+	aWorker->SetBusy(EFalse);
+#if defined(NO_REUSE)
+	// Nothing
+#else
+	// This is the normal case - queue ourself to run (on the main thread) so we can trigger the idle timer from our RunL
+	// Can't do that directly as timers are all thread-local
+	SignalSelf();
+#endif
+	Unlock();
+	}
+
+
+void CThreadPool::SignalSelf()
+	{
+	// TODO: Optimise if we actually are the main thread?
+
+	// Must be holding lock
+	iPendingCallbacks++;
+	TRequestStatus* stat = &iStatus;
+	if (iPendingCallbacks == 1) // Can't use IsActive() as we might not be in the same thread
+		{
+		iStatus = KRequestPending;
+		SetActive();
+		}
+	iMainThread.RequestComplete(stat, KErrNone); // Fortunately RThread::RequestComplete doesn't set the status to KRequestPending before completing it (unlike User::RequestComplete)
+	}
+
+void CThreadPool::CleanupAnyWorkersSharingAllocator(RAllocator* aAllocator)
+	{
+	Lock();
+	//SwitchToThreadPoolHeap(); // Not necessary, Shutdown doesn't alloc
+	for (TInt i = iThreads.Count() - 1; i >= 0; i--)
+		{
+		CWorkerThread* worker = iThreads[i];
+		if (worker->SharedAllocator() == aAllocator)
+			{
+			WT_LOG(_L("Shutting down worker thread id %d because it shares allocator %x"), (TUint)worker->GetThreadId(), aAllocator);
+			ASSERT(!worker->Busy());
+			worker->Shutdown();
+			// We don't delete the worker here, let CThreadDeathWatcher handle that
+			}
+		}
+	Unlock();
+	}
+
+void CThreadPool::PerformHouseKeeping()
+	{
+	// Time to do some housekeeping. Algorithm is:
+	// * Keep a single spare non-busy shared worker around, but only for the main heap (ie a CWorkerThread whose SharedAllocator() equals iThreadPoolAllocator)
+	// * Bin everything else that isn't busy
+
+	WT_LOG(_L("Idle timer expired, cleaning up spare workers"));
+	Lock();
+	ASSERT(&User::Allocator() == iThreadPoolAllocator); // We're running in the thread pool's main thread so no need to switch allocators
+
+#ifdef IMMEDIATE_CLEANUP
+	// Everything gets nuked if this is defined
+	TBool foundSpareWorker = ETrue;
+#else
+	TBool foundSpareWorker = EFalse;
+#endif
+	for (TInt i = iThreads.Count() - 1; i >= 0; i--)
+		{
+		CWorkerThread* worker = iThreads[i];
+		if (!worker->Busy())
+			{
+			RAllocator* allocator = worker->SharedAllocator();
+			if (allocator == iThreadPoolAllocator && !foundSpareWorker)
+				{
+				// This one can stay
+				foundSpareWorker = ETrue;
+				}
+			else
+				{
+				// Everything else (that isn't busy) gets cleaned up
+				delete iThreadDeathWatchers[i];
+				iThreadDeathWatchers.Remove(i);
+				worker->Shutdown();
+				delete worker;
+				iThreads.Remove(i);
+				}
+			}
+		}
+	Unlock();
+	}
+
+void CThreadPool::DoCancel()
+	{
+	// There's nothing we can cancel as we complete ourself
+	}
+
+void CThreadPool::RunL()
+	{
+	Lock();
+	iPendingCallbacks--;
+	while (iPendingCallbacks)
+		{
+		// Consume any further signals currently pending - we're somewhat abusing the active scheduler by completing the same repeatedly without it running in between so we have to consume the abuse here to balance things
+		User::WaitForRequest(iStatus);
+		iPendingCallbacks--;
+		}
+
+	// We kinda overload our RunL, using it for both registering logons and triggering the idle cleanup. Ah well.
+
+	if (iPendingThreadLogons.Count())
+		{
+		for (TInt i = 0; i < iPendingThreadLogons.Count(); i++)
+			{
+			iPendingThreadLogons[i]->StartWatching();
+			}
+		SwitchToThreadPoolHeap();
+		iPendingThreadLogons.Reset();
+		}
+
+#ifdef IMMEDIATE_CLEANUP
+	Unlock();
+	// Delete the thread straight away
+	PerformHouseKeeping();
+#else
+	// Normally go through the timer
+	iIdleTimer->Cancel(); // Reset it if it's already counting
+	iIdleTimer->Start(KCacheTime, KCacheTime, TCallBack(&TimerCallback, this));
+	Unlock();
+#endif
+	}
+
+TInt CThreadPool::TimerCallback(TAny* aSelf)
+	{
+	CThreadPool* self = static_cast<CThreadPool*>(aSelf);
+	self->iIdleTimer->Cancel(); // Stop the thing being periodic
+	self->PerformHouseKeeping();
+	return 0;
+	}
+
+TBool CThreadPool::SharesThreadPoolAllocator(CWorkerThread* aWorker) const
+	{
+	return aWorker->SharedAllocator() == iThreadPoolAllocator;
+	}
+
+////
+
+const TInt KMaxHeapSize = 1024 * 1024;
+
+// CWorkerThreadDispatchers run in the context of the worker thread they're owned by
+class CWorkerThreadDispatcher : public CActive
+	{
+public:
+	CWorkerThreadDispatcher(CWorkerThread* aThread)
+		: CActive(CActive::EPriorityStandard), iThread(aThread)
+		{
+		CActiveScheduler::Add(this);
+		iStatus = KRequestPending;
+		SetActive();
+		}
+
+	void RunL()
+		{
+		if (iStatus.Int() == KErrNone)
+			{
+			iStatus = KRequestPending;
+			SetActive();
+			iThread->ThreadRun();
+			}
+		else
+			{
+			// Time to die
+			iThread->iAsWait.AsyncStop();
+			}
+		}
+
+	void DoCancel() {} // Can never happen because we can only ever be destroyed as a result of the AsyncStop in RunL from which we can never be active
+
+private:
+	CWorkerThread* iThread;
+	};
+
+CWorkerThread* CWorkerThread::NewLC(CThreadPool* aParentPool, RAllocator* aSharedAllocator)
+	{
+	CWorkerThread* self = new(ELeave) CWorkerThread(aParentPool, aSharedAllocator);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	return self;
+	}
+
+CWorkerThread::CWorkerThread(CThreadPool* aParentPool, RAllocator* aSharedAllocator)
+	: iParentPool(aParentPool), iSharedAllocator(aSharedAllocator)
+	{
+	iWorkerThread.SetHandle(0);
+	}
+
+void CWorkerThread::ConstructL()
+	{
+	TInt err = KErrAlreadyExists;
+	TInt nonce = 0;
+	while (err == KErrAlreadyExists)
+		{
+		TName name;
+		if (nonce == 0)
+			{
+			name.Format(_L("WorkerThread_%x"), this);
+			}
+		else
+			{
+			name.Format(_L("WorkerThread_%x_%d"), this, nonce);
+			}
+		nonce++;
+		if (iSharedAllocator)
+			{
+			err = iWorkerThread.Create(name, &ThreadFn, KDefaultStackSize, iSharedAllocator, this);
+			}
+		else
+			{
+			// Create a new heap with default heap size
+			err = iWorkerThread.Create(name, &ThreadFn, KDefaultStackSize, KMinHeapSize, KMaxHeapSize, this);
+			}
+		}
+	User::LeaveIfError(err);
+
+	TRequestStatus stat;
+	iWorkerThread.Rendezvous(stat);
+
+	ASSERT(stat == KRequestPending);
+	if (stat == KRequestPending)
+		{
+		iWorkerThread.Resume();
+		}
+	else
+		{
+		iWorkerThread.Kill(stat.Int());
+		}
+
+	User::WaitForRequest(stat);
+	User::LeaveIfError(stat.Int());
+	// Thread is now ready to do stuff
+	}
+
+CWorkerThread::~CWorkerThread()
+	{
+	if (iWorkerThread.Handle())
+		{
+		WT_LOG(_L("Deleting worker thread %d"), TUint(GetThreadId()));
+		}
+	else
+		{
+		// Haven't constructed the thread yet, so all we have available to log is our this pointer
+		WT_LOG(_L("Deleting worker thread 0x%08x"), this);
+		}
+	ASSERT(iWorkerThread.Handle() == 0 || iWorkerThread.ExitType() != EExitPending);
+	iParentThread.Close();
+	iWorkerThread.Close();
+	}
+
+TInt CWorkerThread::Setup(TInt aTaskId, const TDesC& aThreadName, MTaskRunner::TThreadFunctionL aThreadFunction, TAny* aThreadContext)
+	{
+	ASSERT(!Busy());
+
+	// Things to do in preparation for running a command:
+	// 1. Check what thread we're running in now and set ourselves and our thread death watcher on its scheduler (may not be the thread that created us originally)
+	// 2. Store the required context for the worker thread to access
+
+	TInt err = iParentThread.Open(RThread().Id());
+	if (!err)
+		{
+		iTaskId = aTaskId;
+		iName = aThreadName.Left(iName.MaxLength());
+		iFn = aThreadFunction;
+		iContext = aThreadContext;
+		}
+	return err;
+	}
+
+TInt CWorkerThread::ExecuteTask(TRequestStatus& aCompletionStatus)
+	{
+	WT_LOG(_L("2. + Go task %d (%S)"), iTaskId, &iName);
+
+	ASSERT(iCompletionStatus == NULL);
+	aCompletionStatus = KRequestPending;
+	iCompletionStatus = &aCompletionStatus;
+
+	// Setup rendezvous and completion requestStatuses before signalling iDispatchStatus
+	TRequestStatus rendezvousStat;
+	iWorkerThread.Rendezvous(rendezvousStat);
+
+	// Signal the worker thread to do its thing
+	TRequestStatus* dispatchStat = iDispatchStatus;
+	iWorkerThread.RequestComplete(dispatchStat, KErrNone);
+
+	User::WaitForRequest(rendezvousStat);
+
+	TInt err = rendezvousStat.Int();
+	if (iWorkerThread.ExitType() != EExitPending && rendezvousStat.Int() >= 0)
+		{
+		err = KErrDied;
+		}
+
+	WT_LOG(_L("6. - Go task %d (%S) err=%d"), iTaskId, &iName, err);
+
+	if (err != KErrNone)
+		{
+		// We don't signal completion if there was an error prior to the rendezvous, we just return it here
+		iCompletionStatus = NULL;
+		}
+	return err;
+	}
+
+TThreadId CWorkerThread::GetThreadId() const
+	{
+	return iWorkerThread.Id();
+	}
+
+void CWorkerThread::AbortTask()
+	{
+	// Undo setup
+	iName.Zero();
+	iFn = NULL;
+	iContext = NULL;
+	iParentThread.Close();
+	iParentPool->WorkerFinished(this);
+	}
+
+void CWorkerThread::ThreadRun()
+	{
+	// Runs in the worker thread
+
+	if (!UsingSharedAllocator())
+		{
+		__UHEAP_MARK;
+		}
+
+	TInt i = 0;
+	TInt err = KErrNone;
+	do
+		{
+		TName threadName;
+		threadName.Format(_L("%S_%02d"), &iName, i++);
+		err = User::RenameThread(threadName);
+		if (err == KErrNone)
+			{
+			WT_LOG(_L("3. Running thread for task %d (%S)"), iTaskId, &threadName);
+			}
+		}
+	while (err == KErrAlreadyExists);
+
+	// Execute the actual function
+	WT_LOG(_L("4. + ThreadRun for task %d (%S)"), iTaskId, &iName);
+	TRAP(err, (*iFn)(iContext));
+	WT_LOG(_L("7. - ThreadRun for task %d (%S) signalling thread %d with err=%d"), iTaskId, &iName, TUint(iParentThread.Id()), err);
+
+	if (!UsingSharedAllocator())
+		{
+		// Do this before saying we've actually finished the task, otherwise we risk deadlocking on the thread pool lock when IMMEDIATE_CLEANUP is defined
+		// because CleanupAnyWorkersSharingAllocator takes the lock, but the lock can already be held by that point around the Shutdown that IMMEDIATE_CLEANUP does.
+		iParentPool->CleanupAnyWorkersSharingAllocator(&User::Allocator()); // Otherwise the heap check will fail
+		}
+
+	// And signal back the result
+	iParentPool->WorkerFinished(this);
+	CompleteParentRequest(err);
+
+	// Finally put our name back to what it was
+#ifdef _DEBUG
+	_LIT(KDebugName, "WorkerThread_%x (was %S)");
+	TName threadName = RThread().Name();
+	TPtrC oldName = threadName.Left(threadName.MaxLength() - KDebugName().Length());
+	TName newName;
+	newName.Format(KDebugName, this, &oldName);
+	User::RenameThread(newName);
+#else
+	TName threadName;
+	threadName.Format(_L("WorkerThread_%x"), this);
+	User::RenameThread(threadName);
+#endif
+
+	if (!UsingSharedAllocator())
+		{
+		__UHEAP_MARKEND;
+		}
+	}
+
+TBool CWorkerThread::Busy() const
+	{
+	return iBusy;
+	}
+
+void CWorkerThread::SetBusy(TBool aBusy)
+	{
+	iBusy = aBusy;
+	}
+
+TBool CWorkerThread::UsingSharedAllocator() const
+	{
+	return iSharedAllocator != NULL;
+	}
+
+RAllocator* CWorkerThread::SharedAllocator() const
+	{
+	return iSharedAllocator;
+	}
+
+void CWorkerThread::SignalClientThatThreadHasDied()
+	{
+	WT_LOG(_L("Task %d died with exittype %d reason %d"), iTaskId, iWorkerThread.ExitType(), iWorkerThread.ExitReason());
+	TInt err = iWorkerThread.ExitReason();
+	if (err >= 0) err = KErrDied;
+	CompleteParentRequest(err);
+	}
+
+TInt CWorkerThread::ThreadFn(TAny* aSelf)
+	{
+	CWorkerThread* self = static_cast<CWorkerThread*>(aSelf);
+	if (self->iParentPool->SharesThreadPoolAllocator(self))
+		{
+		// If we're sharing the main fshell heap, we have to play by the rules and not crash
+		User::SetCritical(User::EProcessCritical);
+		}
+	else
+		{
+		__UHEAP_MARK;
+		}
+	TInt err = KErrNoMemory;
+	CTrapCleanup* cleanup = CTrapCleanup::New();
+	//WT_LOG(_L("Worker thread %d creating trapcleanup 0x%x"), TUint(RThread().Id()), cleanup);
+	if (cleanup)
+		{
+		TRAP(err, self->ThreadFnL());
+		//WT_LOG(_L("Worker thread %d deleting trapcleanup 0x%x"), TUint(RThread().Id()), cleanup);
+		delete cleanup;
+		}
+	if (!self->UsingSharedAllocator())
+		{
+		__UHEAP_MARKEND;
+		}
+	return err;
+	}
+
+void CWorkerThread::ThreadFnL()
+	{
+	CActiveScheduler* scheduler = new(ELeave) CActiveScheduler;
+	CleanupStack::PushL(scheduler);
+	CActiveScheduler::Install(scheduler);
+
+	CWorkerThreadDispatcher* dispatcher = new(ELeave) CWorkerThreadDispatcher(this);
+	CleanupStack::PushL(dispatcher);
+	iDispatchStatus = &dispatcher->iStatus;
+	RThread::Rendezvous(KErrNone);
+	iAsWait.Start();
+	CleanupStack::PopAndDestroy(2, scheduler); // dispatcher, scheduler
+	}
+
+void CWorkerThread::CompleteParentRequest(TInt aError)
+	{
+	if (iCompletionStatus)
+		{
+		// If the parent thread has died, then the completion status pointer might no longer be valid (as it would point into the parent's heap
+		// that might now no longer exist) so we can't safely use it in that case
+		if (iParentThread.ExitType() == EExitPending)
+			{
+			iParentThread.RequestComplete(iCompletionStatus, aError);
+			}
+		else
+			{
+			iCompletionStatus = NULL;
+			}
+		}
+	}
+
+void CWorkerThread::Shutdown()
+	{
+	// Can be called from (potentially) any thread. Don't alloc or delete as might not be switched to the thread pool allocator
+	WT_LOG(_L("Shutting down worker thread %d whose exittype is %d"), TUint(GetThreadId()), iWorkerThread.ExitType());
+
+	if (Busy())
+		{
+		WT_LOG(_L("Thread is still busy - killing it"));
+		iWorkerThread.Kill(KErrAbort);
+		// The thread death watcher should take care of everything else, eventually
+		}
+	else if (iWorkerThread.ExitType() == EExitPending)
+		{
+		TRequestStatus stat;
+		iWorkerThread.Logon(stat);
+		iWorkerThread.RequestComplete(iDispatchStatus, KErrCancel);
+		User::WaitForRequest(stat);
+		WT_LOG(_L("Shut down worker %d exit=%d"), TUint(GetThreadId()), iWorkerThread.ExitType());
+		}
+	}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/worker_thread.h	Thu Oct 28 16:54:54 2010 +0100
@@ -0,0 +1,132 @@
+// worker_thread.h
+// 
+// Copyright (c) 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
+//
+
+#ifndef WORKER_THREAD_H
+#define WORKER_THREAD_H
+
+#include <e32base.h>
+
+class MThreadedTask
+	{
+public:
+	virtual TThreadId GetThreadId() const=0;
+	virtual TInt ExecuteTask(TRequestStatus& aCompletionStatus)=0;
+	virtual void AbortTask()=0; // If you decide you don't want to call ExecuteTask
+	};
+
+class MTaskRunner
+	{
+public:
+	typedef void (*TThreadFunctionL)(TAny*);
+	virtual MThreadedTask* NewTaskInSeparateThreadL(const TDesC& aThreadName, TBool aSharedHeap, TThreadFunctionL aThreadFunction, TAny* aThreadContext)=0;
+	};
+
+class CWorkerThread;
+class CThreadDeathWatcher;
+
+class CThreadPool : public CActive, public MTaskRunner
+	{
+public:
+	static CThreadPool* NewL();
+	~CThreadPool();
+	void WorkerDied(CWorkerThread* aWorker);
+	void WorkerFinished(CWorkerThread* aWorker);
+	void CleanupAnyWorkersSharingAllocator(RAllocator* aAllocator);
+	TBool SharesThreadPoolAllocator(CWorkerThread* aWorker) const;
+
+public: // From MTaskRunner
+	virtual MThreadedTask* NewTaskInSeparateThreadL(const TDesC& aThreadName, TBool aSharedHeap, TThreadFunctionL aThreadFunction, TAny* aThreadContext);
+
+private:
+	CThreadPool();
+	void ConstructL();
+	static TInt TimerCallback(TAny* aSelf);
+	void PerformHouseKeeping();
+	void RunL();
+	void DoCancel();
+	void SignalSelf();
+
+	void Lock();
+	void LockLC();
+	void SwitchToThreadPoolHeap();
+	void RestoreHeap();
+	void Unlock();
+	static void DoUnlock(TAny* aLock);
+
+private:
+	RFastLock iLock; // Thread pool is shared between eg fshell main thread and any source threads, so needs locking around WorkerFinished and NewTaskInSeparateThreadL etc
+	RAllocator* iThreadPoolAllocator; // The thread pool can be modified from a thread that isn't using the main heap, so we need to track that and use User::SwitchAllocator as needed
+	RAllocator* iTempThreadAllocator; // Only used while lock is held
+	RPointerArray<CWorkerThread> iThreads;
+	TInt iTaskCounter;
+	RThread iMainThread;
+	TInt iPendingCallbacks;
+	CPeriodic* iIdleTimer;
+	TInt iCountThreadsCreated; // This is for statistics gathering, not involved in the logic
+	RArray<CThreadDeathWatcher*> iPendingThreadLogons; // Not owned
+	RPointerArray<CThreadDeathWatcher> iThreadDeathWatchers;
+	};
+
+class CWorkerThread : public CBase, public MThreadedTask
+	{
+public:
+	static CWorkerThread* NewLC(CThreadPool* aParentPool, RAllocator* aSharedAllocator);
+	// For safety these 2 need to be called with the thread pool lock held
+	TBool Busy() const;
+	void SetBusy(TBool aBusy);
+
+	TBool UsingSharedAllocator() const;
+	RAllocator* SharedAllocator() const;
+	TInt Setup(TInt aTaskId, const TDesC& aThreadName, MTaskRunner::TThreadFunctionL aThreadFunction, TAny* aThreadContext);
+	void Shutdown();
+	~CWorkerThread();
+
+	TBool Running() const { return iWorkerThread.Handle() && iWorkerThread.ExitType() == EExitPending; }
+
+public: // From MThreadedTask
+	TThreadId GetThreadId() const;
+	TInt ExecuteTask(TRequestStatus& aCompletionStatus);
+	void AbortTask();
+
+public: // For CThreadDeathWatcher to use
+	void SignalClientThatThreadHasDied();
+
+private:
+	CWorkerThread(CThreadPool* aParentPool, RAllocator* aSharedAllocator);
+	void ConstructL();
+	void ThreadRun();
+	static TInt ThreadFn(TAny* aSelf);
+	void ThreadFnL();
+	void CompleteParentRequest(TInt aError);
+
+private:
+	CThreadPool* iParentPool;
+	RThread iWorkerThread;
+	RAllocator* iSharedAllocator;
+	TRequestStatus* iDispatchStatus;
+	CActiveSchedulerWait iAsWait; // Use a CActiveSchedulerWait to protect ourselves from whatever aThreadFunction may try and do
+	CThreadDeathWatcher* iThreadDeathWatcher;
+	TBool iBusy;
+
+	// Things specific to the task currently being executed
+	RThread iParentThread; // Worker needs this to signal completion to us
+	TName iName;
+	MTaskRunner::TThreadFunctionL iFn;
+	TAny* iContext;
+	TInt iTaskId;
+	TRequestStatus* iCompletionStatus;
+
+	friend class CWorkerThreadDispatcher;
+	friend class CThreadDeathWatcher;
+	};
+
+#endif
--- a/documentation/change_history.pod	Tue Oct 26 15:36:30 2010 +0100
+++ b/documentation/change_history.pod	Thu Oct 28 16:54:54 2010 +0100
@@ -46,10 +46,33 @@
 
 Updated chkdeps command so that it works with byte-pair compressed binaries (experimental).
 
+=item *
+
+Added L<showdebug|commands::showdebug> command to redirect RDebug::Print()s to the console.
+
+=item *
+
+Added L<input|commands::input> command to allow hardware key presses to be easily simulated via a console.
+
+=item *
+
+Added L<base64|commands::base64> command for encoding to and decoding from Base64.
 
 =item *
 
-Added L<showdebug|commands::showdebug> command to redirect RDebug::Print()s to the console.
+Added C<CBtraceAppStart> to btrace_parser.dll. Also changed the interface of C<CBtraceAppResponse> to use window group name patterns rather than process ids (to make it consistent with other parts of the API).
+
+=item *
+
+Fshell now reuses threads for built-in commands that execute in quick succession. The thread pool takes into account the requirements of the command when assigning a thread (eg whether it needs to share a heap with its parent command) and creates a new one if necessary. Excess threads are cleaned up after a short idle period (currently 1 second).
+
+=item *
+
+Added support for L<source|commands::source> reading from C<stdin> (primarily to better support remote execution of scripts).
+
+=item *
+
+Fixed a bug in iocli.dll that could cause environment variable updates to be ignored.
 
 =back
 
--- a/documentation/cif_syntax.pod	Tue Oct 26 15:36:30 2010 +0100
+++ b/documentation/cif_syntax.pod	Thu Oct 28 16:54:54 2010 +0100
@@ -119,6 +119,10 @@
 
     L<ps|ps>, L<objinfo|objinfo>
 
+=head3 ==smoke-test
+
+Commands may optionally specify a snippet of code to test the basic functionality of the command. See the L<ciftest|commands::ciftest> documentation for more details.
+
 =head2 The enum type
 
 The C<enum> type can be used for an option or argument that takes a limited number of named values, in much the same was as C/C++ enums are used. For an option or argument of type C<enum> you must specify the possible values that the enum can take using the C<==enum-value> keyword. These may optionally have a separate description per value. If any value has an individual description, they all must.
--- a/libraries/btrace_parser/bwins/btrace_parseru.def	Tue Oct 26 15:36:30 2010 +0100
+++ b/libraries/btrace_parser/bwins/btrace_parseru.def	Thu Oct 28 16:54:54 2010 +0100
@@ -27,7 +27,7 @@
 	??0TBtraceIdBase@@IAE@XZ @ 26 NONAME ; TBtraceIdBase::TBtraceIdBase(void)
 	??1CBtraceDomainEvent@@UAE@XZ @ 27 NONAME ; CBtraceDomainEvent::~CBtraceDomainEvent(void)
 	?NewL@CBtraceGeneric@@SAPAV1@AAVCBtraceReader@@@Z @ 28 NONAME ; class CBtraceGeneric * CBtraceGeneric::NewL(class CBtraceReader &)
-	?NewL@CBtraceAppResponse@@SAPAV1@AAVCBtraceReader@@@Z @ 29 NONAME ; class CBtraceAppResponse * CBtraceAppResponse::NewL(class CBtraceReader &)
+	?NewL@CBtraceAppResponse@@SAPAV1@AAVCBtraceReader@@AAVCBtraceContext@@@Z @ 29 NONAME ; class CBtraceAppResponse * CBtraceAppResponse::NewL(class CBtraceReader &, class CBtraceContext &)
 	?FlushL@CBtraceReader@@QAEXXZ @ 30 NONAME ; void CBtraceReader::FlushL(void)
 	?FindThreadsL@CBtraceContext@@QBEXABVTDesC16@@AAV?$RArray@VTBtraceThreadId@@@@@Z @ 31 NONAME ; void CBtraceContext::FindThreadsL(class TDesC16 const &, class RArray<class TBtraceThreadId> &) const
 	??1CBtraceTextOnScreen@@UAE@XZ @ 32 NONAME ; CBtraceTextOnScreen::~CBtraceTextOnScreen(void)
@@ -100,7 +100,7 @@
 	?DecRef@CRefCountedObject@@QAEXXZ @ 99 NONAME ; void CRefCountedObject::DecRef(void)
 	??1CBtraceAppResponse@@UAE@XZ @ 100 NONAME ; CBtraceAppResponse::~CBtraceAppResponse(void)
 	?CurrentTickCount@CBtraceReader@@QBEABVTBtraceTickCount@@XZ @ 101 NONAME ; class TBtraceTickCount const & CBtraceReader::CurrentTickCount(void) const
-	?NotifyAppResponseL@CBtraceAppResponse@@QAEXAAVMBtraceAppResponseObserver@@ABVTProcessId@@W4TBtraceNotificationPersistence@@@Z @ 102 NONAME ; void CBtraceAppResponse::NotifyAppResponseL(class MBtraceAppResponseObserver &, class TProcessId const &, enum TBtraceNotificationPersistence)
+	?NotifyAppResponseL@CBtraceAppResponse@@QAEXAAVMBtraceAppResponseObserver@@ABVTDesC16@@W4TBtraceNotificationPersistence@@@Z @ 102 NONAME ; void CBtraceAppResponse::NotifyAppResponseL(class MBtraceAppResponseObserver &, class TDesC16 const &, enum TBtraceNotificationPersistence)
 	?NotifyContextSwitchL@CBtraceCpuUsage@@QAEXABVTBtraceThreadId@@AAVMBtraceCpuUsageObserver@@@Z @ 103 NONAME ; void CBtraceCpuUsage::NotifyContextSwitchL(class TBtraceThreadId const &, class MBtraceCpuUsageObserver &)
 	??PTBtraceTickCount@@QBEHABV0@@Z @ 104 NONAME ; int TBtraceTickCount::operator>=(class TBtraceTickCount const &) const
 	?CancelNotifyUnclassifiedData@CBtraceKeyPress@@QAEXAAVMBtraceKeyPressObserver@@@Z @ 105 NONAME ; void CBtraceKeyPress::CancelNotifyUnclassifiedData(class MBtraceKeyPressObserver &)
@@ -112,7 +112,7 @@
 	?NewL@CBtraceTextOnScreen@@SAPAV1@AAVCBtraceReader@@@Z @ 111 NONAME ; class CBtraceTextOnScreen * CBtraceTextOnScreen::NewL(class CBtraceReader &)
 	??0TBtraceWindowGroupId@@QAE@ABV0@@Z @ 112 NONAME ; TBtraceWindowGroupId::TBtraceWindowGroupId(class TBtraceWindowGroupId const &)
 	?NotifyCpuUsageL@CBtraceCpuUsage@@QAEXKVTTimeIntervalMicroSeconds32@@AAVMBtraceCpuUsageObserver@@@Z @ 113 NONAME ; void CBtraceCpuUsage::NotifyCpuUsageL(unsigned long, class TTimeIntervalMicroSeconds32, class MBtraceCpuUsageObserver &)
-	?NotifyAppResponseL@CBtraceAppResponse@@QAEXAAVMBtraceAppResponseObserver@@ABVTProcessId@@@Z @ 114 NONAME ; void CBtraceAppResponse::NotifyAppResponseL(class MBtraceAppResponseObserver &, class TProcessId const &)
+	?NotifyAppResponseL@CBtraceAppResponse@@QAEXAAVMBtraceAppResponseObserver@@ABVTDesC16@@@Z @ 114 NONAME ; void CBtraceAppResponse::NotifyAppResponseL(class MBtraceAppResponseObserver &, class TDesC16 const &)
 	?ProcessName@CBtraceContext@@QBEABVTDesC16@@ABVTBtraceProcessId@@@Z @ 115 NONAME ; class TDesC16 const & CBtraceContext::ProcessName(class TBtraceProcessId const &) const
 	?EnableCategoryL@CBtraceReader@@QAEXI@Z @ 116 NONAME ; void CBtraceReader::EnableCategoryL(unsigned int)
 	?NotifyGenericEventL@CBtraceGeneric@@QAEXAAVMBtraceGenericObserver@@@Z @ 117 NONAME ; void CBtraceGeneric::NotifyGenericEventL(class MBtraceGenericObserver &)
@@ -152,4 +152,9 @@
 	?SetMultipartReassemblyL@CBtraceReader@@QAEXH@Z @ 151 NONAME ; void CBtraceReader::SetMultipartReassemblyL(int)
 	?MicroSecondsToNanoTicks@TBtraceUtils@@SAKVTTimeIntervalMicroSeconds32@@@Z @ 152 NONAME ; unsigned long TBtraceUtils::MicroSecondsToNanoTicks(class TTimeIntervalMicroSeconds32)
 	?Start@CBtraceReader@@QAEXABVTBtraceTickCount@@VTTimeIntervalMicroSeconds32@@@Z @ 153 NONAME ; void CBtraceReader::Start(class TBtraceTickCount const &, class TTimeIntervalMicroSeconds32)
+	??1CBtraceAppStart@@UAE@XZ @ 154 NONAME ; CBtraceAppStart::~CBtraceAppStart(void)
+	?CancelNotifyAppStart@CBtraceAppStart@@QAEXAAVMBtraceAppStartObserver@@@Z @ 155 NONAME ; void CBtraceAppStart::CancelNotifyAppStart(class MBtraceAppStartObserver &)
+	?NewL@CBtraceAppStart@@SAPAV1@AAVCBtraceReader@@AAVCBtraceContext@@@Z @ 156 NONAME ; class CBtraceAppStart * CBtraceAppStart::NewL(class CBtraceReader &, class CBtraceContext &)
+	?NotifyAppStartL@CBtraceAppStart@@QAEXAAVMBtraceAppStartObserver@@ABVTDesC16@@@Z @ 157 NONAME ; void CBtraceAppStart::NotifyAppStartL(class MBtraceAppStartObserver &, class TDesC16 const &)
+	?NotifyAppStartL@CBtraceAppStart@@QAEXAAVMBtraceAppStartObserver@@ABVTDesC16@@W4TBtraceNotificationPersistence@@@Z @ 158 NONAME ; void CBtraceAppStart::NotifyAppStartL(class MBtraceAppStartObserver &, class TDesC16 const &, enum TBtraceNotificationPersistence)
 
--- a/libraries/btrace_parser/eabi/btrace_parseru.def	Tue Oct 26 15:36:30 2010 +0100
+++ b/libraries/btrace_parser/eabi/btrace_parseru.def	Thu Oct 28 16:54:54 2010 +0100
@@ -109,10 +109,10 @@
 	_ZN16TBtraceTickCountC2Ev @ 108 NONAME
 	_ZN17CRefCountedObject6DecRefEv @ 109 NONAME
 	_ZN17CRefCountedObject6IncRefEv @ 110 NONAME
-	_ZN18CBtraceAppResponse18NotifyAppResponseLER26MBtraceAppResponseObserverRK10TProcessId @ 111 NONAME
-	_ZN18CBtraceAppResponse18NotifyAppResponseLER26MBtraceAppResponseObserverRK10TProcessId30TBtraceNotificationPersistence @ 112 NONAME
+	_ZN18CBtraceAppResponse18NotifyAppResponseLER26MBtraceAppResponseObserverRK7TDesC16 @ 111 NONAME
+	_ZN18CBtraceAppResponse18NotifyAppResponseLER26MBtraceAppResponseObserverRK7TDesC1630TBtraceNotificationPersistence @ 112 NONAME
 	_ZN18CBtraceAppResponse23CancelNotifyAppResponseER26MBtraceAppResponseObserver @ 113 NONAME
-	_ZN18CBtraceAppResponse4NewLER13CBtraceReader @ 114 NONAME
+	_ZN18CBtraceAppResponse4NewLER13CBtraceReaderR14CBtraceContext @ 114 NONAME
 	_ZN18CBtraceAppResponseD0Ev @ 115 NONAME
 	_ZN18CBtraceAppResponseD1Ev @ 116 NONAME
 	_ZN18CBtraceAppResponseD2Ev @ 117 NONAME
@@ -189,4 +189,11 @@
 	_ZTI22MBtraceContextObserver @ 188 NONAME
 	_ZTV22MBtraceContextObserver @ 189 NONAME
 	_ZN13CBtraceReader5StartERK16TBtraceTickCount27TTimeIntervalMicroSeconds32 @ 190 NONAME
+	_ZN15CBtraceAppStart15NotifyAppStartLER23MBtraceAppStartObserverRK7TDesC16 @ 191 NONAME
+	_ZN15CBtraceAppStart15NotifyAppStartLER23MBtraceAppStartObserverRK7TDesC1630TBtraceNotificationPersistence @ 192 NONAME
+	_ZN15CBtraceAppStart20CancelNotifyAppStartER23MBtraceAppStartObserver @ 193 NONAME
+	_ZN15CBtraceAppStart4NewLER13CBtraceReaderR14CBtraceContext @ 194 NONAME
+	_ZN15CBtraceAppStartD0Ev @ 195 NONAME
+	_ZN15CBtraceAppStartD1Ev @ 196 NONAME
+	_ZN15CBtraceAppStartD2Ev @ 197 NONAME
 
--- a/libraries/btrace_parser/inc/btrace_parser.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/libraries/btrace_parser/inc/btrace_parser.h	Thu Oct 28 16:54:54 2010 +0100
@@ -770,6 +770,43 @@
 	};
 
 
+class MBtraceAppStartObserver
+	{
+public:
+	virtual void HandleAppStartL(const TBtraceTickCount& aTickCount) = 0;
+	};
+
+NONSHARABLE_CLASS(CBtraceAppStart) : public CRefCountedObject, public MBtraceObserver
+	{
+public:
+	IMPORT_C static CBtraceAppStart* NewL(CBtraceReader& aReader, CBtraceContext& aContext);
+	IMPORT_C ~CBtraceAppStart();
+	IMPORT_C void NotifyAppStartL(MBtraceAppStartObserver& aObserver, const TDesC& aWindowGroupNamePattern);
+	IMPORT_C void NotifyAppStartL(MBtraceAppStartObserver& aObserver, const TDesC& aWindowGroupNamePattern, TBtraceNotificationPersistence aPersistence);
+	IMPORT_C void CancelNotifyAppStart(MBtraceAppStartObserver& aObserver);
+private:
+	CBtraceAppStart(CBtraceReader& aReader, CBtraceContext& aContext);
+	void ConstructL();
+	void SeenAppStartL(const TBtraceTickCount& aTickCount, TInt aWindowGroupId);
+private: // From MBtraceObserver.
+	virtual void HandleBtraceFrameL(const TBtraceFrame& aFrame);
+private:
+	class TAppStartNotif
+		{
+	public:
+		TAppStartNotif(MBtraceAppStartObserver& aObserver, const TDesC& aWindowGroupNamePattern, TBtraceNotificationPersistence aPersistence);
+	public:
+		MBtraceAppStartObserver& iObserver;
+		TPtrC iWindowGroupNamePattern;
+		TBtraceNotificationPersistence iPersistence;
+		};
+private:
+	CBtraceReader& iReader;
+	CBtraceContext& iContext;
+	RArray<TAppStartNotif> iNotifs;
+	};
+
+
 class MBtraceAppResponseObserver
 	{
 public:
@@ -779,29 +816,30 @@
 NONSHARABLE_CLASS(CBtraceAppResponse) : public CRefCountedObject, public MBtraceObserver
 	{
 public:
-	IMPORT_C static CBtraceAppResponse* NewL(CBtraceReader& aReader);
+	IMPORT_C static CBtraceAppResponse* NewL(CBtraceReader& aReader, CBtraceContext& aContext);
 	IMPORT_C ~CBtraceAppResponse();
-	IMPORT_C void NotifyAppResponseL(MBtraceAppResponseObserver& aObserver, const TProcessId& aProcessId);
-	IMPORT_C void NotifyAppResponseL(MBtraceAppResponseObserver& aObserver, const TProcessId& aProcessId, TBtraceNotificationPersistence aPersistence);
+	IMPORT_C void NotifyAppResponseL(MBtraceAppResponseObserver& aObserver, const TDesC& aWindowGroupNamePattern);
+	IMPORT_C void NotifyAppResponseL(MBtraceAppResponseObserver& aObserver, const TDesC& aWindowGroupNamePattern, TBtraceNotificationPersistence aPersistence);
 	IMPORT_C void CancelNotifyAppResponse(MBtraceAppResponseObserver& aObserver);
 private:
-	CBtraceAppResponse(CBtraceReader& aReader);
+	CBtraceAppResponse(CBtraceReader& aReader, CBtraceContext& aContext);
 	void ConstructL();
-	void SeenAppResponseL(const TBtraceTickCount& aTickCount, const TProcessId& aProcessId);
+	void SeenAppResponseL(const TBtraceTickCount& aTickCount, TInt aWindowGroupId);
 private: // From MBtraceObserver.
 	virtual void HandleBtraceFrameL(const TBtraceFrame& aFrame);
 private:
 	class TAppResponseNotif
 		{
 	public:
-		TAppResponseNotif(MBtraceAppResponseObserver& aObserver, const TProcessId& aProcessId, TBtraceNotificationPersistence aPersistence);
+		TAppResponseNotif(MBtraceAppResponseObserver& aObserver, const TDesC& aWindowGroupNamePattern, TBtraceNotificationPersistence aPersistence);
 	public:
 		MBtraceAppResponseObserver& iObserver;
-		TProcessId iProcessId;
+		TPtrC iWindowGroupNamePattern;
 		TBtraceNotificationPersistence iPersistence;
 		};
 private:
 	CBtraceReader& iReader;
+	CBtraceContext& iContext;
 	RArray<TAppResponseNotif> iNotifs;
 	};
 
--- a/libraries/btrace_parser/inc/btrace_parser_defs.h	Tue Oct 26 15:36:30 2010 +0100
+++ b/libraries/btrace_parser/inc/btrace_parser_defs.h	Thu Oct 28 16:54:54 2010 +0100
@@ -58,8 +58,9 @@
 	EAmTraceEventEvCaptureKeyPress				= 2,
 	EAmTraceEventEvCapturePointer				= 3,
 	EAmTraceEventEvCaptureUnclassified			= 4,
-	EAmTraceEventEvCaptureSystemAppResponse		= 5,
+	EAmTraceEventEvCaptureAppResponse		    = 5,
 	EAmTraceEventEvCaptureRawScan				= 6,
+	EAmTraceEventEvCaptureAppStart              = 7,
 	};
 
 
--- a/libraries/btrace_parser/src/btrace_appresponse.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/libraries/btrace_parser/src/btrace_appresponse.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -18,17 +18,17 @@
 // CBtraceAppResponse
 //
 
-EXPORT_C CBtraceAppResponse* CBtraceAppResponse::NewL(CBtraceReader& aReader)
+EXPORT_C CBtraceAppResponse* CBtraceAppResponse::NewL(CBtraceReader& aReader, CBtraceContext& aContext)
 	{
-	CBtraceAppResponse* self = new (ELeave) CBtraceAppResponse(aReader);
+	CBtraceAppResponse* self = new (ELeave) CBtraceAppResponse(aReader, aContext);
 	CleanupStack::PushL(self);
 	self->ConstructL();
 	CleanupStack::Pop(self);
 	return self;
 	}
 
-CBtraceAppResponse::CBtraceAppResponse(CBtraceReader& aReader)
-	: iReader(aReader)
+CBtraceAppResponse::CBtraceAppResponse(CBtraceReader& aReader, CBtraceContext& aContext)
+	: iReader(aReader), iContext(aContext)
 	{
 	}
 
@@ -53,14 +53,13 @@
 			{
 			const TUint32* data = reinterpret_cast<const TUint32*>(aFrame.iData.Ptr());
 			const TAmTraceEventEvCapture event = static_cast<TAmTraceEventEvCapture>(*data++);
-			const TUint32 processIdLowWord = *data++;
-			const TUint32 processIdHighWord = *data++;
 
 			switch (event)
 				{
-				case EAmTraceEventEvCaptureSystemAppResponse:
+				case EAmTraceEventEvCaptureAppResponse:
 					{
-					SeenAppResponseL(aFrame.iTickCount, MAKE_TUINT64(processIdHighWord, processIdLowWord));
+					const TInt32 wgId = *data++;
+					SeenAppResponseL(aFrame.iTickCount, wgId);
 					}
 				break;
 				
@@ -77,32 +76,36 @@
 		};
 	}
 
-void CBtraceAppResponse::SeenAppResponseL(const TBtraceTickCount& aTickCount, const TProcessId& aProcessId)
+void CBtraceAppResponse::SeenAppResponseL(const TBtraceTickCount& aTickCount, TInt aWindowGroupId)
 	{
-	TInt ii = iNotifs.Count();
-	while (--ii >= 0)
+	const TBtraceWindowGroupId* btraceWgId = iContext.FindWindowGroup(aWindowGroupId);
+	if (btraceWgId)
 		{
-		TAppResponseNotif& nt = iNotifs[ii];
-		if (nt.iProcessId == aProcessId)
+		TInt ii = iNotifs.Count();
+		while (--ii >= 0)
 			{
-			MBtraceAppResponseObserver& observer = nt.iObserver;
-			if (nt.iPersistence == ENotificationOneShot)
+			TAppResponseNotif& nt = iNotifs[ii];
+			if (iContext.WindowGroupName(*btraceWgId).MatchF(nt.iWindowGroupNamePattern) != KErrNotFound)
 				{
-				iNotifs.Remove(ii);
+				MBtraceAppResponseObserver& observer = nt.iObserver;
+				if (nt.iPersistence == ENotificationOneShot)
+					{
+					iNotifs.Remove(ii);
+					}
+				observer.HandleAppResponseSeenL(aTickCount);
 				}
-			observer.HandleAppResponseSeenL(aTickCount);
 			}
-		}	
+		}
 	}
 
-EXPORT_C void CBtraceAppResponse::NotifyAppResponseL(MBtraceAppResponseObserver& aObserver, const TProcessId& aProcessId)
+EXPORT_C void CBtraceAppResponse::NotifyAppResponseL(MBtraceAppResponseObserver& aObserver, const TDesC& aWindowGroupNamePattern)
 	{
-	NotifyAppResponseL(aObserver, aProcessId, ENotificationOneShot);
+	NotifyAppResponseL(aObserver, aWindowGroupNamePattern, ENotificationOneShot);
 	}
 
-EXPORT_C void CBtraceAppResponse::NotifyAppResponseL(MBtraceAppResponseObserver& aObserver, const TProcessId& aProcessId, TBtraceNotificationPersistence aPersistence)
+EXPORT_C void CBtraceAppResponse::NotifyAppResponseL(MBtraceAppResponseObserver& aObserver, const TDesC& aWindowGroupNamePattern, TBtraceNotificationPersistence aPersistence)
 	{
-	TAppResponseNotif notify(aObserver, aProcessId, aPersistence);
+	TAppResponseNotif notify(aObserver, aWindowGroupNamePattern, aPersistence);
 	User::LeaveIfError(iNotifs.Append(notify));
 	}
 
@@ -122,7 +125,7 @@
 // CBtraceAppResponse::TAppResponseNotif
 //
 
-CBtraceAppResponse::TAppResponseNotif::TAppResponseNotif(MBtraceAppResponseObserver& aObserver, const TProcessId& aProcessId, TBtraceNotificationPersistence aPersistence)
-	: iObserver(aObserver), iProcessId(aProcessId), iPersistence(aPersistence)
+CBtraceAppResponse::TAppResponseNotif::TAppResponseNotif(MBtraceAppResponseObserver& aObserver, const TDesC& aWindowGroupNamePattern, TBtraceNotificationPersistence aPersistence)
+	: iObserver(aObserver), iWindowGroupNamePattern(aWindowGroupNamePattern), iPersistence(aPersistence)
 	{
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libraries/btrace_parser/src/btrace_appstart.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -0,0 +1,131 @@
+// btrace_appstart.cpp
+// 
+// Copyright (c) 2008 - 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 "btrace_parser.h"
+#include <fshell/common.mmh>
+
+
+//
+// CBtraceAppStart
+//
+
+EXPORT_C CBtraceAppStart* CBtraceAppStart::NewL(CBtraceReader& aReader, CBtraceContext& aContext)
+	{
+	CBtraceAppStart* self = new (ELeave) CBtraceAppStart(aReader, aContext);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+CBtraceAppStart::CBtraceAppStart(CBtraceReader& aReader, CBtraceContext& aContext)
+	: iReader(aReader), iContext(aContext)
+	{
+	}
+
+EXPORT_C CBtraceAppStart::~CBtraceAppStart()
+	{
+	iReader.RemoveObserver(KAmTraceCategory, *this);
+	iNotifs.Close();
+	}
+
+void CBtraceAppStart::ConstructL()
+	{
+	iReader.AddObserverL(KAmTraceCategory, *this);
+	}
+
+void CBtraceAppStart::HandleBtraceFrameL(const TBtraceFrame& aFrame)
+	{
+	if (aFrame.iCategory != KAmTraceCategory) return;
+
+	switch (aFrame.iSubCategory)
+		{
+		case EAmTraceSubCategoryEvCapture:
+			{
+			const TUint32* data = reinterpret_cast<const TUint32*>(aFrame.iData.Ptr());
+			const TAmTraceEventEvCapture event = static_cast<TAmTraceEventEvCapture>(*data++);
+
+			switch (event)
+				{
+				case EAmTraceEventEvCaptureAppStart:
+					{
+					const TInt32 wgId = *data++;
+					SeenAppStartL(aFrame.iTickCount, wgId);
+					}
+				break;
+				
+				default:
+					// ignore the event
+				break;
+				};
+			}
+		break;
+
+		default:
+			// ignore anything we don't know about.
+		break;
+		};
+	}
+
+void CBtraceAppStart::SeenAppStartL(const TBtraceTickCount& aTickCount, TInt aWindowGroupId)
+	{
+	const TBtraceWindowGroupId* btraceWgId = iContext.FindWindowGroup(aWindowGroupId);
+	if (btraceWgId)
+		{
+		TInt ii = iNotifs.Count();
+		while (--ii >= 0)
+			{
+			TAppStartNotif& nt = iNotifs[ii];
+			if (iContext.WindowGroupName(*btraceWgId).MatchF(nt.iWindowGroupNamePattern) != KErrNotFound)
+				{
+				MBtraceAppStartObserver& observer = nt.iObserver;
+				if (nt.iPersistence == ENotificationOneShot)
+					{
+					iNotifs.Remove(ii);
+					}
+				observer.HandleAppStartL(aTickCount);
+				}
+			}	
+		}
+	}
+
+EXPORT_C void CBtraceAppStart::NotifyAppStartL(MBtraceAppStartObserver& aObserver, const TDesC& aWindowGroupNamePattern)
+	{
+	NotifyAppStartL(aObserver, aWindowGroupNamePattern, ENotificationOneShot);
+	}
+
+EXPORT_C void CBtraceAppStart::NotifyAppStartL(MBtraceAppStartObserver& aObserver, const TDesC& aWindowGroupNamePattern, TBtraceNotificationPersistence aPersistence)
+	{
+	TAppStartNotif notify(aObserver, aWindowGroupNamePattern, aPersistence);
+	User::LeaveIfError(iNotifs.Append(notify));
+	}
+
+EXPORT_C void CBtraceAppStart::CancelNotifyAppStart(MBtraceAppStartObserver& aObserver)
+	{
+	for (TInt i = (iNotifs.Count() - 1); i >= 0; --i)
+		{
+		if (&iNotifs[i].iObserver == &aObserver)
+			{
+			iNotifs.Remove(i);
+			}
+		}
+	}
+
+
+//
+// CBtraceAppStart::TAppStartNotif
+//
+
+CBtraceAppStart::TAppStartNotif::TAppStartNotif(MBtraceAppStartObserver& aObserver, const TDesC& aWindowGroupNamePattern, TBtraceNotificationPersistence aPersistence)
+	: iObserver(aObserver), iWindowGroupNamePattern(aWindowGroupNamePattern), iPersistence(aPersistence)
+	{
+	}
--- a/libraries/btrace_parser/src/btrace_parser.mmp	Tue Oct 26 15:36:30 2010 +0100
+++ b/libraries/btrace_parser/src/btrace_parser.mmp	Thu Oct 28 16:54:54 2010 +0100
@@ -29,6 +29,7 @@
 source          btrace_textonscreen.cpp
 source          btrace_generic.cpp
 source          btrace_appresponse.cpp
+source          btrace_appstart.cpp
 source          btrace_screenupdate.cpp
 source          btrace_pubsub.cpp
 source          btrace_domainevent.cpp
--- a/libraries/iosrv/client/env.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/libraries/iosrv/client/env.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -141,33 +141,37 @@
 	WaitLC();
 
 	HBufC** valPtr = iVars.Find(aKey);
-	HBufC* currentValue = NULL;
-	if (valPtr) currentValue = *valPtr;
 
 	if (valPtr == NULL && iParentEnv)
 		{
 		// If we don't have it, and we have a parent env, we should pass the request through to it.
 		iParentEnv->SetL(aKey, aValue);
 		}
+	else if (valPtr)
+		{
+		// The key is present in the hash.
+		HBufC* currentValue = *valPtr;
+		if (currentValue && (currentValue->Des().MaxLength() >= aValue.Length()))
+			{
+			// If the existing HBufC is big enough, simply copy.
+			// Note, currentValue will be NULL if this is the first time a 'local' variable is being set.
+			currentValue->Des().Copy(aValue);
+			}
+		else
+			{
+			// Otherwise allocate a new HBufC.
+			*valPtr = aValue.AllocL();
+			delete currentValue;
+			}
+		}
 	else
 		{
-		if (currentValue)
-			{
-			// If we already have a value in the hash, just update it if possible
-			TPtr ptr = currentValue->Des();
-			if (ptr.MaxLength() >= aValue.Length())
-				{
-				ptr.Copy(aValue);
-				CleanupStack::PopAndDestroy(); // Release lock
-				return;
-				}
-			}
+		// New key.
+		HBufC* newVal = aValue.AllocLC();
+		iVars.InsertL(aKey, newVal);
+		CleanupStack::Pop(newVal);
 		}
 	
-	HBufC* newVal = aValue.AllocLC();
-	iVars.InsertL(aKey, newVal);
-	delete currentValue;
-	CleanupStack::Pop(newVal);
 	CleanupStack::PopAndDestroy(); // Release lock
 	}
 
--- a/libraries/iosrv/server/file.cpp	Tue Oct 26 15:36:30 2010 +0100
+++ b/libraries/iosrv/server/file.cpp	Thu Oct 28 16:54:54 2010 +0100
@@ -93,7 +93,7 @@
 		{
 		case RIoFile::ERead:
 			{
-			User::LeaveIfError(iFile.Open(aFs, aName, EFileRead));
+			User::LeaveIfError(iFile.Open(aFs, aName, EFileRead | EFileShareReadersOnly));
 			break;
 			}
 		case RIoFile::EOverwrite: