// setpriority.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
//

#include <fshell/memoryaccesscmd.h>

using namespace IoUtils;

class CCmdSetpriority;
class CNewThreadNotifier : public CActive
	{
public:
	CNewThreadNotifier(CCmdSetpriority& aCmd, RMemoryAccess& aMemAccess);
	void Start();
	~CNewThreadNotifier();
	void DoCancel();
	void RunL();

private:
	CCmdSetpriority& iCmd;
	RMemoryAccess& iMemAccess;
	};

class CCmdSetpriority : public CMemoryAccessCommandBase
	{
public:
	static CCommandBase* NewLC();
	~CCmdSetpriority();
	void NewThread(TInt aHandle);
private:
	CCmdSetpriority();
private: // From CCommandBase.
	virtual const TDesC& Name() const;
	virtual void DoRunL();
	virtual void ArgumentsL(RCommandArgumentList& aArguments);
	virtual void OptionsL(RCommandOptionList& aOptions);
private:
	RArray<TUint> iTids;
	RArray<TUint> iPids;
	TInt iPriority;
	HBufC* iName;
	TProcessKernelInfo iProcInfo;
	TThreadKernelInfo iThreadInfo;
	CNewThreadNotifier* iNotifier;
	};


CCommandBase* CCmdSetpriority::NewLC()
	{
	CCmdSetpriority* self = new(ELeave) CCmdSetpriority();
	CleanupStack::PushL(self);
	self->BaseConstructL();
	return self;
	}

CCmdSetpriority::~CCmdSetpriority()
	{
	delete iNotifier;
	delete iName;
	iTids.Close();
	iPids.Close();
	}

CCmdSetpriority::CCmdSetpriority()
	: CMemoryAccessCommandBase(EManualComplete)
	{
	}

const TDesC& CCmdSetpriority::Name() const
	{
	_LIT(KName, "setpriority");
	return KName;
	}

void CCmdSetpriority::ArgumentsL(RCommandArgumentList& aArguments)
	{
	aArguments.AppendIntL(iPriority, _L("priority"));
	}

void CCmdSetpriority::OptionsL(RCommandOptionList& aOptions)
	{
	aOptions.AppendStringL(iName, _L("match"));
	aOptions.AppendUintL(iPids, _L("pid"));
	aOptions.AppendUintL(iTids, _L("tid"));
	}


EXE_BOILER_PLATE(CCmdSetpriority)

void CCmdSetpriority::DoRunL()
	{
	LoadMemoryAccessL();
	TInt err = KErrNone;

	// Fix up priorities that we had to make different to kernel cos fshell can't handle negative arguments
	// See enum TThrdPriority in kern_priv.h for the meaning of these negative numbers
	switch(iPriority)
		{
		case 101: iPriority = -8; break;
		case 102: iPriority = -7; break;
		case 103: iPriority = -6; break;
		case 104: iPriority = -5; break;
		case 105: iPriority = -4; break;
		case 106: iPriority = -3; break;
		case 107: iPriority = -2; break;
		default:
			break;
		}

	if (iName)
		{
		TPtrC8 name8 = iName->Des().Collapse();
		LeaveIfErr(iMemAccess.SetPriorityOverride(iPriority, name8), _L("Couldn't set priority override"));
		iNotifier = new(ELeave) CNewThreadNotifier(*this, iMemAccess);
		iNotifier->Start();
		return;
		}
	
	if (iTids.Count() && iPids.Count())
		{
		LeaveIfErr(KErrArgument, _L("You cannot specify both --tid and --pid - a single priority cannot be valid for thread and process."));
		}

	for (TInt i = 0; i < iTids.Count(); i++)
		{
		TUint id = iTids[i];
		RThread thread;
		err = iMemAccess.RThreadForceOpen(thread, id);
		if (!err) err = iMemAccess.SetThreadPriority(thread, iPriority);
		if (err) PrintError(err, _L("Couldn't set priority for thread %u"), id);
		thread.Close();
		}
	
	for (TInt i = 0; i < iPids.Count(); i++)
		{
		TUint id = iPids[i];
		// Get KProcessFlagPriorityControl flag
		RProcess process;
		err = process.Open(id);
		LeaveIfErr(err, _L("Couldn't open process with ID %u"), id);

		TBool priorityControlAllowed = EFalse;
		TPckg<TProcessKernelInfo> pkg(iProcInfo);
		err = iMemAccess.GetObjectInfoByHandle(EProcess, RThread().Id(), process.Handle(), pkg);
		if (err)
			{
			PrintWarning(_L("Couldn't get priority control flag setting (%d)\r\n"), err);
			}
		else
			{
			if (iProcInfo.iFlags & KProcessFlagPriorityControl) priorityControlAllowed = ETrue;
			}

		if (!priorityControlAllowed)
			{
			PrintError(KErrAccessDenied, _L("This process does not allow setting of its priority"));
			}
		else if (iPriority != EPriorityBackground && iPriority != EPriorityForeground)
			{
			PrintError(KErrAccessDenied, _L("The kernel will ignore requests to set a process priority that isn't Background (250) or Foreground (350). I can't see how to get round this even using memory access. Sorry."));
			}
		process.SetPriority((TProcessPriority)iPriority);
		process.Close();
		}
	Complete(KErrNone);
	}

void CCmdSetpriority::NewThread(TInt aHandle)
	{
	if (aHandle == KErrNotSupported)
		{
		PrintError(KErrNotSupported, _L("Kernel was not compiled with __DEBUGGER_SUPPORT__, can't get thread creation notifications."));
		Complete(aHandle);
		}
	else if (aHandle < 0)
		{
		PrintError(aHandle, _L("Failed to get new thread notification, or couldn't open handle to thread."));
		Complete(aHandle);
		}
	else
		{
		RThread thread;
		thread.SetHandle(aHandle);
		TFullName name(thread.FullName());
		TInt priority = 0;
		TPckg<TThreadKernelInfo> pkg(iThreadInfo);
		TInt err = iMemAccess.GetObjectInfoByHandle(EThread, RThread().Id(), aHandle, pkg);
		if (!err)
			{
			priority = iThreadInfo.iThreadPriority;
			}

		TUint tid = thread.Id();
		Printf(_L("New thread id %u name %S priority adjusted to %d\r\n"), tid, &name, priority);
		thread.Close();
		}
	}

CNewThreadNotifier::CNewThreadNotifier(CCmdSetpriority& aCmd, RMemoryAccess& aMemAccess)
	: CActive(CActive::EPriorityStandard), iCmd(aCmd), iMemAccess(aMemAccess)
	{
	CActiveScheduler::Add(this);
	}

CNewThreadNotifier::~CNewThreadNotifier()
	{
	Cancel();
	}

void CNewThreadNotifier::DoCancel()
	{
	iMemAccess.CancelNotifyThreadCreation();
	}

void CNewThreadNotifier::RunL()
	{
	TInt stat = iStatus.Int();
	if (stat >= 0)
		{
		Start();
		}
	iCmd.NewThread(stat);
	}

void CNewThreadNotifier::Start()
	{
	if (!IsActive())
		{
		iMemAccess.NotifyThreadCreation(iStatus);
		SetActive();
		}
	}
