commands/sudo/sudo.cpp
changeset 0 7f656887cf89
child 37 534b01198c2d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/sudo/sudo.cpp	Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,537 @@
+// 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:
+	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);
+
+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;
+	};
+
+
+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;
+	}
+
+CCmdSudo::CCmdSudo()
+	{
+	}
+
+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
+
+	RChildProcess childProcess;
+	TRAPL(childProcess.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(); // Don't delete if user asked for --keep
+		}
+	else
+		{
+		CleanupStack::PopAndDestroy(); // DeleteModifiedBinary - remove the binary before we actually start running it, so it is guaranteed cleaned up even if the user kills us with ctrl-c
+		}
+	if (!iChangeBinaryOnDisk)
+		{
+		// Time to get memaccess involved
+		TRAPD(err, FixupExeInMemoryL(childProcess.Process()));
+		if (err)
+			{
+			childProcess.Process().Kill(err);
+			childProcess.Close();
+			User::Leave(err);
+			}
+		}
+
+	if (iWait)
+		{
+		Printf(_L("Process is created but not yet resumed. Press a key to continue...\r\n"));
+		Stdin().ReadKey();
+		}
+
+	TRequestStatus stat;
+	childProcess.Run(stat);
+	User::WaitForRequest(stat);
+	TInt err = stat.Int();
+	childProcess.Close();
+	User::LeaveIfError(err); // This gets translated to our exe's return code I hope
+	}
+
+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
+	}