// 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 <fshell/ioutils.h>
#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<RPipeSection>& 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<RPipeSection>& 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<const TDesC> 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<RPipeSection>& 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<RIoPipe> 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<CPipeLine*>(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;
}
}