core/src/fshell.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Tue, 07 Dec 2010 17:29:09 +0000
changeset 90 ceac7084e2e5
parent 78 b3ffff030d5c
permissions -rw-r--r--
Implemented RObjectIx-based memoryaccess APIs. Upshot is that objinfo now works again on platforms that define FSHELL_NO_DOBJECTIX_SUPPORT.

// 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 EPipelineCompletionError:
		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(EPipelineCompletionError);
		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()
	{
	iScriptHandle.Close();
	iJobsLock.Close();
	iJobs.ResetAndDestroy();
	delete iScriptArgs;
	delete iLineEditor;
	delete iLineCompleter;
	delete iConsole;
	delete iCommandFactory;
	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

#ifdef FSHELL_CAP_ALL // If we don't have all caps we can't hack around with sys\bin directory
	// See if we were built/installed with FSHELL_REPLACE_ECONS and if so, do just that.
	// A bit inefficient we do it every time, but can't see a better way
	// The benefit of doing it like this is that it doesn't require us to remove other occurences of econs.dll (like econseik.dll) from the rombuild
	RLibrary iocons;
	TInt err = iocons.Load(_L("iocons.dll"));
	if (err == KErrNone)
		{
		// We don't include iocons.dll unless FSHELL_REPLACE_ECONS was specified at buildrom time
		CleanupClosePushL(iocons);
		RFs fs;
		CleanupClosePushL(fs);
		User::LeaveIfError(fs.Connect());
		_LIT(KPath, "%c:\\sys\\bin\\econs.dll");
		TBuf<32> econsPath;
		econsPath.Format(KPath, LtkUtils::GetSystemDrive());
		TEntry ioconsEntry;
		err = fs.Entry(iocons.FileName(), ioconsEntry);
		if (err == KErrNone)
			{
			TEntry econsEntry;
			if (fs.Entry(econsPath, econsEntry) == KErrNone && econsEntry.iSize == ioconsEntry.iSize)
				{
				// Then we've already copied iocons.dll to econs.dll, don't do it again
				}
			else
				{
				CFileMan* fm = CFileMan::NewL(fs);
				fm->Copy(iocons.FileName(), econsPath, CFileMan::ERecurse | CFileMan::EOverWrite);
				// Ignore the error - if it fails it shouldn't prevent fshell starting up
				delete fm;
				}
			}
		CleanupStack::PopAndDestroy(2, &iocons); // fs, iocons
		}
#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;
		iScriptHandle = OpenScriptL(iScriptName, iScriptArgs, Env(), FsL(), ioHandles, helpPrinted);
		if (helpPrinted)
			{
			Complete();
			}
		else
			{
			TUint mode = CParser::EExportLineNumbers;
			if (iKeepGoing)
				{
				mode |= CParser::EKeepGoing;
				}
			iParser = CParser::NewL(mode, iScriptHandle, 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 aChange)
	{
	if (iLineEditor && !iIgnoreNextStdinChange && (aChange & RIoReadHandle::EGainedForeground)) // We don't care if the console has just resized
		{
		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;
	}

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)
	_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);
			} 
		}

	RIoFile scriptFile;
	TInt err;
	TInt retries = 5;
	do
		{
		err = scriptFile.Create(aIoHandles.IoSession(), scriptName, RIoFile::ERead);
		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);
	scriptFile.AttachL(readHandle, RIoEndPoint::EForeground);
	CleanupStack::PopAndDestroy(&scriptFile);

	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(&readHandle);
	return readHandle;
	}

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;
	}