/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS shutdown/logoff utility
 * FILE:            base/application/shutdown/shutdown.c
 * PURPOSE:         Initiate logoff, shutdown or reboot of the system
 */

#include "precomp.h"

// Print information about which commandline arguments the program accepts.
static void PrintUsage() {
	LPTSTR lpUsage = NULL;

	if( AllocAndLoadString( &lpUsage,
							GetModuleHandle(NULL),
							IDS_USAGE ) )
	{
		_putts( lpUsage );
	}
}

struct CommandLineOptions {
	BOOL abort; // Not used yet
	BOOL force;
	BOOL logoff;
	BOOL restart;
	BOOL shutdown;
};

struct ExitOptions {
	// This flag is used to distinguish between a user-initiated LOGOFF (which has value 0)
	// and an underdetermined situation because user didn't give an argument to start Exit.
	BOOL shouldExit;
	// flags is the type of shutdown to do - EWX_LOGOFF, EWX_REBOOT, EWX_POWEROFF, etc..
	UINT flags;
	// reason is the System Shutdown Reason code. F.instance SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED.
	DWORD reason;
};

// Takes the commandline arguments, and creates a struct which matches the arguments supplied.
static struct CommandLineOptions ParseArguments(int argc, TCHAR *argv[])
{
	struct CommandLineOptions opts;
	int i;

	// Reset all flags in struct
	opts.abort = FALSE;
	opts.force = FALSE;
	opts.logoff = FALSE;
	opts.restart = FALSE;
	opts.shutdown = FALSE;

	for (i = 1; i < argc; i++)
	{
		if (argv[i][0] == '-' || argv[i][0] == '/')
		{
			switch(argv[i][1]) {
				case '?':
					PrintUsage();
					exit(0);
				case 'f':
				case 'F':
					opts.force = TRUE;
					break;
				case 'l':
				case 'L':
					opts.logoff = TRUE;
					break;
				case 'r':
				case 'R':
					opts.restart = TRUE;
					break;
				case 's':
				case 'S':
					opts.shutdown = TRUE;
					break;
				default:
					// Unknown arguments will exit program.
					PrintUsage();
					exit(0);
					break;
			}
		}
	}

	return opts;
}

// Converts the commandline arguments to flags used to shutdown computer
static struct ExitOptions ParseCommandLineOptionsToExitOptions(struct CommandLineOptions opts)
{
	struct ExitOptions exitOpts;
	exitOpts.shouldExit = TRUE;

	// Sets ONE of the exit type flags
	if (opts.logoff)
		exitOpts.flags = EWX_LOGOFF;
	else if (opts.restart)
		exitOpts.flags = EWX_REBOOT;
	else if(opts.shutdown)
		exitOpts.flags = EWX_POWEROFF;
	else
	{
		exitOpts.flags = 0;
		exitOpts.shouldExit = FALSE;
	}

	// Sets additional flags
	if (opts.force)
	{
		exitOpts.flags = exitOpts.flags | EWX_FORCE;

		// This makes sure that we log off, also if there is only the "-f" option specified.
		// The Windows shutdown utility does it the same way.
		exitOpts.shouldExit = TRUE;
	}

	// Reason for shutdown
	// Hardcoded to "Other (Planned)"
	exitOpts.reason = SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED;

	return exitOpts;
}

// Writes the last error as both text and error code to the console.
void DisplayLastError()
{
	int errorCode = GetLastError();
	LPTSTR lpMsgBuf;

	// Display the error message to the user
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL,
		errorCode,
		LANG_USER_DEFAULT,
		(LPTSTR) &lpMsgBuf,
		0,
		NULL);

	_ftprintf(stderr, lpMsgBuf);
	_ftprintf(stderr, _T("Error code: %d\n"), errorCode);
}

void EnableShutdownPrivileges()
{
	HANDLE token;
	TOKEN_PRIVILEGES privs;

	// Check to see if the choosen action is allowed by the user. Everyone can call LogOff, but only privilieged users can shutdown/restart etc.
	if (! OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token))
	{
		DisplayLastError();
		exit(1);
	}

	// Get LUID (Locally Unique Identifier) for the privilege we need
	if (!LookupPrivilegeValue(
			NULL, // system - NULL is localsystem
			SE_SHUTDOWN_NAME, // name of the privilege
			&privs.Privileges[0].Luid) // output
		)
	{
		DisplayLastError();
		exit(1);
	}
	// and give our current process (i.e. shutdown.exe) the privilege to shutdown the machine.
	privs.PrivilegeCount = 1;
	privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	if (AdjustTokenPrivileges(
			token,
			FALSE,
			&privs,
			0,
			(PTOKEN_PRIVILEGES)NULL, // previous state. Set to NULL, we don't care about previous state.
			NULL
			) == 0) // return value 0 means failure
		{
			DisplayLastError();
			exit(1);
		}
}

 // Main entry for program
int _tmain(int argc, TCHAR *argv[])
{
	struct CommandLineOptions opts;
	struct ExitOptions exitOpts;

	if (argc == 1) // i.e. no commandline arguments given
	{
		PrintUsage();
		exit(0);
	}

	opts = ParseArguments(argc, argv);
	exitOpts = ParseCommandLineOptionsToExitOptions(opts);

	// Perform the shutdown/restart etc. action
	if (exitOpts.shouldExit)
	{
		EnableShutdownPrivileges();

		if (!ExitWindowsEx(exitOpts.flags, exitOpts.reason))
		{
			DisplayLastError();
			exit(1);
		}
	}
	return 0;
}

// EOF
