Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase.
Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.
// sudo.cpp
//
// Copyright (c) 2008 - 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
//
#define __INCLUDE_CAPABILITY_NAMES__
#include <fshell/ioutils.h>
#include <f32image.h>
#include <fshell/memoryaccesscmd.h>
#include <e32rom.h>
using namespace IoUtils;
class CCmdSudo : public CMemoryAccessCommandBase, public MCommandExtensionsV2
{
public:
static CCommandBase* NewLC();
~CCmdSudo();
private:
CCmdSudo();
static void DeleteModifiedBinary(TAny* aSelf);
void DeleteModifiedBinary();
TBool FileExists(const TDesC& aFileName);
void FixupExeInMemoryL(RProcess& aProcess);
void CalculateCaps();
void FindExeL();
void CopyExeLC();
void FixupExeL();
void RunExeL();
void FixupCoreExeLC();
private: // From CCommandBase.
virtual const TDesC& Name() const;
virtual void DoRunL();
virtual void ArgumentsL(RCommandArgumentList& aArguments);
virtual void OptionsL(RCommandOptionList& aOptions);
virtual void RunL();
private: // From MCommandExtensionsV2
virtual void CtrlCPressed();
private:
HBufC* iCmd;
HBufC* iArgs;
RPointerArray<HBufC> iAdd;
RPointerArray<HBufC> iRemove;
TCapabilitySet iCapsToAdd;
TCapabilitySet iCapsToRemove;
TUint iSid;
TUint iVid;
TUint iHeapMin;
TUint iHeapMax;
TUint iStackSize;
TInt iProcessPriority;
TBool iKeep;
TBool iWait;
TBool iChangeBinaryOnDisk;
TBool iFileIsInCore;
TFileName iPath;
TFileName iNewPath;
CArrayPtrFlat<HBufC>* iPathsToCleanup;
// following 2 are temporaries needed during CopyExeLC
TFileName iTempSrc;
TFileName iTempDest;
RChildProcess iChildProcess;
};
CCommandBase* CCmdSudo::NewLC()
{
CCmdSudo* self = new(ELeave) CCmdSudo();
CleanupStack::PushL(self);
self->BaseConstructL();
return self;
}
CCmdSudo::~CCmdSudo()
{
delete iCmd;
delete iArgs;
iAdd.ResetAndDestroy();
iRemove.ResetAndDestroy();
if (iPathsToCleanup)
{
for (TInt i = 0; i < iPathsToCleanup->Count(); i++)
{
HBufC* file = (*iPathsToCleanup)[i];
//Printf(_L("Deleting file %S (not really)\n"), file);
Fs().Delete(*file);
delete file;
}
}
delete iPathsToCleanup;
iChildProcess.Close();
}
CCmdSudo::CCmdSudo()
: CMemoryAccessCommandBase(EManualComplete | ECaptureCtrlC)
{
SetExtension(this);
}
TCapability CapabilityFromString(const TDesC& aName)
{
TBuf<32> cap;
for (TInt i = 0; i < ECapability_Limit; i++)
{
cap.Copy(TPtrC8((TUint8*)CapabilityNames[i]));
if (aName.CompareF(cap) == 0)
{
return (TCapability)i;
}
}
return ECapability_None;
}
void CCmdSudo::DoRunL()
{
iPathsToCleanup = new(ELeave) CArrayPtrFlat<HBufC>(8);
CalculateCaps();
FindExeL();
if (iChangeBinaryOnDisk)
{
#ifndef __WINS__
// Don't try and use IsFileInRom on WINSCW, the emulator makes a mess of it and anyway, shadowing isn't supported so it being in the core is irrelevant
iFileIsInCore = FsL().IsFileInRom(iPath) != NULL;
#endif
if (iFileIsInCore)
{
// Things in core have to be handled differently
iNewPath = iPath; // We don't call CopyExeLC so something has to set this
FixupCoreExeLC();
}
else
{
CopyExeLC();
FixupExeL();
}
}
else
{
if (iHeapMin || iHeapMax || iStackSize) LeaveIfErr(KErrArgument, _L("Heap or stack sizes cannot be modified unless you specify --disk"));
if (iKeep) LeaveIfErr(KErrArgument, _L("--keep option makes no sense if --disk isn't specified."));
CleanupStack::PushL((CBase*)NULL); // RunExeL expects a cleanup item for DeleteModifiedBinary, which isn't necessary when --disk isn't specified
// Everything else is taken care of from RunExeL
}
RunExeL();
}
void CCmdSudo::FindExeL()
{
if (!iChangeBinaryOnDisk)
{
// If we're not changing on disk, it's fine to just use the path as-is and let RProcess::Create sort it out
iPath = *iCmd;
iNewPath = iPath;
return;
}
#ifdef __WINS__
PrintWarning(_L("On WINS, exe-name must be a complete path to a E32 exe"));
iPath = *iCmd;
#else
// We can't reliably use RProcess::Create then FileName because the reason we're calling sudo may be because
// the capabilities don't allow it to load.
_LIT(KExe, ".exe");
_LIT(KSysBin, "\\sys\\bin\\");
iPath = *iCmd;
if (iPath.Right(KExe().Length()).CompareF(KExe) != 0)
{
iPath.Append(KExe);
}
TParsePtrC parse(iPath);
if (!parse.PathPresent())
{
TFindFile find(FsL());
TInt found = find.FindByDir(iPath, KSysBin);
LeaveIfErr(found, _L("Couldn't locate file %S"), &iPath);
iPath = find.File();
}
#endif
}
void CCmdSudo::CopyExeLC()
{
iNewPath = iPath;
iNewPath[0] = 'c'; // Has to be on C otherwise the loader performs hash checks
iNewPath.Append(_L(".sudoed.exe"));
CleanupStack::PushL(TCleanupItem(&CCmdSudo::DeleteModifiedBinary, this));
TInt err = FsL().MkDirAll(iNewPath); // In case C:\sys\bin doesn't exist yet
if (err && err != KErrAlreadyExists)
{
LeaveIfErr(err, _L("Couldn't create C:\\sys\\bin"));
}
CFileMan* fm = CFileMan::NewL(Fs());
CleanupStack::PushL(fm);
LeaveIfErr(fm->Copy(iPath, iNewPath), _L("Couldn't copy file from %S to %S"), &iPath, &iNewPath);
// Clear the read-only bit in the case where we've copied from Z drive
LeaveIfErr(Fs().SetAtt(iNewPath, 0, KEntryAttReadOnly), _L("Couldn't unset read-only flag"));
/* TODO this code looks like it should work but doesn't. Something in cone or similar is not behaving the way it looks like it should.
// In case it's an app which will rely on having its main rsc and its mbm file on the same drive as it, copy them over too
// (Damn cone for not searching drives or working relative to the reg rsc...)
TParsePtrC parse(iPath);
TPtrC exename = parse.Name();
_LIT(KResFmt, "%c:\\Resource\\Apps\\%S.rsc");
_LIT(KResDestFmt, "C:\\Resource\\Apps\\%S.sudoed.rsc");
iTempSrc.Format(KResFmt, iPath[0], &exename);
iTempDest.Format(KResDestFmt, &exename);
iPathsToCleanup->SetReserveL(iPathsToCleanup->Count() + 2);
iPathsToCleanup->AppendL(iTempDest.AllocL());
LeaveIfErr(fm->Copy(iTempSrc, iTempDest), _L("Couldn't copy ancillary file %S to %S"), &iTempSrc, &iTempDest);
LeaveIfErr(Fs().SetAtt(iTempDest, 0, KEntryAttReadOnly), _L("Couldn't unset read-only flag of %S"), &iTempDest);
_LIT(KMbmFmt, "%c:\\Resource\\Apps\\%S.mbm");
iTempSrc.Format(KMbmFmt, iPath[0], &exename);
iTempDest.Format(KMbmFmt, 'c', &exename);
if (!FileExists(iTempDest))
{
iPathsToCleanup->AppendL(iTempDest.AllocL());
LeaveIfErr(fm->Copy(iTempSrc, iTempDest), _L("Couldn't copy ancillary file %S to %S"), &iTempSrc, &iTempDest);
LeaveIfErr(Fs().SetAtt(iTempDest, 0, KEntryAttReadOnly), _L("Couldn't unset read-only flag of %S"), &iTempDest);
}
*/
CleanupStack::PopAndDestroy(fm);
}
void CCmdSudo::FixupExeL()
{
// Now fix up the capabilities or other stuff
RFile file;
CleanupClosePushL(file);
LeaveIfErr(file.Open(Fs(), iNewPath, EFileWrite|EFileStream|EFileShareAny), _L("Couldn't open file"));
E32ImageHeaderV* imageHeader=new(ELeave)E32ImageHeaderV;
CleanupStack::PushL(imageHeader);
TPckg<E32ImageHeaderV> ptr(*imageHeader);
LeaveIfErr(file.Read(ptr, sizeof(E32ImageHeaderV)), _L("Couldn't read E32ImageHeader"));
SSecurityInfo& secinfo = imageHeader->iS;
for (TInt i = 0; i < ECapability_Limit; i++)
{
TCapability cap = (TCapability)i;
if (iCapsToAdd.HasCapability(cap)) secinfo.iCaps.AddCapability(cap);
if (iCapsToRemove.HasCapability(cap)) reinterpret_cast<TCapabilitySet*>(&secinfo.iCaps)->RemoveCapability(cap);
}
if (iOptions.IsPresent(&iSid))
{
secinfo.iSecureId = iSid;
}
if (iOptions.IsPresent(&iVid))
{
secinfo.iVendorId = iVid;
}
if (iHeapMin)
{
imageHeader->iHeapSizeMin = iHeapMin;
}
if (iHeapMax)
{
imageHeader->iHeapSizeMax = iHeapMax;
}
if (iStackSize)
{
imageHeader->iStackSize = iStackSize;
}
if (iProcessPriority)
{
imageHeader->iProcessPriority = iProcessPriority;
}
// Update e32 checksum
imageHeader->iHeaderCrc = KImageCrcInitialiser;
TUint32 crc = 0;
Mem::Crc32(crc, imageHeader, imageHeader->TotalSize());
imageHeader->iHeaderCrc = crc;
LeaveIfErr(file.Write(0, ptr), _L("Couldn't write updated header back to file"));
CleanupStack::PopAndDestroy(2, &file); // imageHeader, file
}
void CCmdSudo::RunExeL()
{
// Now actually execute it
#ifdef __WINS__
if (iChangeBinaryOnDisk)
{
PrintWarning(_L("Updated file written to %S. Not actually executing it."), &iNewPath);
CleanupStack::Pop(); // DeleteModifiedBinary
return;
}
#endif
TRAPL(iChildProcess.CreateL(iNewPath, iArgs ? *iArgs : KNullDesC(), IoSession(), Stdin(), Stdout(), Stderr(), Env()), _L("Failed to execute %S"), &iNewPath);
if (iKeep)
{
Printf(_L("Executing %S...\r\n"), &iNewPath);
}
CleanupStack::Pop(); // DeleteModifiedBinary
if (!iChangeBinaryOnDisk)
{
// Time to get memaccess involved
TRAPD(err, FixupExeInMemoryL(iChildProcess.Process()));
if (err)
{
iChildProcess.Process().Kill(err);
iChildProcess.Close();
User::Leave(err);
}
}
if (iWait)
{
Printf(_L("Process is created but not yet resumed. Press a key to continue...\r\n"));
ReadKey();
Printf(_L("Resuming process...\r\n"));
}
iChildProcess.Run(iStatus);
SetActive();
}
const TDesC& CCmdSudo::Name() const
{
_LIT(KName, "sudo");
return KName;
}
void CCmdSudo::ArgumentsL(RCommandArgumentList& aArguments)
{
aArguments.AppendStringL(iCmd, _L("exe-name"));
aArguments.AppendStringL(iArgs, _L("arguments"));
}
void CCmdSudo::OptionsL(RCommandOptionList& aOptions)
{
aOptions.AppendStringL(iAdd, _L("add-cap"));
aOptions.AppendStringL(iRemove, _L("remove-cap"));
aOptions.AppendUintL(iSid, _L("sid"));
aOptions.AppendUintL(iVid, _L("vid"));
aOptions.AppendUintL(iHeapMin, _L("heap-min"));
aOptions.AppendUintL(iHeapMax, _L("heap-max"));
aOptions.AppendUintL(iStackSize, _L("stack-size"));
aOptions.AppendIntL(iProcessPriority, _L("process-priority"));
aOptions.AppendBoolL(iKeep, _L("keep"));
aOptions.AppendBoolL(iChangeBinaryOnDisk, _L("disk"));
aOptions.AppendBoolL(iWait, _L("wait"));
}
void CCmdSudo::DeleteModifiedBinary(TAny* aSelf)
{
CCmdSudo* self = static_cast<CCmdSudo*>(aSelf);
self->DeleteModifiedBinary();
}
void CCmdSudo::DeleteModifiedBinary()
{
if (iFileIsInCore)
{
#ifdef FSHELL_MEMORY_ACCESS_SUPPORT
iMemAccess.FreeShadowMemory((TLinAddr)Fs().IsFileInRom(iPath), sizeof(TRomImageHeader));
#endif
}
else
{
TInt err = Fs().Delete(iNewPath);
if (err && err != KErrNotFound && err != KErrPathNotFound) PrintError(err, _L("Couldn't delete file %S"), &iNewPath);
}
}
EXE_BOILER_PLATE(CCmdSudo)
TBool CCmdSudo::FileExists(const TDesC& aFileName)
{
TEntry entry;
return Fs().Entry(aFileName, entry) == KErrNone;
}
void CCmdSudo::FixupExeInMemoryL(RProcess& aProcess)
{
#ifdef FSHELL_MEMORY_ACCESS_SUPPORT
LoadMemoryAccessL();
TProcessProperties prop;
prop.iCapsToAdd = iCapsToAdd;
prop.iCapsToRemove = iCapsToRemove;
prop.iProcessPriority = iProcessPriority;
if (iOptions.IsPresent(&iSid))
{
prop.iSid = iSid;
}
if (iOptions.IsPresent(&iVid))
{
prop.iVid = iVid;
}
LeaveIfErr(iMemAccess.SetProcessProperties(aProcess, prop), _L("Couldn't set process properties using memoryaccess"));
#else
(void)aProcess;
LeaveIfErr(KErrNotSupported, _L("Can't fixup process in memory without MemoryAccess, try the --disk option instead"));
#endif
}
void CCmdSudo::CalculateCaps()
{
_LIT(KAll, "All");
// Add caps
iCapsToAdd.SetEmpty();
for (TInt i = 0; i < iAdd.Count(); i++)
{
const TDesC& capName = *iAdd[i];
TCapability cap = CapabilityFromString(capName);
if (cap == ECapability_None)
{
if (capName.CompareF(KAll) == 0)
{
// The pseudo-cap 'All'
iCapsToAdd.SetAllSupported();
}
else
{
PrintWarning(_L("Couldn't understand capability name %S"), &capName);
}
}
else
{
iCapsToAdd.AddCapability(cap);
}
}
// Remove caps
iCapsToRemove.SetEmpty();
for (TInt i = 0; i < iRemove.Count(); i++)
{
const TDesC& capName = *iRemove[i];
TCapability cap = CapabilityFromString(capName);
if (cap == ECapability_None)
{
if (capName.CompareF(KAll) == 0)
{
// The pseudo-cap 'All'
iCapsToRemove.SetAllSupported();
}
else
{
PrintWarning(_L("Couldn't understand capability name %S"), &capName);
}
}
else
{
iCapsToRemove.AddCapability(cap);
}
}
TBool noOptions = (iAdd.Count() == 0) && (iRemove.Count() == 0) && !iOptions.IsPresent(&iSid) && !iOptions.IsPresent(&iVid) && (iHeapMin == 0) && (iHeapMax == 0) && (iStackSize == 0) && (iProcessPriority == 0);
if (noOptions)
{
// Default to All -TCB
iCapsToAdd.SetAllSupported();
iCapsToRemove.AddCapability(ECapabilityTCB);
}
}
void CCmdSudo::FixupCoreExeLC()
{
#ifdef FSHELL_MEMORY_ACCESS_SUPPORT
LoadMemoryAccessL();
const TRomImageHeader* imageHeader = (const TRomImageHeader*)FsL().IsFileInRom(iPath);
if (!imageHeader) LeaveIfErr(KErrNotFound, _L("In FixupCoreExeLC but IsFileInRom returned null??"));
SCapabilitySet caps = imageHeader->iS.iCaps;
for (TInt i = 0; i < ECapability_Limit; i++)
{
TCapability cap = (TCapability)i;
if (iCapsToAdd.HasCapability(cap)) caps.AddCapability(cap);
if (iCapsToRemove.HasCapability(cap)) reinterpret_cast<TCapabilitySet*>(&caps)->RemoveCapability(cap);
}
TPckg<SCapabilitySet> pkg(caps);
LeaveIfErr(iMemAccess.WriteShadowMemory((TLinAddr)&imageHeader->iS.iCaps, pkg), _L("Couldn't write shadow memory for caps"));
if (iOptions.IsPresent(&iSid))
{
TPckg<TUint> pkg(iSid);
LeaveIfErr(iMemAccess.WriteShadowMemory((TLinAddr)&imageHeader->iS.iSecureId, pkg), _L("Couldn't write shadow memory for sid"));
}
if (iOptions.IsPresent(&iVid))
{
TPckg<TUint> pkg(iVid);
LeaveIfErr(iMemAccess.WriteShadowMemory((TLinAddr)&imageHeader->iS.iVendorId, pkg), _L("Couldn't write shadow memory for sid"));
}
if (iHeapMin)
{
TPckg<TUint> pkg(iHeapMin);
LeaveIfErr(iMemAccess.WriteShadowMemory((TLinAddr)&imageHeader->iHeapSizeMin, pkg), _L("Couldn't write shadow memory for iHeapSizeMin"));
}
if (iHeapMax)
{
TPckg<TUint> pkg(iHeapMax);
LeaveIfErr(iMemAccess.WriteShadowMemory((TLinAddr)&imageHeader->iHeapSizeMax, pkg), _L("Couldn't write shadow memory for iHeapSizeMax"));
}
if (iStackSize)
{
TPckg<TUint> pkg(iStackSize);
LeaveIfErr(iMemAccess.WriteShadowMemory((TLinAddr)&imageHeader->iStackSize, pkg), _L("Couldn't write shadow memory for iStackSize"));
}
if (iProcessPriority)
{
TPckg<TUint> pkg(iProcessPriority);
LeaveIfErr(iMemAccess.WriteShadowMemory((TLinAddr)&imageHeader->iPriority, pkg), _L("Couldn't write shadow memory for iProcessPriority"));
}
// DeleteModifiedBinary does the right thing in the case of shadowing
CleanupStack::PushL(TCleanupItem(&CCmdSudo::DeleteModifiedBinary, this));
#else
LeaveIfErr(KErrNotSupported, _L("Can't fixup an exe in Core image without memoryaccess"));
#endif
}
void CCmdSudo::CtrlCPressed()
{
Printf(_L("Ctrl-C pressed, killing %S and cleaning up.\r\n"), &iNewPath);
SetErrorReported(ETrue);
iChildProcess.Process().Kill(KErrAbort);
}
void CCmdSudo::RunL()
{
if (!iKeep)
{
DeleteModifiedBinary();
}
Complete(iStatus.Int());
}