core/src/fshell.cpp
changeset 0 7f656887cf89
child 42 e81b4e28b3e2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/fshell.cpp	Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,853 @@
+// fshell.cpp
+// 
+// Copyright (c) 2006 - 2010 Accenture. All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+// 
+// Initial Contributors:
+// Accenture - Initial contribution
+//
+
+#include <e32cons.h>
+#include <f32file.h>
+#include <fshell/common.mmh>
+#include <fshell/ltkutils.h>
+#include "string_utils.h"
+#include "line_completer.h"
+#include "command_factory.h"
+#include "fshell.h"
+#include "license.h"
+#include "script_command.h"
+
+#ifdef FSHELL_MEMORY_ACCESS_SUPPORT
+#include <fshell/memoryaccess.h>
+#endif
+
+//
+// Constants.
+//
+
+const TInt KNoForegroundJob = -1;
+
+
+//
+// Globals.
+//
+
+CShell* gShell;
+TInt gExitValue(KErrNone);
+
+
+//
+// TError.
+//
+
+TError::TError(RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv)
+	: iStderr(&aStderr), iEnv(&aEnv), iScriptLineNumber(-1)
+	{
+	if (iEnv->IsDefined(KScriptLine))
+		{
+		iScriptFileName = iEnv->GetAsDes(KScriptPath);
+		iScriptFileName.Append(iEnv->GetAsDes(KScriptName));
+		iScriptLineNumber = iEnv->GetAsInt(KScriptLine);
+		}
+	}
+
+void TError::Set(const TError& aError)
+	{
+	if (!iSet)
+		{
+		iError = aError.iError;
+		iReason = aError.iReason;
+		iContext = aError.iContext;
+		iScriptFileName = aError.iScriptFileName;
+		iScriptLineNumber = aError.iScriptLineNumber;
+		iSet = ETrue;
+		}
+	}
+
+void TError::Set(TInt aError, TReason aReason)
+	{
+	if (!iSet)
+		{
+		iError = aError;
+		iReason = aReason;
+		iContext.Zero();
+		iSet = ETrue;
+		}
+	LogIfRequired(aError, aReason, KNullDesC);
+	}
+
+void TError::Set(TInt aError, TReason aReason, TRefByValue<const TDesC> aFmt, ...)
+	{
+	VA_LIST list;
+	VA_START(list, aFmt);
+	if (!iSet)
+		{
+		iError = aError;
+		iReason = aReason;
+		Format(aFmt, list);
+		iSet = ETrue;
+		}
+	LogListIfRequired(aError, aReason, aFmt, list);
+	VA_END(list);
+	}
+
+void TError::Report() const
+	{
+	ASSERT(iError < 0);
+
+	switch (iReason)
+		{
+		case ECommandError:
+			{
+			// Do nothing - assume an error message has been printed by CCommandBase.
+			break;
+			}
+		case EFailedToConstructCommand:
+			{
+			FormatError(_L("Unable to create command \"%S\""), &iContext);
+			break;
+			}
+		case EFailedToRunCommand:
+			{
+			FormatError(_L("Unable to run command \"%S\""), &iContext);
+			break;
+			}
+		case EFailedToCreatePipeLine:
+		case EFailedToSetChildErrorVar:
+		case EFailedToSetScriptLineVar:
+		case EUnknown:
+		default:
+			{
+			PrintError();
+			break;
+			}
+		}
+	}
+
+void TError::Format(TRefByValue<const TDesC> aFmt, ...)
+	{
+	VA_LIST list;
+	VA_START(list, aFmt);
+	FormatList(aFmt, list);
+	VA_END(list);
+	}
+
+void TError::FormatList(TRefByValue<const TDesC> aFmt, VA_LIST& aList)
+	{
+	iContext.Zero();
+	TOverflowTruncate overflow;
+	iContext.AppendFormatList(aFmt, aList, &overflow);
+	}
+
+TInt TError::Error() const
+	{
+	return iError;
+	}
+
+TError::TReason TError::Reason() const
+	{
+	return iReason;
+	}
+
+const TDesC* TError::Context() const
+	{
+	return &iContext;
+	}
+
+const TDesC& TError::ScriptFileName() const
+	{
+	return iScriptFileName;
+	}
+
+TInt TError::ScriptLineNumber() const
+	{
+	return iScriptLineNumber;
+	}
+
+void TError::LogIfRequired(TInt aError, TReason aReason, TRefByValue<const TDesC> aFmt, ...)
+	{
+	VA_LIST list;
+	VA_START(list, aFmt);
+	LogListIfRequired(aError, aReason, aFmt, list);
+	VA_END(list);
+	}
+
+void TError::LogListIfRequired(TInt aError, TReason aReason, TRefByValue<const TDesC> aFmt, VA_LIST& aList)
+	{
+	if (Verbose())
+		{
+		IoUtils::TOverflowTruncate overflow;
+		iScratch.Zero();
+		iScratch.AppendFormat(_L("Error: %S (%d), Reason: %S (%d), Context: \""), &overflow, Stringify::Error(aError), aError, StringifyReason(aReason), aReason);
+		iScratch.AppendFormatList(aFmt, aList, &overflow);
+		iScratch.AppendFormat(_L("\"\r\n"), &overflow);
+		iStderr->Write(iScratch);
+		}
+	}
+
+#define CASE_RETURN_LIT(XXX) case XXX: { _LIT(_KLit, #XXX); return &_KLit; }
+#define DEFAULT_RETURN_LIT(XXX) default: { _LIT(_KLit, XXX); return &_KLit; }
+
+const TDesC* TError::StringifyReason(TReason aReason) const
+	{
+	switch (aReason)
+		{
+		CASE_RETURN_LIT(EUnknown);
+		CASE_RETURN_LIT(EFailedToCreatePipeLine);
+		CASE_RETURN_LIT(EFailedToConstructCommand);
+		CASE_RETURN_LIT(EFailedToRunCommand);
+		CASE_RETURN_LIT(EFailedToSetChildErrorVar);
+		CASE_RETURN_LIT(EFailedToSetScriptLineVar);
+		CASE_RETURN_LIT(ECommandError);
+		DEFAULT_RETURN_LIT("*** REASON UNKNOWN ***");
+		}
+	}
+
+TBool TError::Verbose() const
+	{
+	_LIT(KVerbose, "FSHELL_VERBOSE");
+	return (iEnv->IsDefined(KVerbose));
+	}
+
+void TError::NewLine() const
+	{
+	if (iStderr->AttachedToConsole())
+		{
+		RIoConsoleWriteHandle consoleWriteHandle;
+		consoleWriteHandle = *iStderr;
+		TPoint cursorPos;
+		consoleWriteHandle.GetCursorPos(cursorPos);
+		if (cursorPos.iX != 0)
+			{
+			iStderr->Write(_L("\r\n"));
+			}
+		}
+	}
+
+void TError::PrintError() const
+	{
+	NewLine();
+	IoUtils::TOverflowTruncate overflow;
+	iScratch.Zero();
+	iScratch.AppendFormat(_L("Error: %S (%d)\r\n"), &overflow, Stringify::Error(iError), iError);
+	iStderr->Write(iScratch);
+	}
+
+void TError::FormatError(TRefByValue<const TDesC> aFmt, ...) const
+	{
+	NewLine();
+	VA_LIST list;
+	VA_START(list, aFmt);
+	IoUtils::TOverflowTruncate overflow;
+	iScratch = _L("Error: ");
+	iScratch.AppendFormatList(aFmt, list, &overflow);
+	VA_END(list);
+	iScratch.AppendFormat(_L(" : %S (%d)\r\n"), Stringify::Error(iError), iError);
+	iStderr->Write(iScratch);
+	}
+
+//
+// CShell.
+//
+
+
+void Log(TRefByValue<const TDesC8> aFmt, ...)
+	{
+	RFs fs;
+	TInt err = fs.Connect();
+	if (err == KErrNone)
+		{
+		RFile file;
+		err = file.Open(fs, _L("c:\\fshell.txt"), EFileWrite);
+		if (err == KErrNotFound)
+			{
+			err = file.Replace(fs, _L("c:\\fshell.txt"), EFileWrite);
+			}
+		if (err == KErrNone)
+			{
+			TOverflowTruncate8 overflow;
+			VA_LIST list;
+			VA_START(list, aFmt);
+			TBuf8<0x100> buf;
+			buf.AppendFormatList(aFmt, list, &overflow);
+			VA_END(list);
+			buf.Append(_L("\r\n"));
+			TInt pos(0);
+			file.Seek(ESeekEnd, pos);
+			file.Write(buf);
+			file.Close();
+			}
+		fs.Close();
+		}
+	}
+
+
+CShell* CShell::NewLC()
+	{
+	CShell* self = new(ELeave) CShell();
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	return self;
+	}
+
+CShell::~CShell()
+	{
+	iJobsLock.Close();
+	iJobs.ResetAndDestroy();
+	delete iScriptArgs;
+	delete iLineEditor;
+	delete iLineCompleter;
+	delete iConsole;
+	delete iCommandFactory;
+	delete iScriptData;
+	delete iOneLiner;
+	delete iParser;
+	}
+
+CCommandFactory& CShell::CommandFactory()
+	{
+	return *iCommandFactory;
+	}
+
+void CShell::ClaimJobsLockLC()
+	{
+	iJobsLock.Wait();
+	CleanupStack::PushL(TCleanupItem(ReleaseJobsLock, const_cast<CShell*>(this)));
+	}
+
+void CShell::ReleaseJobsLock(TAny* aSelf)
+	{
+	static_cast<CShell*>(aSelf)->iJobsLock.Signal();
+	}
+
+TInt CShell::BringJobToForeground(TInt aJobId)
+	{
+	TInt ret = KErrNotFound;
+	CJob* job = Job(aJobId);
+	if (job != NULL)
+		{
+		Printf(_L("[%d] %S\r\n"), aJobId, job->Name());
+		job->Resume();
+		ret = job->BringToForeground();
+		if (ret == KErrNone)
+			{
+			if (iForegroundJobId != KNoForegroundJob)
+				{
+				ForegroundJob().SendToBackground();
+				}
+			iForegroundJobId = aJobId;
+			}
+		}
+	if (ret)
+		{
+		PrintError(ret, _L("Unable to bring job [%d] to foreground"), aJobId);
+		}
+	return ret;
+	}
+
+void CShell::SendJobToBackground(TInt aJobId)
+	{
+	CJob* job = Job(aJobId);
+	if (job != NULL)
+		{
+		job->SendToBackground();
+		job->Resume();
+		Printf(_L("[%d] %S\r\n"), aJobId, job->Name());
+		if (aJobId == iForegroundJobId)
+			{
+			iForegroundJobId = KNoForegroundJob;
+			}
+		}
+	else
+		{
+		PrintError(KErrNotFound, _L("Unable to send job [%d] to background"), aJobId);
+		}
+	}
+
+const RPointerArray<CJob>& CShell::Jobs() const
+	{
+	ASSERT(const_cast<RMutex&>(iJobsLock).IsHeld()); // IsHeld should be const but isn't
+	return iJobs;
+	}
+
+CShell::CShell()
+	: CCommandBase(EManualComplete | ENotifyStdinChanges | ESharableIoSession), iForegroundJobId(KNoForegroundJob), iWriterAdaptor(Stdout())
+	{
+	gShell = this;
+	}
+
+void CShell::ConstructL()
+	{
+	BaseConstructL();
+	User::LeaveIfError(iJobsLock.CreateLocal());
+#ifdef FSHELL_MEMORY_ACCESS_SUPPORT
+	// We call LoadDriver here so that memoryaccess gets a chance to start the thread creator tracker as early as possible
+	// Possibly this isn't the best place to put it...
+	RMemoryAccess::LoadDriver();
+#endif
+	}
+
+void CShell::PrintPrompt()
+	{
+	const TDesC& pwd = Env().Pwd();
+	// Truncate the prompt if it will take up more than half the screen, or half of KMaxLineLength
+	TInt maxLen = KMaxLineLength / 2;
+	TSize screenSize;
+	TInt err = Stdout().GetScreenSize(screenSize);
+	if (err == KErrNone && screenSize.iWidth > 3) maxLen = Min(maxLen, screenSize.iWidth / 2); // Nullcons has a width of 1, apparently
+	TBuf<KMaxLineLength> prompt;
+	if (pwd.Length() > maxLen)
+		{
+		TPtrC left(pwd.Left(3)); // For the driveletter:\ bit
+		TPtrC right = pwd.Right(maxLen - 3);
+		prompt.Format(_L("%S...%S>"), &left, &right);
+		}
+	else
+		{
+		prompt.Format(_L("%S>"), &pwd);
+		}
+	iLineEditor->Start(prompt);
+	iLineEditor->ReinstatePromptAndUserInput();
+	}
+
+void CShell::PrintErr(TInt aError)
+	{
+	TPoint cursorPos;
+	Stdout().GetCursorPos(cursorPos);
+	if (cursorPos.iX != 0)
+		{
+		Stderr().Write(_L("\r\n"));
+		}
+	TBuf<128> buf;
+	IoUtils::TOverflowTruncate overflow;
+	buf.AppendFormat(_L("Error: %S (%d)\r\n"), &overflow, Stringify::Error(aError), aError);
+	Stderr().Write(buf);
+	}
+
+void CShell::ProcessLineL(const TDesC& aLine)
+	{
+	TInt jobId = NextJobId();
+	CJob* job = CJob::NewL(jobId, aLine, IoSession(), Stdin(), Stdout(), Stderr(), Env(), *iCommandFactory, this);
+	CleanupStack::PushL(job);
+	ClaimJobsLockLC();
+	User::LeaveIfError(iJobs.Append(job));
+	CleanupStack::PopAndDestroy(); // Jobs lock
+	CleanupStack::Pop(job);
+	iNextJobId = jobId;
+	TBool isForeground(EFalse);
+	job->Start(isForeground);
+	if (isForeground)
+		{
+		iForegroundJobId = jobId;
+		}
+	else
+		{
+		PrintPrompt();
+		}
+	}
+
+const TDesC& CShell::Name() const
+	{
+	_LIT(KName, "fshell");
+	return KName;
+	}
+
+void CShell::DoRunL()
+	{
+#if defined (__WINS__) && !defined (EKA2)
+	RThread().SetPriority(::EPriorityAbsoluteHigh);
+#else
+	RProcess().SetPriority(::EPriorityHigh);
+#endif
+	User::LeaveIfError(iFs.Connect());
+	User::LeaveIfError(iFs.ShareAuto()); // Necessary because this handle is used by CCommandFactory and that can be used from the context of other threads (e.g. the "debug" command).
+	iCommandFactory = CCommandFactory::NewL(iFs);
+
+	if (iOneLiner)
+		{
+		// One line script mode.
+		iParser = CParser::NewL(CParser::ENormal, *iOneLiner, IoSession(), Stdin(), Stdout(), Stderr(), Env(), *iCommandFactory, this);
+		RProcess::Rendezvous(KErrNone);
+		iParser->Start();
+		}
+	else if (iScriptName.Length() > 0)
+		{
+		TIoHandleSet ioHandles(IoSession(), Stdin(), Stdout(), Stderr());
+		TBool helpPrinted;
+		iScriptData = ReadScriptL(iScriptName, iScriptArgs, Env(), FsL(), ioHandles, helpPrinted);
+		if (helpPrinted)
+			{
+			Complete();
+			}
+		else
+			{
+			TUint mode = CParser::EExportLineNumbers;
+			if (iKeepGoing)
+				{
+				mode |= CParser::EKeepGoing;
+				}
+			iParser = CParser::NewL(mode, *iScriptData, IoSession(), Stdin(), Stdout(), Stderr(), Env(), *iCommandFactory, this);
+			RProcess::Rendezvous(KErrNone);
+			iParser->Start();
+			}
+		}
+	else
+		{
+		// Interactive mode.
+
+#ifdef FSHELL_CORE_SUPPORT_LICENSE
+		if (TLicense::CheckL(Stdin(), Stdout()) != TLicense::EValid)
+			{
+			Complete(KErrPermissionDenied);
+			return;
+			}
+#endif
+		
+		User::LeaveIfError(Stdin().CaptureKey(CTRL('c'), 0, 0));
+		User::LeaveIfError(Stdin().CaptureKey(CTRL('z'), 0, 0));
+		iConsole = CConsoleReader::NewL(Stdin(), *this);
+		iLineCompleter = CLineCompleter::NewL(iFs, *iCommandFactory, Env());
+		_LIT(KFshellHistory, "c:\\system\\console\\shell\\history");
+		iLineEditor = CLineEditor::NewL(iFs, iWriterAdaptor, *this, *iLineCompleter, KFshellHistory);
+		RProcess::Rendezvous(KErrNone);
+		PrintPrompt();
+		}
+	}
+
+void CShell::OptionsL(RCommandOptionList& aOptions)
+	{
+	_LIT(KOptExec, "exec");
+	aOptions.AppendStringL(iOneLiner, KOptExec);
+	_LIT(KOptKeepGoing, "keep-going");
+	aOptions.AppendBoolL(iKeepGoing, KOptKeepGoing);
+	}
+
+void CShell::ArgumentsL(RCommandArgumentList& aArguments)
+	{
+	_LIT(KArgScriptName, "script_name");
+	aArguments.AppendFileNameL(iScriptName, KArgScriptName);
+
+	_LIT(KArgArgs, "script_args");
+	aArguments.AppendStringL(iScriptArgs, KArgArgs);
+	}
+	
+void CShell::StdinChange(TUint)
+	{
+	if (iLineEditor && !iIgnoreNextStdinChange)
+		{
+		iLineEditor->Redraw();
+		}
+	iIgnoreNextStdinChange = EFalse;
+	}
+
+TInt CShell::NextJobId()
+	{
+	TInt next = iNextJobId;
+	++next;
+	if (next == KNoForegroundJob)
+		{
+		next = 0;
+		}
+	return next;
+	}
+
+CJob& CShell::ForegroundJob()
+	{
+	ASSERT(iForegroundJobId != KNoForegroundJob);
+	CJob* job = Job(iForegroundJobId);
+	ASSERT(job != NULL);
+	return *job;
+	}
+
+CJob* CShell::Job(TInt aId)
+	{
+	ASSERT(iJobsLock.IsHeld());
+	const TInt numJobs = iJobs.Count();
+	CJob* job = NULL;
+	for (TInt i = 0; i < numJobs; ++i)
+		{
+		if (iJobs[i]->Id() == aId)
+			{
+			job = iJobs[i];
+			break;
+			}
+		}
+	return job;
+	}
+
+TInt CShell::DisownJob(TInt aId)
+	{
+	ASSERT(iJobsLock.IsHeld());
+	const TInt numJobs = iJobs.Count();
+	for (TInt i = 0; i < numJobs; ++i)
+		{
+		CJob* job = iJobs[i];
+		if (job->Id() == aId)
+			{
+			if (job->IsDisownable())
+				{
+				job->Disown();
+				delete job;
+				iJobs.Remove(i);
+				return KErrNone;
+				}
+			else
+				{
+				return KErrAccessDenied;
+				}
+			}
+		}
+	return KErrNotFound;
+	}
+
+HBufC* CShell::ReadScriptL(const TDesC& aScriptName, const TDesC* aArguments, IoUtils::CEnvironment& aEnv, RFs& aFs, TIoHandleSet& aIoHandles, TBool& aHelpPrinted, RPointerArray<HBufC>* aAdditionalPrefixArguments)
+	{
+	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)
+	_LIT(KScriptDir, "y:\\system\\console\\scripts\\");
+	_LIT(KScriptSuffix, ".script");
+	TInt scriptNameLen = scriptName.Length();
+	if (!scriptName.Exists(aFs) && (scriptName.DriveAndPath().CompareF(aEnv.Pwd()) == 0))
+		{
+		TFindFile finder(aFs);
+		TInt found = finder.FindByDir(scriptName.NameAndExt(), KScriptDir);
+		if (found == KErrNotFound && scriptName.Right(KScriptSuffix().Length()).CompareF(KScriptSuffix) != 0)
+			{
+			// If it doesn't already end in .script, try that
+			scriptName.Append(KScriptSuffix);
+			found = finder.FindByDir(scriptName.NameAndExt(), KScriptDir);
+			}
+		if (found == KErrNone)
+			{
+			scriptName.Copy(finder.File());
+			}
+		else
+			{
+			// Put scriptname back the way it was
+			scriptName.SetLength(scriptNameLen);
+			} 
+		}
+
+	RFile scriptFile;
+	TInt err;
+	TInt retries = 5;
+	do
+		{
+		err = scriptFile.Open(aFs, scriptName, EFileRead | EFileShareReadersOnly);
+		if ((err == KErrNone) || (err != KErrInUse))
+			{
+			break;
+			}
+		User::After(500000);
+		--retries;
+		}
+		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);
+	aEnv.SetLocalL(KScriptName);
+	aEnv.SetLocalL(KScriptPath);
+	aEnv.SetLocalL(KScriptLine);
+	aEnv.SetLocalL(_L("0"));
+	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())
+		{
+		// Need to display help
+		if (scriptCommand->ShouldDisplayHelp())
+			{
+			err = KErrNone;
+			}
+		else if (err == KErrArgument)
+			{
+			scriptCommand->PrintError(KErrArgument, _L("Couldn't parse command line"));
+			}
+		scriptCommand->DisplayScriptHelpL();
+		aHelpPrinted = ETrue;
+		}
+	else
+		{
+		aHelpPrinted = EFalse;
+		}
+	User::LeaveIfError(err); // Propagate error
+	CleanupStack::PopAndDestroy(scriptCommand);
+	CleanupStack::Pop(decodedScriptData);
+	return decodedScriptData;
+	}
+
+void CShell::SetToForeground()
+	{
+	Stdin().SetToForeground();
+	iIgnoreNextStdinChange = ETrue;
+	}
+
+void CShell::CoHandleKey(TUint aKeyCode, TUint aModifiers)
+	{
+	switch (aKeyCode)
+		{
+		case CTRL('c'):
+			{
+			if (iForegroundJobId == KNoForegroundJob)
+				{
+				PrintPrompt();
+				}
+			else
+				{
+				ClaimJobsLockLC();
+				ForegroundJob().Kill();
+				CleanupStack::PopAndDestroy(); // Jobs lock.
+				}
+			SetToForeground();
+			break;
+			}
+		case CTRL('z'):
+			{
+			if (iForegroundJobId != KNoForegroundJob)
+				{
+				ClaimJobsLockLC();
+				CJob& job = ForegroundJob();
+				job.Suspend();
+				CleanupStack::PopAndDestroy(); // Jobs lock.
+				SetToForeground();
+				Printf(_L("[%d] %S\t\t%S\r\n"), iForegroundJobId, ShStringify::JobStatus(job.Status()), job.Name());
+				PrintPrompt();
+				iForegroundJobId = KNoForegroundJob;
+				}
+			break;
+			}
+		default:
+			{
+			// Only handle the key if there's no foreground job. Note, it's possible for the foreground job
+			// to still be pending, but have implicilty given keyboard focus back to fshell. For example:
+			//
+			// sleep& && echo hello
+			//
+			// The above job will not complete until the background sleep command is killed. During this period
+			// fshell has implicitly taken keyboard focus because the hello command has released it. However
+			// fshell's line editor hasn't been started and so isn't ready to handle input. Perhaps the line
+			// editor should implicitly start in this situation? For the time being playing safe by ignoring
+			// the keyboard input if there's a foreground job in progress.
+			if (iForegroundJobId == KNoForegroundJob)
+				{
+				iLineEditor->HandleKey(aKeyCode, aModifiers);
+				}
+			}
+		}
+	}
+
+void CShell::CoHandleError(TInt aError)
+	{
+	// Treat console errors as fatal (they're likely to be due to a remote console being closed).
+	if (!IsComplete()) Complete(aError);
+	}
+
+void CShell::LeoHandleLine(const TDesC& aLine)
+	{
+	if (aLine.Length() > 0)
+		{
+		TRAPD(err, ProcessLineL(aLine));
+		if (err)
+			{
+			PrintErr(err);
+			PrintPrompt();
+			}
+		}
+	else
+		{
+		PrintPrompt();
+		}
+	}
+
+void CShell::HandleJobComplete(CJob& aJob, const TError& aError)
+	{
+	if (aError.Error() < 0)
+		{
+		aError.Report();
+		}
+
+	if (aJob.Id() == iForegroundJobId)
+		{
+		iForegroundJobId = KNoForegroundJob;
+		PrintPrompt();
+		}
+
+#ifdef _DEBUG
+	TInt pos = iJobs.Find(&aJob);
+	ASSERT(pos >= 0);
+#else
+	TInt pos = iJobs.Find(&aJob);
+#endif
+
+	delete &aJob;
+	iJobs.Remove(pos);
+	}
+
+void CShell::HandleParserComplete(CParser&, const TError& aError)
+	{
+	if ((aError.Error() < 0))
+		{
+		aError.Report();
+		if (!iOneLiner)
+			{
+			PrintError(aError.Error(), _L("Aborted \"%S\" at line %d"), &aError.ScriptFileName(), aError.ScriptLineNumber());
+			}
+		SetErrorReported(ETrue);
+		}
+
+	gExitValue = aError.Error();
+	Complete(aError.Error());
+	}
+
+
+//
+// Main.
+//
+
+void MainL()
+	{
+	_LIT(KShellName, "fshell");
+#ifdef EKA2
+	User::RenameThread(KShellName);
+#else
+	RThread().Rename(KShellName);
+#endif
+	CActiveScheduler* scheduler = new(ELeave) CActiveScheduler;
+	CleanupStack::PushL(scheduler);
+	CActiveScheduler::Install(scheduler);
+	IoUtils::CCommandBase* command = CShell::NewLC();
+	command->RunCommandL();
+	CleanupStack::PopAndDestroy(2, scheduler);
+	if (gExitValue)
+		{
+		User::Leave(gExitValue);
+		}
+	}
+
+GLDEF_C TInt E32Main()
+	{
+	__UHEAP_MARK;
+	TInt err = KErrNoMemory;
+	CTrapCleanup* cleanup = CTrapCleanup::New();
+	if (cleanup)
+		{
+		TRAP(err, MainL());
+		delete cleanup;
+		}
+	__UHEAP_MARKEND;
+	return err;
+	}