// 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());
	}
