diff -r 000000000000 -r 7f656887cf89 core/src/pipe_line.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/src/pipe_line.cpp Wed Jun 23 15:52:26 2010 +0100 @@ -0,0 +1,652 @@ +// pipe_line.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 "error.h" +#include "pipe_line.h" +#include "console.h" +#include "command_factory.h" +#include "command_wrappers.h" + + +// +// RPipeSection. +// + +RPipeSection::TRedirection::TRedirection() + : iType(ENotRedirected), iFileName(NULL), iHandle(EUnknown) + { + } + +void RPipeSection::TRedirection::SetFileNameL(const TDesC& aCwd, const TDesC& aName) + { + if ((aName == _L("NUL")) || (aName == _L("/dev/null"))) + { + iType = ENull; + } + else + { + if (iFileName == NULL) + { + iFileName = new(ELeave) TFileName2(); + } + *iFileName = aName; + + if (iFileName->Length() > 0) + { + const TUint16 firstChar = (*iFileName)[0]; + const TUint16 lastChar = (*iFileName)[iFileName->Length() - 1]; + const TUint16 singleQuote = '\''; + const TUint16 doubleQuote = '"'; + if (((firstChar == singleQuote) && (lastChar == singleQuote)) || ((firstChar == doubleQuote) && (lastChar == doubleQuote))) + { + // The string is quoted - remove the quotes. + *iFileName = iFileName->Mid(1, iFileName->Length() - 2); + } + } + iFileName->MakeAbsoluteL(aCwd); + } + } + +RPipeSection::RPipeSection() + : iCommandArguments(5), iCommandArgumentsBuf(NULL) + { + } + +void RPipeSection::Close() + { + iCommandArguments.Close(); + delete iStdinRedirection.iFileName; + delete iStdoutRedirection.iFileName; + delete iStderrRedirection.iFileName; + delete iCommandArgumentsBuf; + iCommandArgumentsBuf = NULL; + } + +HBufC* RPipeSection::GetCommandArguments() const + { + if (iCommandArgumentsBuf) return iCommandArgumentsBuf; + + TInt length = 0; + const TInt numArgs = iCommandArguments.Count(); + for (TInt i = 0; i < numArgs; ++i) + { + length += iCommandArguments[i].Length() + 1; + } + iCommandArgumentsBuf = HBufC::New(length); + if (iCommandArgumentsBuf) + { + TPtr ptr(iCommandArgumentsBuf->Des()); + for (TInt i = 0; i < numArgs; ++i) + { + ptr.Append(iCommandArguments[i]); + if (i < (numArgs - 1)) + { + ptr.Append(_L(" ")); + } + } + } + return iCommandArgumentsBuf; + } + + + +// +// CPipeLine. +// + +CPipeLine* CPipeLine::NewL(RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, const RArray& aPipeSections, TBool aBackground, MPipeLineObserver* aObserver, TError& aErrorContext) + { + CPipeLine* self = NewLC(aIoSession, aStdin, aStdout, aStderr, aEnv, aFactory, aPipeSections, aBackground, aObserver, aErrorContext); + CleanupStack::Pop(self); + return self; + } + +CPipeLine* CPipeLine::NewLC(RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, const RArray& aPipeSections, TBool aBackground, MPipeLineObserver* aObserver, TError& aErrorContext) + { + CPipeLine* self = new(ELeave) CPipeLine(aIoSession, aStdin, aStdout, aStderr, aEnv, aFactory, aObserver); + CleanupStack::PushL(self); + self->ConstructL(aPipeSections, aBackground, aErrorContext); + return self; + } + +CPipeLine::~CPipeLine() + { + const TInt count = iCommands.Count(); + for (TInt i = 0; i < count; ++i) + { + iCommands[i].Close(); + } + iCommands.Close(); + delete iCompletionCallBack; + } + +void CPipeLine::Kill() + { + const TInt numCommands = iCommands.Count(); + for (TInt i = 0; i < numCommands; ++i) + { + MCommand* command = iCommands[i].iCommand; + if (command) + { + command->CmndKill(); + } + } + } + +TInt CPipeLine::Suspend() + { + TInt ret = KErrNone; + const TInt numCommands = iCommands.Count(); + for (TInt i = 0; i < numCommands; ++i) + { + MCommand* command = iCommands[i].iCommand; + if (command) + { + TInt err = command->CmndSuspend(); + if (err != KErrNone) + { + ret = err; + } + } + } + return ret; + } + +TInt CPipeLine::Resume() + { + TInt ret = KErrNone; + const TInt numCommands = iCommands.Count(); + for (TInt i = 0; i < numCommands; ++i) + { + MCommand* command = iCommands[i].iCommand; + if (command) + { + TInt err = command->CmndResume(); + if (err != KErrNone) + { + ret = err; + } + } + } + return ret; + } + +TInt CPipeLine::BringToForeground() + { + TInt ret = KErrNone; + const TInt numCommands = iCommands.Count(); + if (numCommands > 0) + { + MCommand* command = iCommands[0].iCommand; + if (command) + { + ret = command->CmndStdin().SetToForeground(); + } + else + { + ret = KErrDied; + } + if (ret == KErrNone) + { + for (TInt i = 0; i < numCommands; ++i) + { + MCommand* command = iCommands[i].iCommand; + if (command) + { + command->CmndForeground(); + } + } + } + } + else + { + ret = KErrNotFound; + } + return ret; + } + +void CPipeLine::SendToBackground() + { + const TInt numCommands = iCommands.Count(); + for (TInt i = 0; i < numCommands; ++i) + { + MCommand* command = iCommands[i].iCommand; + if (command) + { + command->CmndBackground(); + } + } + } + +TInt CPipeLine::Reattach(RIoEndPoint& aStdinEndPoint, RIoEndPoint& aStdoutEndPoint, RIoEndPoint& aStderrEndPoint) + { + // Go through the array of pipe-commands attaching them to the new end points where appropriate. + // Note, some commands may have already completed, in which case they can be ignored. + // Also, we have to be careful to only reattach I/O handles that weren't explicitly redirected + // by the user, or by virtue of their position in the pipe-line. + + TInt err = KErrNone; + const TInt numCommands = iCommands.Count(); + for (TInt i = 0; i < numCommands; ++i) + { + RPipedCommand& pipedCommand = iCommands[i]; + if (pipedCommand.iCommand) + { + if ((i == 0) && !pipedCommand.iStdinRedirected) + { + // The first command in the pipe-line whose stdin was not redirected. + err = pipedCommand.iCommand->CmndReattachStdin(aStdinEndPoint); + } + if ((err == KErrNone) && ((i < (numCommands - 1)) || (numCommands == 1)) && !pipedCommand.iStderrRedirected) + { + // A middle command in the pipe-line whose stderr has not been redirected. + err = pipedCommand.iCommand->CmndReattachStderr(aStderrEndPoint); + } + if ((err == KErrNone) && (i == (numCommands - 1)) && !pipedCommand.iStdoutRedirected) + { + // The last command in the pipe-line, whose stdout has not been redirected. + err = pipedCommand.iCommand->CmndReattachStdout(aStdoutEndPoint); + } + } + if (err) + { + break; + } + } + return err; + } + +TBool CPipeLine::IsDisownable() const + { + const TInt numCommands = iCommands.Count(); + for (TInt i = 0; i < numCommands; ++i) + { + const RPipedCommand& pipedCommand = iCommands[i]; + if (pipedCommand.iCommand && !pipedCommand.iCommand->CmndIsDisownable()) + { + return EFalse; + } + } + return ETrue; + } + +void CPipeLine::Disown() + { + const TInt numCommands = iCommands.Count(); + for (TInt i = 0; i < numCommands; ++i) + { + RPipedCommand& pipedCommand = iCommands[i]; + if (pipedCommand.iCommand) + { + pipedCommand.iCommand->CmndDisown(); + } + } + } + +CPipeLine::CPipeLine(RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MPipeLineObserver* aObserver) + : iIoSession(aIoSession), iStdin(aStdin), iStdout(aStdout), iStderr(aStderr), iEnv(aEnv), iFactory(aFactory), iObserver(aObserver), iCompletionError(aStderr, aEnv) + { + } + +void SetIoObjectName(RIoSession& aIoSession, TInt aObjHandle, TRefByValue aFmt, ...) + { + TOverflowTruncate overflow; + VA_LIST list; + VA_START(list, aFmt); + TFullName name; + name.AppendFormatList(aFmt, list, &overflow); + aIoSession.SetObjectName(aObjHandle, name); + } + +void CPipeLine::ConstructL(const RArray& aPipeSections, TBool aBackground, TError& aErrorContext) + { + // Run the pipe-line in the background even if we weren't explicitly asked to if fshell's + // STDIN read handle isn't in the foreground. This prevents the pipe-line from stealing the + // foreground from whatever has it (normally another instance of fshell running in interactive + // mode). + if (!aBackground && !iStdin.IsForeground()) + { + aBackground = ETrue; + } + + if (iObserver) + { + iCompletionCallBack = new(ELeave) CAsyncCallBack(TCallBack(CompletionCallBack, this), CActive::EPriorityStandard); + } + + TInt i; + const TInt numPipeSections = aPipeSections.Count(); + + // Construct the command objects and IO handles. + // Note, all the IO handles are duplicated from the shell's to ensure that the underlying console is correctly set. + // Later redirections may cause some handles to be re-attached to different end points. + for (i = 0; i < numPipeSections; ++i) + { + const RPipeSection& thisPipeSection = aPipeSections[i]; + User::LeaveIfError(iCommands.Append(RPipedCommand())); + RPipedCommand& pipedCommand = iCommands[i]; + pipedCommand.iCommandName = thisPipeSection.iCommandName.AllocL(); + HBufC* args = thisPipeSection.GetCommandArguments(); + User::LeaveIfNull(args); + pipedCommand.iCommand = iFactory.CreateCommandL(thisPipeSection.iCommandName, aErrorContext, *args); + User::LeaveIfError(pipedCommand.iCommand->CmndStdin().Create(iIoSession)); + User::LeaveIfError(pipedCommand.iCommand->CmndStdin().Duplicate(iStdin)); + SetIoObjectName(iIoSession, pipedCommand.iCommand->CmndStdin().SubSessionHandle(), _L("%S_stdin"), &thisPipeSection.iFullName); + User::LeaveIfError(pipedCommand.iCommand->CmndStdout().Create(iIoSession)); + User::LeaveIfError(pipedCommand.iCommand->CmndStdout().Duplicate(iStdout)); + SetIoObjectName(iIoSession, pipedCommand.iCommand->CmndStdout().SubSessionHandle(), _L("%S_stdout"), &thisPipeSection.iFullName); + User::LeaveIfError(pipedCommand.iCommand->CmndStderr().Create(iIoSession)); + User::LeaveIfError(pipedCommand.iCommand->CmndStderr().Duplicate(iStderr)); + SetIoObjectName(iIoSession, pipedCommand.iCommand->CmndStderr().SubSessionHandle(), _L("%S_stderr"), &thisPipeSection.iFullName); + } + + // Construct pipes. + RArray pipes; + CleanupClosePushL(pipes); + for (i = 1; i < numPipeSections; ++i) + { + RIoPipe pipe; + User::LeaveIfError(pipe.Create(iIoSession)); + CleanupClosePushL(pipe); + SetIoObjectName(iIoSession, pipe.SubSessionHandle(), _L("%S ==> %S_pipe"), &aPipeSections[i - 1].iFullName, &aPipeSections[i].iFullName); + User::LeaveIfError(pipes.Append(pipe)); + } + + RIoNull null; + User::LeaveIfError(null.Create(iIoSession)); + CleanupClosePushL(null); + SetIoObjectName(iIoSession, null.SubSessionHandle(), _L("null")); + + // Connect the pipe-line. + for (i = 0; i < numPipeSections; ++i) + { + const RPipeSection& thisPipeSection = aPipeSections[i]; + RPipedCommand& thisPipedCommand = iCommands[i]; + + switch (thisPipeSection.iStdinRedirection.iType) + { + case RPipeSection::TRedirection::ENotRedirected: + { + if (i == 0) + { + // This is the first pipe section. No wiring to do - already wired to the shell's stdin. + if (!aBackground) + { + User::LeaveIfError(thisPipedCommand.iCommand->CmndStdin().SetToForeground()); + } + } + else + { + if (aPipeSections[i - 1].iStdoutRedirection.iType == RPipeSection::TRedirection::ENotRedirected) + { + // Wire intermediate pipe sections input up to the previous one's output via a pipe. + User::LeaveIfError(pipes[i - 1].Attach(thisPipedCommand.iCommand->CmndStdin(), RIoEndPoint::EForeground)); + } + else + { + // The previous pipe section's output has been redirected, so attach this pipe section's input to null. + User::LeaveIfError(null.Attach(thisPipedCommand.iCommand->CmndStdin(), RIoEndPoint::EForeground)); + } + } + break; + } + case RPipeSection::TRedirection::EFile: + { + RIoFile file; + User::LeaveIfError(file.Create(iIoSession, *thisPipeSection.iStdinRedirection.iFileName, RIoFile::ERead)); + CleanupClosePushL(file); + SetIoObjectName(iIoSession, file.SubSessionHandle(), _L("file_%S"), thisPipeSection.iStdinRedirection.iFileName); + User::LeaveIfError(file.Attach(thisPipedCommand.iCommand->CmndStdin(), RIoEndPoint::EForeground)); + CleanupStack::PopAndDestroy(&file); + if (i > 0) + { + if (aPipeSections[i - 1].iStdoutRedirection.iType == RPipeSection::TRedirection::ENotRedirected) + { + // Re-wire the previous pipe section's output to null. + User::LeaveIfError(null.Attach(iCommands[i - 1].iCommand->CmndStdout())); + } + } + thisPipedCommand.iStdinRedirected = ETrue; + break; + } + case RPipeSection::TRedirection::ENull: + { + User::LeaveIfError(null.Attach(thisPipedCommand.iCommand->CmndStdin(), RIoEndPoint::EForeground)); + if (i > 0) + { + if (aPipeSections[i - 1].iStdoutRedirection.iType == RPipeSection::TRedirection::ENotRedirected) + { + // Re-wire the previous pipe section's output to null. + User::LeaveIfError(null.Attach(iCommands[i - 1].iCommand->CmndStdout())); + } + } + thisPipedCommand.iStdinRedirected = ETrue; + break; + } + case RPipeSection::TRedirection::EHandle: + case RPipeSection::TRedirection::EFileAppend: + default: + { + ASSERT(EFalse); + break; + } + } + + switch (thisPipeSection.iStdoutRedirection.iType) + { + case RPipeSection::TRedirection::ENotRedirected: + { + if (i < (numPipeSections - 1)) + { + // Attach this pipe section's output to the next one's input via a pipe. + User::LeaveIfError(pipes[i].Attach(thisPipedCommand.iCommand->CmndStdout())); + } + break; + } + case RPipeSection::TRedirection::EFile: + case RPipeSection::TRedirection::EFileAppend: + { + RIoFile file; + User::LeaveIfError(file.Create(iIoSession, *thisPipeSection.iStdoutRedirection.iFileName, (thisPipeSection.iStdoutRedirection.iType == RPipeSection::TRedirection::EFile) ? RIoFile::EOverwrite : RIoFile::EAppend)); + CleanupClosePushL(file); + SetIoObjectName(iIoSession, file.SubSessionHandle(), _L("file_%S"), thisPipeSection.iStdoutRedirection.iFileName); + User::LeaveIfError(file.Attach(thisPipedCommand.iCommand->CmndStdout())); + CleanupStack::PopAndDestroy(&file); + thisPipedCommand.iStdoutRedirected = ETrue; + break; + } + case RPipeSection::TRedirection::ENull: + { + User::LeaveIfError(null.Attach(thisPipedCommand.iCommand->CmndStdout())); + thisPipedCommand.iStdoutRedirected = ETrue; + break; + } + case RPipeSection::TRedirection::EHandle: + { + // Handle redirection of stdout to stderr after stderr has been wired up. + thisPipedCommand.iStdoutRedirected = ETrue; + break; + } + default: + { + ASSERT(EFalse); + break; + } + } + + switch (thisPipeSection.iStderrRedirection.iType) + { + case RPipeSection::TRedirection::ENotRedirected: + { + // Wire error output directly to the shell's stderr. + User::LeaveIfError(thisPipedCommand.iCommand->CmndStderr().Duplicate(iStderr)); + break; + } + case RPipeSection::TRedirection::EFile: + case RPipeSection::TRedirection::EFileAppend: + { + RIoFile file; + User::LeaveIfError(file.Create(iIoSession, *thisPipeSection.iStderrRedirection.iFileName, (thisPipeSection.iStderrRedirection.iType == RPipeSection::TRedirection::EFile) ? RIoFile::EOverwrite : RIoFile::EAppend)); + CleanupClosePushL(file); + SetIoObjectName(iIoSession, file.SubSessionHandle(), _L("file_%S"), thisPipeSection.iStderrRedirection.iFileName); + User::LeaveIfError(file.Attach(thisPipedCommand.iCommand->CmndStderr())); + CleanupStack::PopAndDestroy(&file); + thisPipedCommand.iStderrRedirected = ETrue; + break; + } + case RPipeSection::TRedirection::ENull: + { + User::LeaveIfError(null.Attach(thisPipedCommand.iCommand->CmndStderr())); + thisPipedCommand.iStderrRedirected = ETrue; + break; + } + case RPipeSection::TRedirection::EHandle: + { + ASSERT(thisPipeSection.iStderrRedirection.iHandle == RPipeSection::TRedirection::EStdout); + User::LeaveIfError(thisPipedCommand.iCommand->CmndStderr().Duplicate(thisPipedCommand.iCommand->CmndStdout())); + thisPipedCommand.iStderrRedirected = ETrue; + break; + } + default: + { + ASSERT(EFalse); + break; + } + } + + if (thisPipeSection.iStdoutRedirection.iType == RPipeSection::TRedirection::EHandle) + { + ASSERT(thisPipeSection.iStdoutRedirection.iHandle == RPipeSection::TRedirection::EStderr); + User::LeaveIfError(thisPipedCommand.iCommand->CmndStdout().Duplicate(thisPipedCommand.iCommand->CmndStderr())); + } + } + + // Pipe handles (if any) and null object (if needed) now held open by attached read a write handles. + CleanupStack::PopAndDestroy(&null); + if (numPipeSections > 1) + { + CleanupStack::PopAndDestroy(numPipeSections - 1); // The pipe handles. + } + CleanupStack::PopAndDestroy(&pipes); + + // Run the pipe-line. + for (i = 0; i < numPipeSections; ++i) + { + const RPipeSection& thisPipeSection = aPipeSections[i]; + RPipedCommand& thisPipedCommand = iCommands[i]; + HBufC* args = thisPipeSection.GetCommandArguments(); + TInt err = KErrNoMemory; + if (args) + { + err = thisPipedCommand.iCommand->CmndRun(*args, iEnv, *this, iIoSession); + if ((err == KErrNone) && thisPipedCommand.iCommand) + { + aBackground ? thisPipedCommand.iCommand->CmndBackground() : thisPipedCommand.iCommand->CmndForeground(); + } + } + if (err) + { + Kill(); + aErrorContext.Set(err, TError::EFailedToRunCommand, thisPipeSection.iFullName); + User::Leave(err); + } + } + } + +TInt CPipeLine::CompletionCallBack(TAny* aSelf) + { + CPipeLine* self = static_cast(aSelf); + self->iCompletionError.Set(self->iCommands[self->iCommands.Count() - 1].iCompletionError, TError::ECommandError); + self->iObserver->HandlePipeLineComplete(*self, self->iCompletionError); + return KErrNone; + } + +void CPipeLine::HandleCommandComplete(MCommand& aCommand, TInt aError) + { + TBool allNowComplete(ETrue); + const TInt numCommands = iCommands.Count(); + for (TInt i = 0; i < numCommands; ++i) + { + RPipedCommand& thisPipedCommand = iCommands[i]; + if (thisPipedCommand.iCommand == &aCommand) + { + if (aCommand.CmndExitType() == EExitPanic) + { + _LIT(KFormat, "*** PANIC ***\r\n\tCommand: \'%S\'\r\n\tCategory: \'%S\'\r\n\tReason: %d\r\n"); + TBuf<256> buf; + TOverflowTruncate overflow; + TExitCategoryName category(aCommand.CmndExitCategory()); + buf.AppendFormat(KFormat, &overflow, thisPipedCommand.iCommandName, &category, aError); + iStderr.Write(buf); + if (aError >= 0) + { + // Panicking with KERN-EXEC 0 shouldn't equate to a completionerror of KErrNone! + aError = KErrDied; + } + } + else if (aCommand.CmndExitType() == EExitTerminate) + { + _LIT(KFormat, "Command '%S' terminated with reason %d\r\n"); + TBuf<256> buf; + TOverflowTruncate overflow; + buf.AppendFormat(KFormat, &overflow, thisPipedCommand.iCommandName, aError); + iStderr.Write(buf); + if (aError >= 0) + { + // Terminate 0 shouldn't equate to a completionError of KErrNone + aError = KErrDied; + } + } + thisPipedCommand.iCommand->CmndRelease(); + thisPipedCommand.iCommand = NULL; + thisPipedCommand.iCompletionError = aError; + } + else if (thisPipedCommand.iCommand) + { + allNowComplete = EFalse; + } + } + + if (allNowComplete && iObserver) + { + iCompletionCallBack->CallBack(); + } + } + + +// +// CPipeLine::RPipedCommand. +// + +CPipeLine::RPipedCommand::RPipedCommand() + : iCommand(NULL), iCompletionError(KErrNone), iCommandName(NULL), iStdinRedirected(EFalse), iStdoutRedirected(EFalse), iStderrRedirected(EFalse) + { + } + +void CPipeLine::RPipedCommand::Close() + { + if (iCommand) + { + iCommand->CmndKill(); + if (iCommand) + { + iCommand->CmndRelease(); + iCommand = NULL; + } + } + if (iCommandName) + { + delete iCommandName; + } + } +