diff -r 000000000000 -r 7f656887cf89 core/src/fshell.cpp --- /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 +#include +#include +#include +#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 +#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 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 aFmt, ...) + { + VA_LIST list; + VA_START(list, aFmt); + FormatList(aFmt, list); + VA_END(list); + } + +void TError::FormatList(TRefByValue 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 aFmt, ...) + { + VA_LIST list; + VA_START(list, aFmt); + LogListIfRequired(aError, aReason, aFmt, list); + VA_END(list); + } + +void TError::LogListIfRequired(TInt aError, TReason aReason, TRefByValue 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 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 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(this))); + } + +void CShell::ReleaseJobsLock(TAny* aSelf) + { + static_cast(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& CShell::Jobs() const + { + ASSERT(const_cast(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 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* 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; + }